Post

RecyclerView实现一个树形菜单

很早想写这样一个 Demo,以前实现树形菜单使用的是TreeViewList,因为当时觉得ExpandableListView实现多级菜单,一个继承了 ListView 的封装,因为那个时候我也不会写,后来自己试着写了一下,发现根本没有必要自定义什么控件,直接使用RecyclerView,只需要自己控制一下数据源的转化就可以了.

技术要点不多:

  • 无论你的数据源是什么样的,都把它转化成这样的,为了方便递归
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    {
      "tree": {
          "children": [
              {
                  "available": true,
                  "children": [],
                  "id": "548005da36ec3532c4a18391",
                  "name": "第一轮复习"
              }
          ],
      "id": "gaozhongshuxue",
      "name": "高中数学"
      }
    }
    
  • 构建一个便于递归的实体类,即它能存储它全部的子节点,我下面简单的写了一个
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    public class Course {
      //标识
      @Expose
      @SerializedName("id")
      public String id;
      //描述信息
      @Expose
      @SerializedName("name")
      public String name;
      //层级
      public int level;
      //是否打开的状态
      public boolean open;
      //父节点的标识
      public String parentId;
      //子节点的容器
      public LinkedList<Course> children = new LinkedList<>();
    
      public boolean hasChild() {
          return children != null && children.size() > 0;
      }
      public void addChildren(LinkedList<Course> children) {
          this.children.clear();
          this.children.addAll(children);
      }
    }
    
  • 如何递归呢?也是很简单的,我这么写其实是有点冗余了,不过是个 Demo 就不改了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void createTree(Course container, JSONArray children, String parentId, int level) throws JSONException {
	if (children != null) {
		int childrenSize = children.length();
		LinkedList<Course> tree = new LinkedList<>();
		for (int i = 0; i < childrenSize; i++) {
			JSONObject item = children.getJSONObject(i);
			Course course = new Course();
			course.id = item.getString("id");
			course.name = item.getString("name");
			course.level = level;
 			course.open = false;
			course.parentId = parentId;
			JSONArray children11 = item.getJSONArray("children");
			createTree(course, children11, course.id, level + 1);
			tree.add(course);
			if (container == null) {
				//如果是 null,那么表示这个是最顶层的节点,需要把这个对象 add 进去
 				mData.add(course);
			}
		}
		if (container != null) {
			container.addChildren(tree);
		}
	}
}
  • 每次 item 的点击的时候我们需要判断这个 item 的对象是不是一个拥有子节点的对象,然后做分发处理,这个一般需要获取到 Adapter 的实例以及全部数据,我的 Demo 就不注意结构了,我这里考虑了动画效果而使用了notifyItemRangeInserted,notifyItemRangeRemoved,如果你不需要动画,直接获取dispatchClick返回值是 true 时直接调用notifyDataSetChanged也是可以的,如果是 false 则表示这个是一个最终节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public boolean dispatchClick(LinkedList<Course> container, Course course) {
	if (container == null || course == null) {
		return false;
	}
	if (course.hasChild()) {
		int insertPosition = container.indexOf(course) + 1;
		if (course.open) {
			size = 0;
			removeAllChildren(container, course);
			notifyItemRangeRemoved(insertPosition, size);
		} else {
			course.open = true;
			container.addAll(insertPosition, course.children);
			notifyItemRangeInserted(insertPosition, course.children.size());
		}

		return true;
	}

	return false;
}

private void removeAllChildren(LinkedList<Course> container, Course course) {

	course.open = false;
	int childrenSize = course.children.size();
	for (Course tree11 : course.children) {
		if (tree11.hasChild() && tree11.open) {
			tree11.open = false;
			removeAllChildren(container, tree11);
		}
	}

	size += childrenSize;
	container.removeAll(course.children);
}
  • 现在基本的结构就完成了,RecyclerView默认的动画可能看不出来弹出效果,我们需要重写一个,现在需要继承SimpleItemAnimator,修改两个方法
1
2
3
4
5
6
7
8
9
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
	resetAnimation(holder);
	ViewCompat.setTranslationY(holder.itemView, -(holder.itemView.getMeasuredHeight() / 2));
	ViewCompat.setAlpha(holder.itemView, 0);
	mPendingAdditions.add(holder);
	return true;
}
//animateRemove的就不写了

源码可见https://github.com/Haoxiqiang/TreeView

This post is licensed under CC BY 4.0 by the author.