花了几天研究了下鸿扬大神的博客《Android打造任意层级树形控件,考验你的数据结构和设计》,再结合公司项目改造改造,现在做个笔记。

先看看Demo的实现效果。首先看的是多选效果

再看看单选效果图。

(不好意思,还没学会整动态图,两张图片看不出什么区别哈)

 

 

先回顾下数据结构中树的几个重要概念。

(1)一棵树是N个节点和N-1条边的集合。

(2)除去根节点外,每一个节点都有一个父亲,每条边都将某个节点连接到它的父亲。

(3)一棵树的深度等于它的最深的树叶的深度;该深度总是等于这棵树的高度

 

将要打造的树形控件本质上是一个listView,既然是树形的,那么listView的item本质上其实就是树的节点,所以每个item都得具备一下树节点的属性吧,比如说它的父节点是谁?儿子节点都有哪些等等,所以我们需要将我们从服务器接收回来的数据转化成节点模式的数据,这里就新建一个类Node。

先看看Node类必不可少的几个属性吧,一个是自身标志id、一个是父辈标志pid,还有一个是你需要在页面展示的内容,比如说你这个树形控件展示的机构部门,那么这个name就是机构名称,如果这个属性控件展示的人员,那么这个name就是人员的姓名,这里我把id,pid都设置成字符串类型,主要是为了防止有时候id,pid可能是非int类型的数据,比如说带字符串的id,pid或者超过2147483647的数字。所以索性就将id,pid设置成字符串类型。

  1 package com.example.keranbin.testdemo.treeHelp;
  2 
  3 import java.util.ArrayList;
  4 import java.util.List;
  5 
  6 /**
  7  * Created by keranbin on 2016/8/30.
  8  */
  9 public class Node {
 10     //id
 11     private String id;
 12 
 13     //父辈id
 14     private String pid = "";
 15 
 16     //要在树形控件上展示的内容,比如说机构名称。
 17     private String name;
 18 
 19     //树的层级
 20     private int level;
 21 
 22     // 是否是展开的
 23     private boolean isExpand = false;
 24     
 25     //是否是选择的
 26     private boolean isChoose=false;
 27 
 28     //缩进图标
 29     private int icon;
 30 
 31     //父亲节点
 32     private Node parent;
 33 
 34     //儿子节点集合
 35     private List<Node> children = new ArrayList<>();
 36     
 37     
 38 
 39 
 40     public Node(String id, String pid, String name) {
 41         this.id = id;
 42         this.pid = pid;
 43         this.name = name;
 44 
 45     }
 46 
 47     public String getId() {
 48         return id;
 49     }
 50 
 51     public void setId(String id) {
 52         this.id = id;
 53     }
 54 
 55     public String getPid() {
 56         return pid;
 57     }
 58 
 59     public void setPid(String pid) {
 60         this.pid = pid;
 61     }
 62 
 63     public String getName() {
 64         return name;
 65     }
 66 
 67     public void setName(String name) {
 68         this.name = name;
 69     }
 70 
 71 
 72     /**
 73      * 得到当前节点的层级,如果当前节点没有父节点,则该节点
 74      *是根节点,否则当前节点的层级为当前节点父节点的层级加1
 75      * @return
 76      */
 77     public int getLevel() {
 78         return parent == null ? 0 : parent.getLevel() + 1;
 79     }
 80 
 81     public void setLevel(int level) {
 82         this.level = level;
 83     }
 84 
 85     public boolean isExpand() {
 86         return isExpand;
 87     }
 88 
 89     /**
 90      * 设置当前节点是否展开,如果是false,那么递归关闭当前节点的所有子节点
 91      *
 92      * @param expand
 93      */
 94     public void setExpand(boolean expand) {
 95         isExpand = expand;
 96         if (!expand) {
 97             for (Node node : children) {
 98                 node.setExpand(false);
 99             }
100         }
101     }
102 
103     public boolean isChoose() {
104         return isChoose;
105     }
106 
107     public void setChoose(boolean choose) {
108         isChoose = choose;
109     }
110 
111     public int getIcon() {
112         return icon;
113     }
114 
115     public void setIcon(int icon) {
116         this.icon = icon;
117     }
118 
119     public Node getParent() {
120         return parent;
121     }
122 
123     public void setParent(Node parent) {
124         this.parent = parent;
125     }
126 
127     public List<Node> getChildren() {
128         return children;
129     }
130 
131     public void setChildren(List<Node> children) {
132         this.children = children;
133     }
134 
135 
136     /**
137      * 是否是根节点
138      */
139     public boolean isRoot() {
140         return parent == null;
141     }
142 
143     /**
144      * 是否是展开状态,
145      */
146     public boolean isParentExpand() {
147         if (parent == null)
148             return false;
149         return parent.isExpand();
150     }
151 
152 
153     /**
154      * 是否是叶子节点
155      *
156      * @return
157      */
158     public boolean isLeft() {
159         return children.size() == 0;
160     }
161 }

Node类已经打造完毕啦,那么我们如何将服务器端取回的bean转化成我们的Node类呢?答案是通过注解+反射。

 1 package com.example.keranbin.business.ccsq.annotion;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 /**
 9  * Created by keranbin on 2016/8/30.
10  */
11 
12 
13 @Target(ElementType.FIELD)
14 @Retention(RetentionPolicy.RUNTIME)
15 public @interface TreeNodeId {
16 }
 1 package com.example.keranbin.business.ccsq.annotion;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 /**
 9  * Created by keranbin on 2016/8/30.
10  */
11 @Target(ElementType.FIELD)
12 @Retention(RetentionPolicy.RUNTIME)
13 public @interface TreeNodePid {
14 }
 1 package com.example.keranbin.business.ccsq.annotion;
 2 
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 
 8 /**
 9  * Created by keranbin on 2016/8/30.
10  */
11 @Target(ElementType.FIELD)
12 @Retention(RetentionPolicy.RUNTIME)
13 public @interface TreeNodeLabel {
14 }

我们新建一个工具类TreeHelp,先看看怎么将bean转化成Node。

 1    /**
 2      * 将服务器端取回的数据转化成Node
 3      * @param datas
 4      * @return
 5      * @throws IllegalAccessError
 6      */
 7     public static <T> List<Node> convertDataToNodes(List<T> datas) throws IllegalAccessException {
 8         List<Node> nodes = new ArrayList<>();
 9         Node node = null;
10         for (T t : datas) {
11             String id = "";
12             String pid = "";
13             String label = null;
14             String type=null;
15             node = null;
16             Class c = t.getClass();
17             Field fields[] = c.getDeclaredFields();
18 
19             for (Field field : fields) {
20                 if (field.getAnnotation(TreeNodeId.class) != null) {
21                     //设置访问权限,强制性的可以访问
22                     field.setAccessible(true);
23                     id= (String) field.get(t);
24                 }
25 
26                 if (field.getAnnotation(TreeNodePid.class) != null) {
27                     //设置访问权限,强制性的可以访问
28                     field.setAccessible(true);
29                     pid= (String) field.get(t);
30                 }
31 
32                 if (field.getAnnotation(TreeNodeLabel.class) != null) {
33                     //设置访问权限,强制性的可以访问
34                     field.setAccessible(true);
35                     label = (String) field.get(t);
36                 }
38             node = new Node(id, pid, label,type);
39             nodes.add(node);
40         }

这时候我们已经将bean转化成Node啦,但是我们现在所得到的Node集合中的各个Node是毫无关联的,父节点并不知道谁是他的儿子,儿子节点也不知道谁是他的父亲。所以我们得处理下

 1    /**
 2          * 循环对比两个Node,设置节点间关联关系
 3          */
 4         for (int i = 0; i < nodes.size(); i++) {
 5             Node n = nodes.get(i);
 6             for (int j = i+1; j < nodes.size(); j++) {
 7                 Node m = nodes.get(j);
 8                 if (m.getId().equals( n.getPid())) {//m是n的父节点
 9                     m.getChildren().add(n);
10                     n.setParent(m);
11                 } else if (m.getPid().equals( n.getId())) {//n是m的父节点
12                     n.getChildren().add(m);
13                     m.setParent(n);
14                 }
15             }
16         }

除此之外,我们还得为节点设置图标。

 1  /**
 2      * 为节点设置图标
 3      * 逻辑:(1)如果当前节点有孩子节点并且处于展开状态,那么设置向下的图标
 4      * (2)如果当前节点有孩子节点并且处于闭合状态,那么设置向右的图标
 5      * (3)如果当前节点没有孩子节点,传参-1,到时候判断是-1,不设置图标
 6      * @param n
 7      */
 8     private static void setNodeIcon(Node n) {
 9         if (n.getChildren().size() > 0 && n.isExpand()) {
10             n.setIcon(R.mipmap.tree_ex);
11         } else if (n.getChildren().size() > 0 && !n.isExpand()) {
12             n.setIcon(R.mipmap.tree_ec);
13         } else {
14             n.setIcon(-1);
15         }
16     }

这时候我们已经设置好节点的关联关系啦,但是,这个时候的nodes是杂乱无章的,试想一下,我们不可能一进这个页面从根节点到所有的叶子节点都展示给用户吧,要是树的层级少那还乐观,但是要是树的层级是100,1000呢,所以我们需要给Nodes集合中的Node排一下序,然后还需要设置一个方法来过滤出可见的节点。

 1 **
 2      * 得到排序后的Nodes
 3      * @param datas
 4      * @param <T>
 5      * @return
 6      */
 7     public static <T> List<Node> getSortedNodes(List<T> datas, int defaultExpandLevel) throws IllegalAccessException {
 8         List<Node> result = new ArrayList<>();
 9         List<Node> nodes = convertDataToNodes(datas);
10         //获取树的根节点
11         List<Node> rootNodes = getRootNodes(nodes);
12 
13         for (Node node : rootNodes) {
14             addNode(result, node, defaultExpandLevel, 1);
15         }
16         return result;
17     }
 /**
     * 从所有节点中过滤出根节点
     * @param nodes
     * @return
     */
    private static List<Node> getRootNodes(List<Node> nodes) {
        List<Node> root = new ArrayList<>();
        for (Node node : nodes) {
            if (node.isRoot()) {
                root.add(node);
            }
        }
        return root;
    }
 1  /**
 2      * 把一个节点的所有孩子节点都放入result
 3      * @param result
 4      * @param node               当前节点
 5      * @param defaultExpandLevel 默认初始化是展开几层
 6      * @param currentLevel       当前节点层级
 7      */
 8     private static void addNode(List<Node> result, Node node, int defaultExpandLevel, int currentLevel) {
 9         result.add(node);
10         //如果默认展开层级大于或者当前节点层级,那么设置当前层级是展开的,否则设置是闭合的
11         if (defaultExpandLevel >= currentLevel){
12             node.setExpand(true);
13         }
14 
15         //如果当前节点已经是叶子节点,那么不需要做任何处理啦
16         if(node.isLeft()){
17             return;
18 
19         }else{
20             //如果当前节点不是叶子节点,递归循环遍历不断的添加子节点
21             for(int i=0;i<node.getChildren().size();i++){
22                 addNode(result,node.getChildren().get(i),defaultExpandLevel,currentLevel+1);
23             }
24         }
25     }
 1   /**
 2      * 过滤出可见的节点
 3      * @param nodes
 4      * @return
 5      */
 6     public static List<Node> filterVisibleNodes(List<Node> nodes){
 7         List<Node> visibleNodes=new ArrayList<>();
 8         for (Node node:nodes){
 9             //如果当前节点是根节点或者当前节点的父节点是展开的
10             if (node.isRoot()||node.isParentExpand()){
11                 setNodeIcon(node);
12                 visibleNodes.add(node);
13             }
14         }
15         return visibleNodes;
16     }

至此,我们的treeHelp类打造完毕,整个类的代码如下。

  1 package com.example.keranbin.business.help.treeHelp;
  2 
  3 import com.example.keranbin.business.R;
  4 import com.example.keranbin.business.ccsq.annotion.TreeNodeId;
  5 import com.example.keranbin.business.ccsq.annotion.TreeNodeLabel;
  6 import com.example.keranbin.business.ccsq.annotion.TreeNodePid;
  7 import com.example.keranbin.business.ccsq.annotion.TreeNodeType;
  8 
  9 import java.lang.reflect.Field;
 10 import java.util.ArrayList;
 11 import java.util.List;
 12 
 13 /**
 14  * Created by keranbin on 2016/8/30.
 15  */
 16 public class TreeHelp {
 17     /**
 18      * 将服务器端取回的数据转化成Node
 19      * @param datas
 20      * @return
 21      * @throws IllegalAccessError
 22      */
 23     public static <T> List<Node> convertDataToNodes(List<T> datas) throws IllegalAccessException {
 24         List<Node> nodes = new ArrayList<>();
 25         Node node = null;
 26         for (T t : datas) {
 27             String id = "";
 28             String pid = "";
 29             String label = null;
 30             node = null;
 31             Class c = t.getClass();
 32             Field fields[] = c.getDeclaredFields();
 33 
 34             for (Field field : fields) {
 35                 if (field.getAnnotation(TreeNodeId.class) != null) {
 36                     //设置访问权限,强制性的可以访问
 37                     field.setAccessible(true);
 38                     id= (String) field.get(t);
 39                 }
 40 
 41                 if (field.getAnnotation(TreeNodePid.class) != null) {
 42                     //设置访问权限,强制性的可以访问
 43                     field.setAccessible(true);
 44                     pid= (String) field.get(t);
 45                 }
 46 
 47                 if (field.getAnnotation(TreeNodeLabel.class) != null) {
 48                     //设置访问权限,强制性的可以访问
 49                     field.setAccessible(true);
 50                     label = (String) field.get(t);
 51                 }
 52 
 53 
 54             }
 55 
 56 
 57             node = new Node(id, pid, label);
 58             nodes.add(node);
 59         }
 60 
 61 
 62         /**
 63          * 循环对比两个Node,设置节点间关联关系
 64          */
 65         for (int i = 0; i < nodes.size(); i++) {
 66             Node n = nodes.get(i);
 67             for (int j = i+1; j < nodes.size(); j++) {
 68                 Node m = nodes.get(j);
 69                 if (m.getId().equals( n.getPid())) {//m是n的父节点
 70                     m.getChildren().add(n);
 71                     n.setParent(m);
 72                 } else if (m.getPid().equals( n.getId())) {//n是m的父节点
 73                     n.getChildren().add(m);
 74                     m.setParent(n);
 75                 }
 76             }
 77         }
 78 
 79         /**
 80          * 设置节点的图标
 81          */
 82         for (Node n : nodes) {
 83             setNodeIcon(n);
 84         }
 85         return nodes;
 86     }
 87 
 88     /**
 89      * 为节点设置图标
 90      * 逻辑:(1)如果当前节点有孩子节点并且处于展开状态,那么设置向下的图标
 91      * (2)如果当前节点有孩子节点病区处于闭合状态,那么设置向右的图标
 92      * (3)如果当前节点没有孩子节点,传参-1,到时候判断是-1,不设置图标
 93      * @param n
 94      */
 95     private static void setNodeIcon(Node n) {
 96         if (n.getChildren().size() > 0 && n.isExpand()) {
 97             n.setIcon(R.mipmap.tree_ex);
 98         } else if (n.getChildren().size() > 0 && !n.isExpand()) {
 99             n.setIcon(R.mipmap.tree_ec);
100         } else {
101             n.setIcon(-1);
102         }
103     }
104 
105     /**
106      * 得到排序后的Nodes
107      * @param datas
108      * @param <T>
109      * @return
110      */
111     public static <T> List<Node> getSortedNodes(List<T> datas, int defaultExpandLevel) throws IllegalAccessException {
112         List<Node> result = new ArrayList<>();
113         List<Node> nodes = convertDataToNodes(datas);
114         //获取树的根节点
115         List<Node> rootNodes = getRootNodes(nodes);
116 
117         for (Node node : rootNodes) {
118             addNode(result, node, defaultExpandLevel, 1);
119         }
120         return result;
121     }
122 
123     /**
124      * 从所有节点中过滤出根节点
125      * @param nodes
126      * @return
127      */
128     private static List<Node> getRootNodes(List<Node> nodes) {
129         List<Node> root = new ArrayList<>();
130         for (Node node : nodes) {
131             if (node.isRoot()) {
132                 root.add(node);
133             }
134         }
135         return root;
136     }
137 
138     /**
139      * 把一个节点的所有孩子节点都放入result
140      * @param result
141      * @param node               当前节点
142      * @param defaultExpandLevel 默认初始化是展开几层
143      * @param currentLevel       当前节点层级
144      */
145     private static void addNode(List<Node> result, Node node, int defaultExpandLevel, int currentLevel) {
146         result.add(node);
147         //如果默认展开层级大于或者当前节点层级,那么设置当前层级是展开的,否则设置是闭合的
148         if (defaultExpandLevel >= currentLevel){
149             node.setExpand(true);
150         }
151 
152         //如果当前节点已经是叶子节点,那么不需要做任何处理啦
153         if(node.isLeft()){
154             return;
155 
156         }else{
157             //如果当前节点不是叶子节点,递归循环遍历不断的添加子节点
158             for(int i=0;i<node.getChildren().size();i++){
159                 addNode(result,node.getChildren().get(i),defaultExpandLevel,currentLevel+1);
160             }
161         }
162     }
163 
164     /**
165      * 过滤出可见的节点
166      * @param nodes
167      * @return
168      */
169     public static List<Node> filterVisibleNodes(List<Node> nodes){
170         List<Node> visibleNodes=new ArrayList<>();
171         for (Node node:nodes){
172             //如果当前节点是根节点或者当前节点的父节点是展开的
173             if (node.isRoot()||node.isParentExpand()){
174                 setNodeIcon(node);
175                 visibleNodes.add(node);
176             }
177         }
178         return visibleNodes;
179     }
180 }

 再来看看我们的公共adapter

 1 package com.example.keranbin.testdemo.treeHelp;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.AdapterView;
 8 import android.widget.BaseAdapter;
 9 import android.widget.ListView;
10 
11 import java.util.List;
12 
13 /**
14  * Created by keranbin on 2016/8/30.
15  */
16 public abstract class TreeListViewAdapter<T> extends BaseAdapter implements AdapterView.OnItemClickListener {
17     protected Context context;
18     protected ListView listView;
19     protected List<Node> mAllNodes;
20     protected List<Node> mVisibleNodes;
21     protected LayoutInflater inflater;
22 
23     private OnTreeNodeClickListener onTreeNodeClickListener;
24     
25     public TreeListViewAdapter(Context context, ListView listView, List<T> datas, int defaultExpandLevel) throws IllegalAccessException {
26         this.context = context;
27         this.listView = listView;
28         mAllNodes = TreeHelp.getSortedNodes(datas,defaultExpandLevel);
29         mVisibleNodes = TreeHelp.filterVisibleNodes(mAllNodes);
30         inflater = LayoutInflater.from(context);
31 
32         listView.setOnItemClickListener(this);
33     }
34 
35     @Override
36     public int getCount() {
37         return mVisibleNodes.size();
38     }
39 
40     @Override
41     public Object getItem(int position) {
42         return mVisibleNodes.get(position);
43     }
44 
45     @Override
46     public long getItemId(int position) {
47         return position;
48     }
49 
50     @Override
51     public View getView(int position, View view, ViewGroup viewGroup) {
52         Node node=mVisibleNodes.get(position);
53         view=getConvertView(node,position,view,viewGroup);
54         //设置内边距
55         view.setPadding(30*node.getLevel(),3,3,3);
56         return view;
57     }
58 
59 
60     /**
61      * 设置点击展开或者收缩
62      *
63      * @param position
64      */
65     private void expandOrCollapse(int position) {
66         Node node = mVisibleNodes.get(position);
67         if (node != null) {
68             if (node.isLeft())
69                 return;
70             node.setExpand(!node.isExpand());
71             mVisibleNodes=TreeHelp.filterVisibleNodes(mAllNodes);
72             notifyDataSetChanged();
73         }
74     }
75 
76     @Override
77     public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
78         expandOrCollapse(position);
79         if (onTreeNodeClickListener!=null){
80             onTreeNodeClickListener.setOnClick(mVisibleNodes.get(position),position);
81         }
82     }
83     
84     public void setOnTreeNodeClickListener(OnTreeNodeClickListener onTredNodeClickListener){
85         this.onTreeNodeClickListener=onTredNodeClickListener;
86     }
87 
88     /**
89      * 设置node的点击回调
90      */
91     public interface OnTreeNodeClickListener{
92         void setOnClick(Node node, int position);
93     }
94     
95     public abstract View getConvertView(Node node,int position, View view, ViewGroup viewGroup);
96 }

我们建一个CheckBoxTreeListViewAdapter继承自TreeViewAdapter

  1 package com.example.keranbin.testdemo;
  2 
  3 import android.content.Context;
  4 import android.view.View;
  5 import android.view.ViewGroup;
  6 import android.widget.CheckBox;
  7 import android.widget.CompoundButton;
  8 import android.widget.ImageView;
  9 import android.widget.ListView;
 10 import android.widget.TextView;
 11 
 12 import com.example.keranbin.testdemo.treeHelp.Node;
 13 import com.example.keranbin.testdemo.treeHelp.TreeListViewAdapter;
 14 
 15 import java.util.ArrayList;
 16 import java.util.List;
 17 
 18 /**
 19  * Created by keranbin on 2016/8/30.
 20  */
 21 public class CheckBoxTreeListViewAdapter<T> extends TreeListViewAdapter {
 22     //判断checkbox是否是单选的标志
 23     private boolean isSingle = true;
 24 
 25     private static List<Node> nodeList;
 26     
 27     private OnTreeNodeChooseListener onTreeNodeChooseListener;
 28 
 29     /**
 30      * @param context           上下文对象
 31      * @param listView              
 32      * @param datas             
 33      * @param defaultExpandLevel  默认初始化时展开几层
 34      * @param isSingle              checkbox是不是单选的
 35      * @throws IllegalAccessException
 36      */
 37     public CheckBoxTreeListViewAdapter(Context context, ListView listView, List<T> datas, int defaultExpandLevel, boolean isSingle) throws IllegalAccessException {
 38         super(context, listView, datas, defaultExpandLevel);
 39         this.isSingle = isSingle;
 40     }
 41 
 42     
 43     @Override
 44     public View getConvertView(final com.example.keranbin.testdemo.treeHelp.Node node, int position, View view, ViewGroup viewGroup) {
 45         ViewHolder vh=null;
 46         if (view == null) {
 47             vh = new ViewHolder();
 48             view = inflater.inflate(R.layout.lv_tree_item, viewGroup, false);
 49             vh.ivIcon = (ImageView) view.findViewById(R.id.iv_tree_icon);
 50             vh.tvName = (TextView) view.findViewById(R.id.tv_tree_title);
 51             vh.cbChoose = (CheckBox) view.findViewById(R.id.cb_tree_choose);
 52             view.setTag(vh);
 53         } else {
 54             vh = (ViewHolder) view.getTag();
 55         }
 56 
 57 
 58         //如果node的icon为-1,说明是叶子节点,隐藏图标,显示checkbox,否则显示相应的图标,隐藏checkbox
 59         if (node.getIcon() == -1) {
 60             vh.ivIcon.setVisibility(View.INVISIBLE);
 61             vh.cbChoose.setVisibility(View.VISIBLE);
 62             vh.cbChoose.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
 63                 @Override
 64                 public void onCheckedChanged(CompoundButton compoundButton, boolean isCheck) {
 65                     if (isSingle) {  //如果checkbox是单选的
 66                         if (isCheck) { //如果checkbox的状态是选中的,那么除了被选中的那条数据,其他Node节点的checkbox状态都为false
 67                             for (int i = 0; i < mAllNodes.size(); i++) {
 68                                 if (((Node) mAllNodes.get(i)).getId().equals(node.getId())) {
 69                                     ((Node) mAllNodes.get(i)).setChoose(isCheck);
 70                                 } else {
 71                                     ((Node) mAllNodes.get(i)).setChoose(false);
 72                                 }
 73                             }
 74                         } else {//如果checkbox的状态是选中的,所有Node节点checkbox状态都为false
 75                             for (int i = 0; i < mAllNodes.size(); i++) {
 76                                 if (((Node) mAllNodes.get(i)).getId().equals(node.getId())) {
 77                                     ((Node) mAllNodes.get(i)).setChoose(isCheck);
 78                                 }
 79                             }
 80                         }
 81                     } else {   ////如果checkbox是多选的,对应node节点的checkbox状态视用户的操作而定
 82                         for (int i = 0; i < mAllNodes.size(); i++) {
 83                             if (((Node) mAllNodes.get(i)).getId().equals(node.getId()))
 84                                 ((Node) mAllNodes.get(i)).setChoose(isCheck);
 85 
 86                         }
 87                     }
 88                     onTreeNodeChooseListener.OnTreeNodeChoose(getSelectedNodes());//回调所选择的节点数据给用户
 89                     notifyDataSetChanged();
 90                 }
 91             });
 92             vh.cbChoose.setChecked(node.isChoose());
 93         } else {
 94             vh.ivIcon.setVisibility(View.VISIBLE);
 95             vh.ivIcon.setImageResource(node.getIcon());
 96             vh.cbChoose.setVisibility(View.INVISIBLE);
 97         }
 98         vh.tvName.setText(node.getName());
 99         return view;
100     }
101     
102     /**
103      * 返回所选node集合
104      * @return
105      */
106     public List<Node> getSelectedNodes(){
107         nodeList=new ArrayList<>();
108         for(int i=0;i<mAllNodes.size();i++){
109             if(((Node)mAllNodes.get(i)).isChoose()){
110                 nodeList.add((Node) mAllNodes.get(i));
111             }
112         }
113         return nodeList;
114     }
115 
116     public void setOnTreedNodeChooseListener(OnTreeNodeChooseListener onTreeNodeChooseListener) {
117         this.onTreeNodeChooseListener = onTreeNodeChooseListener;
118     }
119     
120     public interface OnTreeNodeChooseListener {
121         void OnTreeNodeChoose(List<Node> nodes);
122     }
123 
124     class ViewHolder {
125         private ImageView ivIcon;
126         private TextView tvName;
127         private CheckBox cbChoose;
128     }
129 }

至此,我们的程序结束了吗,没有,看看下面出现的这种情况

细心的童鞋就会发现原生动物不是具体的动物,居然可以选择???????这是什么情况造成的呢,是我们代码的问题,是的,我们的代码逻辑还不够严谨,造成这种情况的原因是可能管理员还没为原始动物添加具体的动物,而我们代码中,原始动物并没有子节点,是叶子节点,是可以选择的,到底怎么解决?无非就是添加一个字段进行判断是不是具体的动物,具体实现就不啰嗦,大家可以自己研究研究。