Android 打造任意层级树形控件 考验你的数据结构和设计

来源:互联网 发布:中国行政区数据库 编辑:程序博客网 时间:2024/04/29 15:22
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40212367,本文出自:【张鸿洋的博客】

1、概述

大家在项目中或多或少的可能会见到,偶尔有的项目需要在APP上显示个树形控件,比如展示一个机构组织,最上面是boss,然后各种部门,各种小boss,最后各种小罗罗;整体是一个树形结构;遇到这样的情况,大家可能回去百度,因为层次多嘛,可能更容易想到ExpandableListView , 因为这玩意层级比Listview多,但是ExpandableListView实现目前只支持两级,当然也有人改造成多级的;但是从我个人角度去看,首先我不喜欢ExpandableListView ,数据集的组织比较复杂。所以今天带大家使用ListView来打造一个树形展示效果。ListView应该是大家再熟悉不过的控件了,并且数据集也就是个List<T> 。

本篇博客目标实现,只要是符合树形结构的数据可以轻松的通过我们的代码,实现树形效果,有多轻松,文末就知道了~~

好了,既然是要展现树形结构,那么数据上肯定就是树形的一个依赖,也就是说,你的每条记录,至少有个字段指向它的父节点;类似(id , pId, others ….)

2、原理分析

先看看我们的效果图:


我们支持任意层级,包括item的布局依然让用户自己的去控制,我们的demo的Item布局很简单,一个图标+文本~~

原理就是,树形不树形,其实不就是多个缩进么,只要能够判断每个item属于树的第几层(术语貌似叫高度),设置合适的缩进即可。

当然了,原理说起来简单,还得控制每一层间关系,添加展开缩回等,以及有了缩进还要能显示在正确的位置,不过没关系,我会带着大家一步一步实现的。

3、用法

由于整体比较长,我决定首先带大家看一下用法,就是如果学完了这篇博客,我们需要树形控件,我们需要花多少精力去完成~~

现在需求来了:我现在需要展示一个文件管理系统的树形结构:

数据是这样的:

[html] view plain copy
print?
  1. //id , pid , label , 其他属性  
  2.         mDatas.add(new FileBean(1, 0, ”文件管理系统”));  
  3.         mDatas.add(new FileBean(2, 1, ”游戏”));  
  4.         mDatas.add(new FileBean(3, 1, ”文档”));  
  5.         mDatas.add(new FileBean(4, 1, ”程序”));  
  6.         mDatas.add(new FileBean(5, 2, “war3”));  
  7.         mDatas.add(new FileBean(6, 2, ”刀塔传奇”));  
  8.   
  9.         mDatas.add(new FileBean(7, 4, ”面向对象”));  
  10.         mDatas.add(new FileBean(8, 4, ”非面向对象”));  
  11.   
  12.         mDatas.add(new FileBean(9, 7, “C++”));  
  13.         mDatas.add(new FileBean(10, 7, “JAVA”));  
  14.         mDatas.add(new FileBean(11, 7, “Javascript”));  
  15.         mDatas.add(new FileBean(12, 8, “C”));  
//id , pid , label , 其他属性        mDatas.add(new FileBean(1, 0, "文件管理系统"));        mDatas.add(new FileBean(2, 1, "游戏"));        mDatas.add(new FileBean(3, 1, "文档"));        mDatas.add(new FileBean(4, 1, "程序"));        mDatas.add(new FileBean(5, 2, "war3"));        mDatas.add(new FileBean(6, 2, "刀塔传奇"));        mDatas.add(new FileBean(7, 4, "面向对象"));        mDatas.add(new FileBean(8, 4, "非面向对象"));        mDatas.add(new FileBean(9, 7, "C++"));        mDatas.add(new FileBean(10, 7, "JAVA"));        mDatas.add(new FileBean(11, 7, "Javascript"));        mDatas.add(new FileBean(12, 8, "C"));

当然了,bean可以有很多属性,我们提供你动态的设置树节点上的显示、以及不约束id, pid 的命名,你可以起任意丧心病狂的属性名称;

那么我们如何确定呢?

看下Bean:

[java] view plain copy
print?
  1. package com.zhy.bean;  
  2.   
  3. import com.zhy.tree.bean.TreeNodeId;  
  4. import com.zhy.tree.bean.TreeNodeLabel;  
  5. import com.zhy.tree.bean.TreeNodePid;  
  6.   
  7. public class FileBean  
  8. {  
  9.     @TreeNodeId  
  10.     private int _id;  
  11.     @TreeNodePid  
  12.     private int parentId;  
  13.     @TreeNodeLabel  
  14.     private String name;  
  15.     private long length;  
  16.     private String desc;  
  17.   
  18.     public FileBean(int _id, int parentId, String name)  
  19.     {  
  20.         super();  
  21.         this._id = _id;  
  22.         this.parentId = parentId;  
  23.         this.name = name;  
  24.     }  
  25.   
  26. }  
package com.zhy.bean;import com.zhy.tree.bean.TreeNodeId;import com.zhy.tree.bean.TreeNodeLabel;import com.zhy.tree.bean.TreeNodePid;public class FileBean{    @TreeNodeId    private int _id;    @TreeNodePid    private int parentId;    @TreeNodeLabel    private String name;    private long length;    private String desc;    public FileBean(int _id, int parentId, String name)    {        super();        this._id = _id;        this.parentId = parentId;        this.name = name;    }}

现在,不用说,应该也知道我们通过注解来确定的。

下面看我们如何将这数据转化为树

布局文件就一个listview,就补贴了,直接看Activity

[java] view plain copy
print?
  1. package com.zhy.tree_view;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.widget.ListView;  
  9.   
  10. import com.zhy.bean.FileBean;  
  11. import com.zhy.tree.bean.TreeListViewAdapter;  
  12.   
  13. public class MainActivity extends Activity  
  14. {  
  15.     private List<FileBean> mDatas = new ArrayList<FileBean>();  
  16.     private ListView mTree;  
  17.     private TreeListViewAdapter mAdapter;  
  18.   
  19.     @Override  
  20.     protected void onCreate(Bundle savedInstanceState)  
  21.     {  
  22.         super.onCreate(savedInstanceState);  
  23.         setContentView(R.layout.activity_main);  
  24.   
  25.         initDatas();  
  26.         mTree = (ListView) findViewById(R.id.id_tree);  
  27.         try  
  28.         {  
  29.               
  30.             mAdapter = new SimpleTreeAdapter<FileBean>(mTree, this, mDatas, 10);  
  31.             mTree.setAdapter(mAdapter);  
  32.         } catch (IllegalAccessException e)  
  33.         {  
  34.             e.printStackTrace();  
  35.         }  
  36.   
  37.     }  
  38.   
  39.     private void initDatas()  
  40.     {  
  41.   
  42.         // id , pid , label , 其他属性  
  43.         mDatas.add(new FileBean(10“文件管理系统”));  
  44.         mDatas.add(new FileBean(21“游戏”));  
  45.         mDatas.add(new FileBean(31“文档”));  
  46.         mDatas.add(new FileBean(41“程序”));  
  47.         mDatas.add(new FileBean(52“war3”));  
  48.         mDatas.add(new FileBean(62“刀塔传奇”));  
  49.   
  50.         mDatas.add(new FileBean(74“面向对象”));  
  51.         mDatas.add(new FileBean(84“非面向对象”));  
  52.   
  53.         mDatas.add(new FileBean(97“C++”));  
  54.         mDatas.add(new FileBean(107“JAVA”));  
  55.         mDatas.add(new FileBean(117“Javascript”));  
  56.         mDatas.add(new FileBean(128“C”));  
  57.   
  58.     }  
  59.   
  60. }  
package com.zhy.tree_view;import java.util.ArrayList;import java.util.List;import android.app.Activity;import android.os.Bundle;import android.widget.ListView;import com.zhy.bean.FileBean;import com.zhy.tree.bean.TreeListViewAdapter;public class MainActivity extends Activity{    private List<FileBean> mDatas = new ArrayList<FileBean>();    private ListView mTree;    private TreeListViewAdapter mAdapter;    @Override    protected void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        initDatas();        mTree = (ListView) findViewById(R.id.id_tree);        try        {            mAdapter = new SimpleTreeAdapter<FileBean>(mTree, this, mDatas, 10);            mTree.setAdapter(mAdapter);        } catch (IllegalAccessException e)        {            e.printStackTrace();        }    }    private void initDatas()    {        // id , pid , label , 其他属性        mDatas.add(new FileBean(1, 0, "文件管理系统"));        mDatas.add(new FileBean(2, 1, "游戏"));        mDatas.add(new FileBean(3, 1, "文档"));        mDatas.add(new FileBean(4, 1, "程序"));        mDatas.add(new FileBean(5, 2, "war3"));        mDatas.add(new FileBean(6, 2, "刀塔传奇"));        mDatas.add(new FileBean(7, 4, "面向对象"));        mDatas.add(new FileBean(8, 4, "非面向对象"));        mDatas.add(new FileBean(9, 7, "C++"));        mDatas.add(new FileBean(10, 7, "JAVA"));        mDatas.add(new FileBean(11, 7, "Javascript"));        mDatas.add(new FileBean(12, 8, "C"));    }}

Activity里面并没有什么特殊的代码,拿到listview,传入mData,当中初始化了一个Adapter;

看来我们的核心代码都在我们的Adapter里面:

那么看一眼我们的Adapter

[java] view plain copy
print?
  1. package com.zhy.tree_view;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.content.Context;  
  6. import android.view.View;  
  7. import android.view.ViewGroup;  
  8. import android.widget.ImageView;  
  9. import android.widget.ListView;  
  10. import android.widget.TextView;  
  11.   
  12. import com.zhy.tree.bean.Node;  
  13. import com.zhy.tree.bean.TreeListViewAdapter;  
  14.   
  15. public class SimpleTreeAdapter<T> extends TreeListViewAdapter<T>  
  16. {  
  17.   
  18.     public SimpleTreeAdapter(ListView mTree, Context context, List<T> datas,  
  19.             int defaultExpandLevel) throws IllegalArgumentException,  
  20.             IllegalAccessException  
  21.     {  
  22.         super(mTree, context, datas, defaultExpandLevel);  
  23.     }  
  24.   
  25.     @Override  
  26.     public View getConvertView(Node node , int position, View convertView, ViewGroup parent)  
  27.     {  
  28.           
  29.         ViewHolder viewHolder = null;  
  30.         if (convertView == null)  
  31.         {  
  32.             convertView = mInflater.inflate(R.layout.list_item, parent, false);  
  33.             viewHolder = new ViewHolder();  
  34.             viewHolder.icon = (ImageView) convertView  
  35.                     .findViewById(R.id.id_treenode_icon);  
  36.             viewHolder.label = (TextView) convertView  
  37.                     .findViewById(R.id.id_treenode_label);  
  38.             convertView.setTag(viewHolder);  
  39.   
  40.         } else  
  41.         {  
  42.             viewHolder = (ViewHolder) convertView.getTag();  
  43.         }  
  44.   
  45.         if (node.getIcon() == -1)  
  46.         {  
  47.             viewHolder.icon.setVisibility(View.INVISIBLE);  
  48.         } else  
  49.         {  
  50.             viewHolder.icon.setVisibility(View.VISIBLE);  
  51.             viewHolder.icon.setImageResource(node.getIcon());  
  52.         }  
  53.         viewHolder.label.setText(node.getName());  
  54.           
  55.         return convertView;  
  56.     }  
  57.   
  58.     private final class ViewHolder  
  59.     {  
  60.         ImageView icon;  
  61.         TextView label;  
  62.     }  
  63.   
  64. }  
package com.zhy.tree_view;import java.util.List;import android.content.Context;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.ListView;import android.widget.TextView;import com.zhy.tree.bean.Node;import com.zhy.tree.bean.TreeListViewAdapter;public class SimpleTreeAdapter<T> extends TreeListViewAdapter<T>{    public SimpleTreeAdapter(ListView mTree, Context context, List<T> datas,            int defaultExpandLevel) throws IllegalArgumentException,            IllegalAccessException    {        super(mTree, context, datas, defaultExpandLevel);    }    @Override    public View getConvertView(Node node , int position, View convertView, ViewGroup parent)    {        ViewHolder viewHolder = null;        if (convertView == null)        {            convertView = mInflater.inflate(R.layout.list_item, parent, false);            viewHolder = new ViewHolder();            viewHolder.icon = (ImageView) convertView                    .findViewById(R.id.id_treenode_icon);            viewHolder.label = (TextView) convertView                    .findViewById(R.id.id_treenode_label);            convertView.setTag(viewHolder);        } else        {            viewHolder = (ViewHolder) convertView.getTag();        }        if (node.getIcon() == -1)        {            viewHolder.icon.setVisibility(View.INVISIBLE);        } else        {            viewHolder.icon.setVisibility(View.VISIBLE);            viewHolder.icon.setImageResource(node.getIcon());        }        viewHolder.label.setText(node.getName());        return convertView;    }    private final class ViewHolder    {        ImageView icon;        TextView label;    }}

我们的SimpleTreeAdapter继承了我们的TreeListViewAdapter ; 除此之外,代码上只需要复写getConvertView , 且getConvetView其实和我们平时的getView写法一致;

公布出getConvertView 的目的是,让用户自己去决定Item的展示效果。其他的代码,我已经打包成jar了,用的时候导入即可。这样就完成了我们的树形控件。

也就是说用我们的树形控件,只需要将传统继承BaseAdapter改为我们的TreeListViewAdapter ,然后去实现getConvertView 就好了。

那么现在的效果是:


默认就全打开了,因为我们也支持动态设置打开的层级,方面使用者使用。

用起来是不是很随意,加几个注解,ListView的Adapater换个类继承下~~好了,下面开始带大家一起从无到有的实现~

4、实现


1、思路

我们的思路是这样的,我们显示时,需要很多属性,我们需要知道当前节点是否是父节点,当前的层级,他的孩子节点等等;但是用户的数据集是不固定的,最多只能给出类似id,pId 这样的属性。也就是说,用户给的bean并不适合我们用于控制显示,于是我们准备这样做:

1、在用户的Bean中提取出必要的几个元素 id , pId , 以及显示的文本(通过注解+反射);然后组装成我们的真正显示时的Node;即List<Bean> -> List<Node>

2、显示的并非是全部的Node,比如某些节点的父节点是关闭状态,我们需要进行过滤;即List<Node> ->过滤后的List<Node>

3、显示时,比如点击父节点,它的子节点会跟随其后显示,我们内部是个List,也就是说,这个List的顺序也是很关键的;当然排序我们可以放为步骤一;

最后将过滤后的Node进行显示,设置左内边距即可。

说了这么多,首先看一眼我们封装后的Node

2、Node

[java] view plain copy
print?
  1. package com.zhy.tree.bean;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. import org.w3c.dom.NamedNodeMap;  
  7.   
  8. import android.util.Log;  
  9.   
  10. public class Node  
  11. {  
  12.   
  13.     private int id;  
  14.     /** 
  15.      * 根节点pId为0 
  16.      */  
  17.     private int pId = 0;  
  18.   
  19.     private String name;  
  20.   
  21.     /** 
  22.      * 当前的级别 
  23.      */  
  24.     private int level;  
  25.   
  26.     /** 
  27.      * 是否展开 
  28.      */  
  29.     private boolean isExpand = false;  
  30.   
  31.     private int icon;  
  32.   
  33.     /** 
  34.      * 下一级的子Node 
  35.      */  
  36.     private List<Node> children = new ArrayList<Node>();  
  37.   
  38.     /** 
  39.      * 父Node 
  40.      */  
  41.     private Node parent;  
  42.   
  43.     public Node()  
  44.     {  
  45.     }  
  46.   
  47.     public Node(int id, int pId, String name)  
  48.     {  
  49.         super();  
  50.         this.id = id;  
  51.         this.pId = pId;  
  52.         this.name = name;  
  53.     }  
  54.   
  55.     public int getIcon()  
  56.     {  
  57.         return icon;  
  58.     }  
  59.   
  60.     public void setIcon(int icon)  
  61.     {  
  62.         this.icon = icon;  
  63.     }  
  64.   
  65.     public int getId()  
  66.     {  
  67.         return id;  
  68.     }  
  69.   
  70.     public void setId(int id)  
  71.     {  
  72.         this.id = id;  
  73.     }  
  74.   
  75.     public int getpId()  
  76.     {  
  77.         return pId;  
  78.     }  
  79.   
  80.     public void setpId(int pId)  
  81.     {  
  82.         this.pId = pId;  
  83.     }  
  84.   
  85.     public String getName()  
  86.     {  
  87.         return name;  
  88.     }  
  89.   
  90.     public void setName(String name)  
  91.     {  
  92.         this.name = name;  
  93.     }  
  94.   
  95.     public void setLevel(int level)  
  96.     {  
  97.         this.level = level;  
  98.     }  
  99.   
  100.     public boolean isExpand()  
  101.     {  
  102.         return isExpand;  
  103.     }  
  104.   
  105.     public List<Node> getChildren()  
  106.     {  
  107.         return children;  
  108.     }  
  109.   
  110.     public void setChildren(List<Node> children)  
  111.     {  
  112.         this.children = children;  
  113.     }  
  114.   
  115.     public Node getParent()  
  116.     {  
  117.         return parent;  
  118.     }  
  119.   
  120.     public void setParent(Node parent)  
  121.     {  
  122.         this.parent = parent;  
  123.     }  
  124.   
  125.     /** 
  126.      * 是否为跟节点 
  127.      *  
  128.      * @return 
  129.      */  
  130.     public boolean isRoot()  
  131.     {  
  132.         return parent == null;  
  133.     }  
  134.   
  135.     /** 
  136.      * 判断父节点是否展开 
  137.      *  
  138.      * @return 
  139.      */  
  140.     public boolean isParentExpand()  
  141.     {  
  142.         if (parent == null)  
  143.             return false;  
  144.         return parent.isExpand();  
  145.     }  
  146.   
  147.     /** 
  148.      * 是否是叶子界点 
  149.      *  
  150.      * @return 
  151.      */  
  152.     public boolean isLeaf()  
  153.     {  
  154.         return children.size() == 0;  
  155.     }  
  156.   
  157.     /** 
  158.      * 获取level 
  159.      */  
  160.     public int getLevel()  
  161.     {  
  162.         return parent == null ? 0 : parent.getLevel() + 1;  
  163.     }  
  164.   
  165.     /** 
  166.      * 设置展开 
  167.      *  
  168.      * @param isExpand 
  169.      */  
  170.     public void setExpand(boolean isExpand)  
  171.     {  
  172.         this.isExpand = isExpand;  
  173.         if (!isExpand)  
  174.         {  
  175.   
  176.             for (Node node : children)  
  177.             {  
  178.                 node.setExpand(isExpand);  
  179.             }  
  180.         }  
  181.     }  
  182.   
  183. }  
package com.zhy.tree.bean;import java.util.ArrayList;import java.util.List;import org.w3c.dom.NamedNodeMap;import android.util.Log;public class Node{    private int id;    /**     * 根节点pId为0     */    private int pId = 0;    private String name;    /**     * 当前的级别     */    private int level;    /**     * 是否展开     */    private boolean isExpand = false;    private int icon;    /**     * 下一级的子Node     */    private List<Node> children = new ArrayList<Node>();    /**     * 父Node     */    private Node parent;    public Node()    {    }    public Node(int id, int pId, String name)    {        super();        this.id = id;        this.pId = pId;        this.name = name;    }    public int getIcon()    {        return icon;    }    public void setIcon(int icon)    {        this.icon = icon;    }    public int getId()    {        return id;    }    public void setId(int id)    {        this.id = id;    }    public int getpId()    {        return pId;    }    public void setpId(int pId)    {        this.pId = pId;    }    public String getName()    {        return name;    }    public void setName(String name)    {        this.name = name;    }    public void setLevel(int level)    {        this.level = level;    }    public boolean isExpand()    {        return isExpand;    }    public List<Node> getChildren()    {        return children;    }    public void setChildren(List<Node> children)    {        this.children = children;    }    public Node getParent()    {        return parent;    }    public void setParent(Node parent)    {        this.parent = parent;    }    /**     * 是否为跟节点     *      * @return     */    public boolean isRoot()    {        return parent == null;    }    /**     * 判断父节点是否展开     *      * @return     */    public boolean isParentExpand()    {        if (parent == null)            return false;        return parent.isExpand();    }    /**     * 是否是叶子界点     *      * @return     */    public boolean isLeaf()    {        return children.size() == 0;    }    /**     * 获取level     */    public int getLevel()    {        return parent == null ? 0 : parent.getLevel() + 1;    }    /**     * 设置展开     *      * @param isExpand     */    public void setExpand(boolean isExpand)    {        this.isExpand = isExpand;        if (!isExpand)        {            for (Node node : children)            {                node.setExpand(isExpand);            }        }    }}

包含了树节点一些常见的属性,一些常见的方法;对于getLevel,setExpand这些方法,大家可以好好看看~

有了Node,刚才的用法中,出现的就是我们Adapter所继承的超类:TreeListViewAdapter;核心代码都在里面,我们准备去一探究竟:

3、TreeListViewAdapter

代码不是很长,直接完整的贴出:

[java] view plain copy
print?
  1. package com.zhy.tree.bean;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.content.Context;  
  6. import android.view.LayoutInflater;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9. import android.widget.AdapterView;  
  10. import android.widget.AdapterView.OnItemClickListener;  
  11. import android.widget.BaseAdapter;  
  12. import android.widget.ListView;  
  13.   
  14. public abstract class TreeListViewAdapter<T> extends BaseAdapter  
  15. {  
  16.   
  17.     protected Context mContext;  
  18.     /** 
  19.      * 存储所有可见的Node 
  20.      */  
  21.     protected List<Node> mNodes;  
  22.     protected LayoutInflater mInflater;  
  23.     /** 
  24.      * 存储所有的Node 
  25.      */  
  26.     protected List<Node> mAllNodes;  
  27.   
  28.     /** 
  29.      * 点击的回调接口 
  30.      */  
  31.     private OnTreeNodeClickListener onTreeNodeClickListener;  
  32.   
  33.     public interface OnTreeNodeClickListener  
  34.     {  
  35.         void onClick(Node node, int position);  
  36.     }  
  37.   
  38.     public void setOnTreeNodeClickListener(  
  39.             OnTreeNodeClickListener onTreeNodeClickListener)  
  40.     {  
  41.         this.onTreeNodeClickListener = onTreeNodeClickListener;  
  42.     }  
  43.   
  44.     /** 
  45.      *  
  46.      * @param mTree 
  47.      * @param context 
  48.      * @param datas 
  49.      * @param defaultExpandLevel 
  50.      *            默认展开几级树 
  51.      * @throws IllegalArgumentException 
  52.      * @throws IllegalAccessException 
  53.      */  
  54.     public TreeListViewAdapter(ListView mTree, Context context, List<T> datas,  
  55.             int defaultExpandLevel) throws IllegalArgumentException,  
  56.             IllegalAccessException  
  57.     {  
  58.         mContext = context;  
  59.         /** 
  60.          * 对所有的Node进行排序 
  61.          */  
  62.         mAllNodes = TreeHelper.getSortedNodes(datas, defaultExpandLevel);  
  63.         /** 
  64.          * 过滤出可见的Node 
  65.          */  
  66.         mNodes = TreeHelper.filterVisibleNode(mAllNodes);  
  67.         mInflater = LayoutInflater.from(context);  
  68.   
  69.         /** 
  70.          * 设置节点点击时,可以展开以及关闭;并且将ItemClick事件继续往外公布 
  71.          */  
  72.         mTree.setOnItemClickListener(new OnItemClickListener()  
  73.         {  
  74.             @Override  
  75.             public void onItemClick(AdapterView<?> parent, View view,  
  76.                     int position, long id)  
  77.             {  
  78.                 expandOrCollapse(position);  
  79.   
  80.                 if (onTreeNodeClickListener != null)  
  81.                 {  
  82.                     onTreeNodeClickListener.onClick(mNodes.get(position),  
  83.                             position);  
  84.                 }  
  85.             }  
  86.   
  87.         });  
  88.   
  89.     }  
  90.   
  91.     /** 
  92.      * 相应ListView的点击事件 展开或关闭某节点 
  93.      *  
  94.      * @param position 
  95.      */  
  96.     public void expandOrCollapse(int position)  
  97.     {  
  98.         Node n = mNodes.get(position);  
  99.   
  100.         if (n != null)// 排除传入参数错误异常  
  101.         {  
  102.             if (!n.isLeaf())  
  103.             {  
  104.                 n.setExpand(!n.isExpand());  
  105.                 mNodes = TreeHelper.filterVisibleNode(mAllNodes);  
  106.                 notifyDataSetChanged();// 刷新视图  
  107.             }  
  108.         }  
  109.     }  
  110.   
  111.     @Override  
  112.     public int getCount()  
  113.     {  
  114.         return mNodes.size();  
  115.     }  
  116.   
  117.     @Override  
  118.     public Object getItem(int position)  
  119.     {  
  120.         return mNodes.get(position);  
  121.     }  
  122.   
  123.     @Override  
  124.     public long getItemId(int position)  
  125.     {  
  126.         return position;  
  127.     }  
  128.   
  129.     @Override  
  130.     public View getView(int position, View convertView, ViewGroup parent)  
  131.     {  
  132.         Node node = mNodes.get(position);  
  133.         convertView = getConvertView(node, position, convertView, parent);  
  134.         // 设置内边距  
  135.         convertView.setPadding(node.getLevel() * 30333);  
  136.         return convertView;  
  137.     }  
  138.   
  139.     public abstract View getConvertView(Node node, int position,  
  140.             View convertView, ViewGroup parent);  
  141.   
  142. }  
package com.zhy.tree.bean;import java.util.List;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.BaseAdapter;import android.widget.ListView;public abstract class TreeListViewAdapter<T> extends BaseAdapter{    protected Context mContext;    /**     * 存储所有可见的Node     */    protected List<Node> mNodes;    protected LayoutInflater mInflater;    /**     * 存储所有的Node     */    protected List<Node> mAllNodes;    /**     * 点击的回调接口     */    private OnTreeNodeClickListener onTreeNodeClickListener;    public interface OnTreeNodeClickListener    {        void onClick(Node node, int position);    }    public void setOnTreeNodeClickListener(            OnTreeNodeClickListener onTreeNodeClickListener)    {        this.onTreeNodeClickListener = onTreeNodeClickListener;    }    /**     *      * @param mTree     * @param context     * @param datas     * @param defaultExpandLevel     *            默认展开几级树     * @throws IllegalArgumentException     * @throws IllegalAccessException     */    public TreeListViewAdapter(ListView mTree, Context context, List<T> datas,            int defaultExpandLevel) throws IllegalArgumentException,            IllegalAccessException    {        mContext = context;        /**         * 对所有的Node进行排序         */        mAllNodes = TreeHelper.getSortedNodes(datas, defaultExpandLevel);        /**         * 过滤出可见的Node         */        mNodes = TreeHelper.filterVisibleNode(mAllNodes);        mInflater = LayoutInflater.from(context);        /**         * 设置节点点击时,可以展开以及关闭;并且将ItemClick事件继续往外公布         */        mTree.setOnItemClickListener(new OnItemClickListener()        {            @Override            public void onItemClick(AdapterView<?> parent, View view,                    int position, long id)            {                expandOrCollapse(position);                if (onTreeNodeClickListener != null)                {                    onTreeNodeClickListener.onClick(mNodes.get(position),                            position);                }            }        });    }    /**     * 相应ListView的点击事件 展开或关闭某节点     *      * @param position     */    public void expandOrCollapse(int position)    {        Node n = mNodes.get(position);        if (n != null)// 排除传入参数错误异常        {            if (!n.isLeaf())            {                n.setExpand(!n.isExpand());                mNodes = TreeHelper.filterVisibleNode(mAllNodes);                notifyDataSetChanged();// 刷新视图            }        }    }    @Override    public int getCount()    {        return mNodes.size();    }    @Override    public Object getItem(int position)    {        return mNodes.get(position);    }    @Override    public long getItemId(int position)    {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent)    {        Node node = mNodes.get(position);        convertView = getConvertView(node, position, convertView, parent);        // 设置内边距        convertView.setPadding(node.getLevel() * 30, 3, 3, 3);        return convertView;    }    public abstract View getConvertView(Node node, int position,            View convertView, ViewGroup parent);}

首先我们的类继承自BaseAdapter,然后我们对应的数据集是,过滤出的可见的Node;

我们的构造方法默认接收4个参数:listview,context,mdatas,以及默认展开的级数:0只显示根节点;

可以在构造方法中看到:对用户传入的数据集做了排序,和过滤的操作;一会再看这些方法,这些方法我们使用了一个TreeHelper进行了封装。

注:如果你觉得你的Item布局十分复杂,且布局会展示Bean的其他数据,那么为了方便,你可以让Node中包含一个泛型T , 每个Node携带与之对于的Bean的所有数据;

可以看到我们还直接为Item设置了点击事件,因为我们树,默认就有点击父节点展开与关闭;但是为了让用户依然可用点击监听,我们自定义了一个点击的回调供用户使用;

当用户点击时,默认调用expandOrCollapse方法,将当然节点重置展开标志,然后重新过滤出可见的Node,最后notifyDataSetChanged即可;

其他的方法都是BaseAdapter默认的一些方法了。

下面我们看下TreeHelper中的一些方法:

4、TreeHelper

首先看TreeListViewAdapter构造方法中用到的两个方法:

[java] view plain copy
print?
  1. /** 
  2.      * 传入我们的普通bean,转化为我们排序后的Node 
  3.      * @param datas 
  4.      * @param defaultExpandLevel 
  5.      * @return 
  6.      * @throws IllegalArgumentException 
  7.      * @throws IllegalAccessException 
  8.      */  
  9.     public static <T> List<Node> getSortedNodes(List<T> datas,  
  10.             int defaultExpandLevel) throws IllegalArgumentException,  
  11.             IllegalAccessException  
  12.   
  13.     {  
  14.         List<Node> result = new ArrayList<Node>();  
  15.         //将用户数据转化为List<Node>以及设置Node间关系  
  16.         List<Node> nodes = convetData2Node(datas);  
  17.         //拿到根节点  
  18.         List<Node> rootNodes = getRootNodes(nodes);  
  19.         //排序  
  20.         for (Node node : rootNodes)  
  21.         {  
  22.             addNode(result, node, defaultExpandLevel, 1);  
  23.         }  
  24.         return result;  
  25.     }  
/**     * 传入我们的普通bean,转化为我们排序后的Node     * @param datas     * @param defaultExpandLevel     * @return     * @throws IllegalArgumentException     * @throws IllegalAccessException     */    public static <T> List<Node> getSortedNodes(List<T> datas,            int defaultExpandLevel) throws IllegalArgumentException,            IllegalAccessException    {        List<Node> result = new ArrayList<Node>();        //将用户数据转化为List<Node>以及设置Node间关系        List<Node> nodes = convetData2Node(datas);        //拿到根节点        List<Node> rootNodes = getRootNodes(nodes);        //排序        for (Node node : rootNodes)        {            addNode(result, node, defaultExpandLevel, 1);        }        return result;    }

拿到用户传入的数据,转化为List<Node>以及设置Node间关系,然后根节点,从根往下遍历进行排序;

接下来看:filterVisibleNode

[java] view plain copy
print?
  1. /** 
  2.      * 过滤出所有可见的Node 
  3.      *  
  4.      * @param nodes 
  5.      * @return 
  6.      */  
  7.     public static List<Node> filterVisibleNode(List<Node> nodes)  
  8.     {  
  9.         List<Node> result = new ArrayList<Node>();  
  10.   
  11.         for (Node node : nodes)  
  12.         {  
  13.             // 如果为跟节点,或者上层目录为展开状态  
  14.             if (node.isRoot() || node.isParentExpand())  
  15.             {  
  16.                 setNodeIcon(node);  
  17.                 result.add(node);  
  18.             }  
  19.         }  
  20.         return result;  
  21.     }  
/**     * 过滤出所有可见的Node     *      * @param nodes     * @return     */    public static List<Node> filterVisibleNode(List<Node> nodes)    {        List<Node> result = new ArrayList<Node>();        for (Node node : nodes)        {            // 如果为跟节点,或者上层目录为展开状态            if (node.isRoot() || node.isParentExpand())            {                setNodeIcon(node);                result.add(node);            }        }        return result;    }

过滤Node的代码很简单,遍历所有的Node,只要是根节点或者父节点是展开状态就添加返回;

最后看看这两个方法用到的别的一些私有方法:

[java] view plain copy
print?
  1. /** 
  2.  * 将我们的数据转化为树的节点 
  3.  *  
  4.  * @param datas 
  5.  * @return 
  6.  * @throws NoSuchFieldException 
  7.  * @throws IllegalAccessException 
  8.  * @throws IllegalArgumentException 
  9.  */  
  10. private static <T> List<Node> convetData2Node(List<T> datas)  
  11.         throws IllegalArgumentException, IllegalAccessException  
  12.   
  13. {  
  14.     List<Node> nodes = new ArrayList<Node>();  
  15.     Node node = null;  
  16.   
  17.     for (T t : datas)  
  18.     {  
  19.         int id = -1;  
  20.         int pId = -1;  
  21.         String label = null;  
  22.         Class<? extends Object> clazz = t.getClass();  
  23.         Field[] declaredFields = clazz.getDeclaredFields();  
  24.         for (Field f : declaredFields)  
  25.         {  
  26.             if (f.getAnnotation(TreeNodeId.class) != null)  
  27.             {  
  28.                 f.setAccessible(true);  
  29.                 id = f.getInt(t);  
  30.             }  
  31.             if (f.getAnnotation(TreeNodePid.class) != null)  
  32.             {  
  33.                 f.setAccessible(true);  
  34.                 pId = f.getInt(t);  
  35.             }  
  36.             if (f.getAnnotation(TreeNodeLabel.class) != null)  
  37.             {  
  38.                 f.setAccessible(true);  
  39.                 label = (String) f.get(t);  
  40.             }  
  41.             if (id != -1 && pId != -1 && label != null)  
  42.             {  
  43.                 break;  
  44.             }  
  45.         }  
  46.         node = new Node(id, pId, label);  
  47.         nodes.add(node);  
  48.     }  
  49.   
  50.     /** 
  51.      * 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系 
  52.      */  
  53.     for (int i = 0; i < nodes.size(); i++)  
  54.     {  
  55.         Node n = nodes.get(i);  
  56.         for (int j = i + 1; j < nodes.size(); j++)  
  57.         {  
  58.             Node m = nodes.get(j);  
  59.             if (m.getpId() == n.getId())  
  60.             {  
  61.                 n.getChildren().add(m);  
  62.                 m.setParent(n);  
  63.             } else if (m.getId() == n.getpId())  
  64.             {  
  65.                 m.getChildren().add(n);  
  66.                 n.setParent(m);  
  67.             }  
  68.         }  
  69.     }  
  70.   
  71.     // 设置图片  
  72.     for (Node n : nodes)  
  73.     {  
  74.         setNodeIcon(n);  
  75.     }  
  76.     return nodes;  
  77. }  
  78.   
  79. private static List<Node> getRootNodes(List<Node> nodes)  
  80. {  
  81.     List<Node> root = new ArrayList<Node>();  
  82.     for (Node node : nodes)  
  83.     {  
  84.         if (node.isRoot())  
  85.             root.add(node);  
  86.     }  
  87.     return root;  
  88. }  
  89.   
  90. /** 
  91.  * 把一个节点上的所有的内容都挂上去 
  92.  */  
  93. private static void addNode(List<Node> nodes, Node node,  
  94.         int defaultExpandLeval, int currentLevel)  
  95. {  
  96.   
  97.     nodes.add(node);  
  98.     if (defaultExpandLeval >= currentLevel)  
  99.     {  
  100.         node.setExpand(true);  
  101.     }  
  102.   
  103.     if (node.isLeaf())  
  104.         return;  
  105.     for (int i = 0; i < node.getChildren().size(); i++)  
  106.     {  
  107.         addNode(nodes, node.getChildren().get(i), defaultExpandLeval,  
  108.                 currentLevel + 1);  
  109.     }  
  110. }  
  111.   
  112. /** 
  113.  * 设置节点的图标 
  114.  *  
  115.  * @param node 
  116.  */  
  117. private static void setNodeIcon(Node node)  
  118. {  
  119.     if (node.getChildren().size() > 0 && node.isExpand())  
  120.     {  
  121.         node.setIcon(R.drawable.tree_ex);  
  122.     } else if (node.getChildren().size() > 0 && !node.isExpand())  
  123.     {  
  124.         node.setIcon(R.drawable.tree_ec);  
  125.     } else  
  126.         node.setIcon(-1);  
  127.   
  128. }  
  /**     * 将我们的数据转化为树的节点     *      * @param datas     * @return     * @throws NoSuchFieldException     * @throws IllegalAccessException     * @throws IllegalArgumentException     */    private static <T> List<Node> convetData2Node(List<T> datas)            throws IllegalArgumentException, IllegalAccessException    {        List<Node> nodes = new ArrayList<Node>();        Node node = null;        for (T t : datas)        {            int id = -1;            int pId = -1;            String label = null;            Class<? extends Object> clazz = t.getClass();            Field[] declaredFields = clazz.getDeclaredFields();            for (Field f : declaredFields)            {                if (f.getAnnotation(TreeNodeId.class) != null)                {                    f.setAccessible(true);                    id = f.getInt(t);                }                if (f.getAnnotation(TreeNodePid.class) != null)                {                    f.setAccessible(true);                    pId = f.getInt(t);                }                if (f.getAnnotation(TreeNodeLabel.class) != null)                {                    f.setAccessible(true);                    label = (String) f.get(t);                }                if (id != -1 && pId != -1 && label != null)                {                    break;                }            }            node = new Node(id, pId, label);            nodes.add(node);        }        /**         * 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系         */        for (int i = 0; i < nodes.size(); i++)        {            Node n = nodes.get(i);            for (int j = i + 1; j < nodes.size(); j++)            {                Node m = nodes.get(j);                if (m.getpId() == n.getId())                {                    n.getChildren().add(m);                    m.setParent(n);                } else if (m.getId() == n.getpId())                {                    m.getChildren().add(n);                    n.setParent(m);                }            }        }        // 设置图片        for (Node n : nodes)        {            setNodeIcon(n);        }        return nodes;    }    private static List<Node> getRootNodes(List<Node> nodes)    {        List<Node> root = new ArrayList<Node>();        for (Node node : nodes)        {            if (node.isRoot())                root.add(node);        }        return root;    }    /**     * 把一个节点上的所有的内容都挂上去     */    private static void addNode(List<Node> nodes, Node node,            int defaultExpandLeval, int currentLevel)    {        nodes.add(node);        if (defaultExpandLeval >= currentLevel)        {            node.setExpand(true);        }        if (node.isLeaf())            return;        for (int i = 0; i < node.getChildren().size(); i++)        {            addNode(nodes, node.getChildren().get(i), defaultExpandLeval,                    currentLevel + 1);        }    }    /**     * 设置节点的图标     *      * @param node     */    private static void setNodeIcon(Node node)    {        if (node.getChildren().size() > 0 && node.isExpand())        {            node.setIcon(R.drawable.tree_ex);        } else if (node.getChildren().size() > 0 && !node.isExpand())        {            node.setIcon(R.drawable.tree_ec);        } else            node.setIcon(-1);    }

convetData2Node即遍历用户传入的Bean,转化为Node,其中Id,pId,label通过注解加反射获取;然后设置Node间关系;

getRootNodes 这个简单,获得根节点

addNode :通过递归的方式,把一个节点上的所有的子节点等都按顺序放入;

setNodeIcon :设置图标,这里标明,我们的jar还依赖两个小图标,即两个三角形;如果你觉得树不需要这样的图标,可以去掉;


5、注解的类

最后就是我们的3个注解类了,没撒用,就启到一个标识的作用

TreeNodeId

[java] view plain copy
print?
  1. package com.zhy.tree.bean;  
  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. @Target(ElementType.FIELD)  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. public @interface TreeNodeId  
  11. {  
  12. }  
package com.zhy.tree.bean;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface TreeNodeId{}

TreeNodePid

[java] view plain copy
print?
  1. package com.zhy.tree.bean;  
  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. @Target(ElementType.FIELD)  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. public @interface TreeNodePid  
  11. {  
  12.   
  13. }  
package com.zhy.tree.bean;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface TreeNodePid{}
TreeNodeLabel

[java] view plain copy
print?
  1. package com.zhy.tree.bean;  
  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. @Target(ElementType.FIELD)  
  9. @Retention(RetentionPolicy.RUNTIME)  
  10. public @interface TreeNodeLabel  
  11. {  
  12.   
  13. }  
package com.zhy.tree.bean;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface TreeNodeLabel{}


5、最后的展望

基于上面的例子,我们还有很多地方可以改善,下面我提一下:

1、Item的布局依赖很多Bean的属性,在Node中使用泛型存储与之对应的Bean,这样在getConvertView中就可以通过Node获取到原本的Bean数据了;

2、关于自定义或者不要三角图标;可以让TreeListViewAdapter公布出设置图标的方法,Node全部使用TreeListViewAdapter中设置的图标;关于不显示,直接getConverView里面不管就行了;

3、我们通过注解得到的Id ,pId , label ; 如果嫌慢,可以通过回调的方式进行获取;我们遍历的时候,去通过Adapter中定义类似:abstract int getId(T t) ;将t作为参数,让用户返回id ,类似还有 pid ,label ;这样循环的代码需要从ViewHelper提取到Adapter构造方法中;

4、关于设置包含复选框,选择了多个Node,不要保存position完事,去保存Node中的Id即原Bean的主键;然后在getConvertView中对Id进行对比,防止错乱;

5、关于注解,目前注解只启到了标识的左右;其实还能干很多事,比如默认我们任务用户的id , pid是整形,但是有可能是别的类型;我们可以通过在注解中设置方法来确定,例如:

[java] view plain copy
print?
  1. @Target(ElementType.FIELD)  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. public @interface TreeNodeId  
  4. {  
  5.     Class type() ;  
  6. }  
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface TreeNodeId{    Class type() ;}

[java] view plain copy
print?
  1. @TreeNodeId(type = Integer.class)  
  2.     private int _id;  
@TreeNodeId(type = Integer.class)    private int _id;

当然了,如果你的需求没有上述修改的需要,就不需要折腾了~~

到此,我们整个博客就结束了~~设计中如果存在不足,大家可以自己去改善;希望大家通过本博客学习到的不仅是一个例子如何实现,更多的是如何设计;当然鄙人能力有限,请大家自行去其糟粕;



源码点击下载(已经打成jar)

源码点击下载(未打成jar版)



博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

1、高仿微信5.2.1主界面及消息提醒

2、高仿QQ5.0侧滑











阅读全文
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 怀孕四个月胎盘低怎么办 怀孕五个月胎盘低怎么办 怀孕3个月胎盘低怎么办 怀孕三个月了胎盘低怎么办 新房交房开发商拿不出证件怎么办? 新干式变压器未送电进水怎么办 三相380转单相220怎么办 外国人一直找你出去怎么办 美团众包跑腿单物品太重怎么办 美团退款成功后 物品怎么办 win10电脑没有网络图标怎么办 电脑开机桌面什么都没有怎么办 xp桌面什么都没有了怎么办 手机下滑通知栏不见了怎么办 跑800米赶上月经怎么办 强迫症吃药没效怎么办 复读生档案丢了怎么办 大学退学重新高考学籍怎么办 玻尿酸鼻子宽了怎么办 被昆山市圆通快递公司骗怎么办 借壳上市后壳公司怎么办 盐城国有民办学校倒闭教师怎么办 联想复印机显示更换墨粉盒怎么办 襄垣县共公自行车卡怎么办 转南京江宁户口怎么办儿童医保 剑与家园鬼服怎么办 花生苗出现虫洞怎么办 被蝎子草扎到手怎么办 被蝎子草蛰了怎么办 螫麻子草扎了怎么办 苹果app没删除干净怎么办 字写的太潦草看不懂怎么办 肝内胆管结石疼怎么办 肝内胆管有结石怎么办 肝里胆管有结石怎么办 被信任的人算计你怎么办 卡罗拉1.8油耗高怎么办 请问09年途锐柴油版怎么办 油电混合没电了怎么办 前向运动精子3%怎么办 精子活力正常精子活率低怎么办?