android 创建多层树型结构
2015-12-29 15:33
429 查看
在项目开发过程中、遇到一个可能大家都经常会遇到的问题:在一个页面中要显示一个树型结构图,例如组织结构、文件系统等等。一开始是第一层、下面是第二层、第三层....
例如:
这样的结构图、想必肯定很常见,最近学习了一种比较好的方法,记录以及分享。
然后我们就开始填充数据,创建树形结构:
这里的代码不多、首先制造我们需要的树型结构数据,初始化adapter 任何给我们的listview。然后我们看下我们的adapter;
这里跟我们普通继承BaseApter没什么区别,就是给每个Item 赋值,关键的地方来了,TreeListAdapter是什么?,我们来看看:
Node包括树节点的常用属性,包括节点的关闭是否、级别已经子节点、都是用来控制显示的。既然我们数据表bean有了、那怎么跟我们用户的数据集关联起来呢,这里就要使用到一开始我们所说的反射机制了。在TreeListAdapter中有一个TreeHelper、就是用来转化处理我们所需要数据集的。那我们来看下TreeHelper:
方法我们一个个来看:
1、convetData2Node:遍历传进来的bean,通过注解加反射获取id、pid、label转化为Node,然后设置各个Node之间的关系
2、getRootNodes:通过遍历获取根节点
3、addNode:通过递归的方式、把一个节点上的所有的子节点都按顺序放入
4、setNodeIcon:这个比较简单,就是设置每个Item前面的小图标
5、getSortedNodes:这个上面这些方法,把我们传入的数据集转换我们需要的Node数据集
6、filterVisibleNode:通过遍历获取所有可见的Node,只要是根节点获取父节点展开状态就添加返回
上面就是这个Helper的方法,有效的把用户数据转化为我们需要的数据集。
最后看下我们的注解类,作用就是起到标识的作用,用于反射。
TreeNodeId:
TreeNodeLabel:
大功告成,以上是我对这个功能的总结,只是用于记录和分享,有什么宝贵的意见欢迎大家指正,下面有Demo源码。
源码下载
例如:
这样的结构图、想必肯定很常见,最近学习了一种比较好的方法,记录以及分享。
功能分析
一看这个结构,其实就是一个listview,只是在特定情况下显示部分层次的内容以及不显示其它层次内容,既然我们要实现树形结构、那么父层次跟子层次肯定是有联系的,也就是说每一条记录都应该有自己的id,而且必须有一个字段指向父类的Id。这样我们我们就可以理清它们之间的关系了。功能实现
首先我们要准备一个基础数据bean:package com.test.tree; import com.test.tree.utils.TreeNodeId; import com.test.tree.utils.TreeNodeLabel; import com.test.tree.utils.TreeNodePid; /** * Created by fuweiwei on 2015/12/24. */ public class BaseTreeBean { @TreeNodeId private String id; //自己id @TreeNodePid private String parentId; //上一层id @TreeNodeLabel private String name; //名字 public BaseTreeBean(){ } public BaseTreeBean(String id, String parentId, String name){ this.id = id; this.parentId = parentId; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getParentId() { return parentId; } public void setParentId(String parentId) { this.parentId = parentId; } public String getName() { return name; } public void setName(String name) { this.name = name; } }这就是我们每一条记录的bean,这里我们使用了注解,后面方便我们通过发射机制来获取值。
然后我们就开始填充数据,创建树形结构:
package com.test.tree; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.widget.ListView; import android.widget.Toast; import com.test.tree.utils.Node; import com.test.tree.utils.TreeListAdapter; import java.util.ArrayList; import java.util.List; /** * Created by fuweiwei on 2015/12/24. */ public class MainActivity extends FragmentActivity { private List<BaseTreeBean> mDatas = new ArrayList<BaseTreeBean>(); private ListView mLvTree; private SimpleTreeAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mLvTree = (ListView) findViewById(R.id.listview); initData(); } public void initData(){ //增加第一层数据 mDatas.add(new BaseTreeBean("1","0","文件夹")); //增加第二层数据 mDatas.add(new BaseTreeBean("2","1","学习")); mDatas.add(new BaseTreeBean("3","1","游戏")); mDatas.add(new BaseTreeBean("4","1","娱乐")); //增加第三层数据 mDatas.add(new BaseTreeBean("5","2","数学资料")); mDatas.add(new BaseTreeBean("6","2","语文资料")); mDatas.add(new BaseTreeBean("7","2","英语资料")); mDatas.add(new BaseTreeBean("8","3","竞技游戏")); mDatas.add(new BaseTreeBean("9","3","休闲游戏")); mDatas.add(new BaseTreeBean("10","4","电影")); mDatas.add(new BaseTreeBean("11","4","音乐")); //增加第四层数据 mDatas.add(new BaseTreeBean("12","5","小学数学")); mDatas.add(new BaseTreeBean("13","5","初中数学")); mDatas.add(new BaseTreeBean("14","5","高中数学")); mDatas.add(new BaseTreeBean("15","8","英雄联盟")); mDatas.add(new BaseTreeBean("16","8","穿越火线")); mDatas.add(new BaseTreeBean("17","10","大话西游")); mDatas.add(new BaseTreeBean("18","10","功夫足球")); //可以一直添加数据层 try { mAdapter = new SimpleTreeAdapter<BaseTreeBean>(mLvTree, MainActivity.this, mDatas, 0); mAdapter.setOnTreeNodeClickListener(new TreeListAdapter.OnTreeNodeClickListener() { @Override public void onClick(Node node, int position) { if(node.isLeaf()&&node.getLevel()==3){ Toast.makeText(MainActivity.this,node.getName(),Toast.LENGTH_SHORT).show(); } } }); } catch (IllegalAccessException e) { e.printStackTrace(); } mLvTree.setAdapter(mAdapter); } }
这里的代码不多、首先制造我们需要的树型结构数据,初始化adapter 任何给我们的listview。然后我们看下我们的adapter;
package com.test.tree; 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.test.tree.utils.Node; import com.test.tree.utils.TreeListAdapter; import java.util.List; public class SimpleTreeAdapter<T> extends TreeListAdapter<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.adapter_tree_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); viewHolder.next = (ImageView) convertView .findViewById(R.id.id_treenode_next); } else { viewHolder = (ViewHolder) convertView.getTag(); } if (node.getIcon() == -1) { viewHolder.icon.setVisibility(View.INVISIBLE); if(node.getLevel()==3){ viewHolder.next.setVisibility(View.VISIBLE); }else{ viewHolder.next.setVisibility(View.INVISIBLE); } } else { viewHolder.next.setVisibility(View.INVISIBLE); viewHolder.icon.setVisibility(View.VISIBLE); viewHolder.icon.setImageResource(node.getIcon()); } viewHolder.label.setText(node.getName()); return convertView; } private final class ViewHolder { ImageView icon; ImageView next; TextView label; } }
这里跟我们普通继承BaseApter没什么区别,就是给每个Item 赋值,关键的地方来了,TreeListAdapter是什么?,我们来看看:
package com.test.tree.utils; 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; import java.util.List; /** * Created by fuweiwei on 2015/12/24. */ public abstract class TreeListAdapter<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 listview * @param context 上下文对象 * @param datas 泛型数据 * @param defaultExpandLevel 默认展开几级树 * @throws IllegalArgumentException * @throws IllegalAccessException */ public TreeListAdapter(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); }这里的adapter继承了BaseAdapter ,大家可能注意到了这里使用的数据集并不是我们的BaseTreeBean,那是为什么呢,因为用户的数据集是不固定的,只能提供id,pId这样的属性,也就是说,用户给的bean并不适合我们用来控制显示,所有使用了Node,那我们看看Node是什么:
package com.test.tree.utils; import java.util.ArrayList; import java.util.List; /** * Created by fuweiwei on 2015/12/24. */ public class Node { private String id; /** * 根节点pId为0 */ private String 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(String id, String 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 String getId() { return id; } public void setId(String id) { this.id = id; } public String getpId() { return pId; } public void setpId(String 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); } } } }
Node包括树节点的常用属性,包括节点的关闭是否、级别已经子节点、都是用来控制显示的。既然我们数据表bean有了、那怎么跟我们用户的数据集关联起来呢,这里就要使用到一开始我们所说的反射机制了。在TreeListAdapter中有一个TreeHelper、就是用来转化处理我们所需要数据集的。那我们来看下TreeHelper:
package com.test.tree.utils; import com.test.tree.R; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * Created by fuweiwei on 2015/12/24. */ public class TreeHelper { /** * 传入我们的普通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> List<Node> nodes = convetData2Node(datas); // 拿到根节点 List<Node> rootNodes = getRootNodes(nodes); // 排序以及设置Node间关系 for (Node node : rootNodes) { addNode(result, node, defaultExpandLevel, 1); } return result; } /** * 过滤出所有可见的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; } /** * 将我们的数据转化为树的节点 * * @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) { String id = null; String pId =null; 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 = (String) f.get(t); } if (f.getAnnotation(TreeNodePid.class) != null) { f.setAccessible(true); pId = (String) f.get(t);; } if (f.getAnnotation(TreeNodeLabel.class) != null) { f.setAccessible(true); label = (String) f.get(t); } if (id!=null && pId != null && 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().equals( n.getId())) { n.getChildren().add(m); m.setParent(n); } else if (m.getId().equals( 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.icon_tree_ex); } else if (node.getChildren().size() > 0 && !node.isExpand()) { node.setIcon(R.drawable.icon_tree_ec); } else node.setIcon(-1); } }
方法我们一个个来看:
1、convetData2Node:遍历传进来的bean,通过注解加反射获取id、pid、label转化为Node,然后设置各个Node之间的关系
2、getRootNodes:通过遍历获取根节点
3、addNode:通过递归的方式、把一个节点上的所有的子节点都按顺序放入
4、setNodeIcon:这个比较简单,就是设置每个Item前面的小图标
5、getSortedNodes:这个上面这些方法,把我们传入的数据集转换我们需要的Node数据集
6、filterVisibleNode:通过遍历获取所有可见的Node,只要是根节点获取父节点展开状态就添加返回
上面就是这个Helper的方法,有效的把用户数据转化为我们需要的数据集。
最后看下我们的注解类,作用就是起到标识的作用,用于反射。
TreeNodeId:
package com.test.tree.utils; 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:
package com.test.tree.utils; 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:
package com.test.tree.utils; 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 { }
大功告成,以上是我对这个功能的总结,只是用于记录和分享,有什么宝贵的意见欢迎大家指正,下面有Demo源码。
源码下载
相关文章推荐
- android的5种数据存储方式
- 利用Gradle实现android的版本共存
- android:clipToPadding和android:clipChildren
- android 修改listview item view 的方法
- Android屏幕截图
- Android 阅读源码,让你彻底理解Handler、Message、Looper之间的关系
- android通过蓝牙连接打印机实现格式化打印(二)
- Android studio 无法启动安卓模拟器Cannot launch AVD in emulator. Output: emulator: WARNING: Increasing RAM siz
- android中常见的内存溢出和解决办法
- android硬件加速(View.LAYER_TYPE_SOFTWARE)与GridView for ScrollView 显示问题
- Android_AnimationDrawable介绍及使用
- MTK android L使用汇顶TP如何使用B协议
- Android-->Fragment生命周期详解(上)
- android studio 用 Live Templates 自动生成switch、try、for、if
- Jenkins构建Android项目持续集成之findbugs的使用
- android 按钮两次点击事件区分
- androidstudio的gradle project sync failed解决
- Android应用:使用adb获得activity堆栈信息
- Android中多界面的退出
- Android拍照裁剪图片