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


    转载请标明出处: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 plaincopy
     
    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"));  


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

    那么我们如何确定呢?

    看下Bean:

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  


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

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

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

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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(1, 0, "文件管理系统"));  
    44.         mDatas.add(new FileBean(2, 1, "游戏"));  
    45.         mDatas.add(new FileBean(3, 1, "文档"));  
    46.         mDatas.add(new FileBean(4, 1, "程序"));  
    47.         mDatas.add(new FileBean(5, 2, "war3"));  
    48.         mDatas.add(new FileBean(6, 2, "刀塔传奇"));  
    49.   
    50.         mDatas.add(new FileBean(7, 4, "面向对象"));  
    51.         mDatas.add(new FileBean(8, 4, "非面向对象"));  
    52.   
    53.         mDatas.add(new FileBean(9, 7, "C++"));  
    54.         mDatas.add(new FileBean(10, 7, "JAVA"));  
    55.         mDatas.add(new FileBean(11, 7, "Javascript"));  
    56.         mDatas.add(new FileBean(12, 8, "C"));  
    57.   
    58.     }  
    59.   
    60. }  


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

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

    那么看一眼我们的Adapter

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  


    我们的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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  


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

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

    3、TreeListViewAdapter

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

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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() * 30, 3, 3, 3);  
    136.         return convertView;  
    137.     }  
    138.   
    139.     public abstract View getConvertView(Node node, int position,  
    140.             View convertView, ViewGroup parent);  
    141.   
    142. }  


    首先我们的类继承自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 plaincopy在CODE上查看代码片派生到我的代码片
     
    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.     }  


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

    接下来看:filterVisibleNode

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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的代码很简单,遍历所有的Node,只要是根节点或者父节点是展开状态就添加返回;

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

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  


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

    getRootNodes 这个简单,获得根节点

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

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

    5、注解的类

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

    TreeNodeId

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  

    TreeNodePid

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  

    TreeNodeLabel

    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    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. }  



    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 plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @Target(ElementType.FIELD)  
    2. @Retention(RetentionPolicy.RUNTIME)  
    3. public @interface TreeNodeId  
    4. {  
    5.     Class type() ;  
    6. }  
    [java] view plaincopy在CODE上查看代码片派生到我的代码片
     
    1. @TreeNodeId(type = Integer.class)  
    2.     private int _id;  

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

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

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

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

  • 相关阅读:
    Xftp6 和 Xshell 6 下载与安装使用
    Oracle 11 安装教程(桌面类)
    Oracle 11 安装 提示环境不满足最低要求解决方案
    FICO年终完全手册
    SAP月结操作讲解
    ABAP-FI常用BAPI
    FB01与F-02的区别(转载)
    SAP应用创新-维护控制表、视图统一路径
    FI 业务
    SAP 财务模块 FI-TV 差旅管理
  • 原文地址:https://www.cnblogs.com/dongweiq/p/4261977.html
Copyright © 2020-2023  润新知