博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
手把手教你实现高性能Android树形控件 TreeView
阅读量:7084 次
发布时间:2019-06-28

本文共 6302 字,大约阅读时间需要 21 分钟。

先看效果

原理说明

使用recyclerView 动态添加、删除Item,实现显示效果。recyclerView本身就支持view的复用,所以用它来实现树形控件的性能是非常好的。

总体思路

既然是控件,就要适用于各种各样的数据。所以我们要泛型编程。首先要抽象数据,使我们的控件能适配各种各样的数据。然后将数据构造成树的结构。最后使用recyclerView动态进行添加和删除。思路看起来有点粗略,下面一起来实现吧。

代码实现

抽象数据

设计一种既能适配各种数据类型,又能满足树形控件要求的实体类。想一想,既然是树形控件,那么树的节点一定会有一个id和parentId。又要兼容各种数据类型,所以,我们可以考虑把id和parentId做成接口,具体数据类型做成泛型。

设想一下,一个树形控件的模型(树的节点)需要哪些字段?

1、有id,pid,用户数据T
2、树的节点应该有层级的吧 每个层级前面的缩进是不一样的 所以给它一个 level字段
3、树的节点应该有一个字段表明树是否展开吧 所以给他一个 isExpand字段
4、树的节点可能有子节点的吧 所以给他一个List children字段
5、既然有子节点,那也应该有父节点吧 所以给他一个 parent字段
6、取个名字吧 叫 TreeNode

public interface NodeId {    public String getId();    public String getPId();}复制代码
// 泛型参数应该要实现NodeId接口,确保能拿到id和ParentIdpublic class TreeNode
{ private final static String TAG = "TreeNode"; private T data; // 用户的数据 private int level; // 层级 private boolean isExpand; // 是否展开 private TreeNode
parent; // 父节点 private List
> children = new ArrayList<>(); // 孩子结点 public TreeNode(T data) { this(data,-1); } public TreeNode(T data) { this.data = data; } public String getId() { if (data == null) { Log.e(TAG, "getId: data is null"); return ""; } return data.getId(); } public String getPId() { if (data == null) { Log.e(TAG, "getParentId: data is null"); return ""; } return data.getPId(); } // 如果没有父节点,就证明是跟结点 public boolean isRoot() { return parent == null; } public boolean isParentExpand() { if (parent == null) { return false; } return parent.isExpand(); } public boolean isExpand() { return isExpand; } // 设置结点关闭的时候,因该将改结点下的所有结点一起关闭。 public void setExpand(boolean expand) { isExpand = expand; if (!isExpand) { for (TreeNode node : children) { node.setExpand(false); } } } public int getLevel() { return parent == null ? 0 : parent.getLevel() + 1; } public T getData() { return data; } public void setData(T data) { this.data = data; } public void setLevel(int level) { this.level = level; } public TreeNode getParent() { return parent; } public void setParent(TreeNode parent) { this.parent = parent; } public List
> getChildren() { return children; } public void setChildren(List
> children) { this.children = children; } public boolean isLeaf() { return ListUtil.isEmpty(children); //是否是叶子结点,没有子结点,就证明是叶子结点 }}复制代码

这样,我们就将用户的数据全部转化为TreeNode类型,然后使用TreeNode作为树形控件的模型统一操作。

构造树形结构

树结点的数据模型有了,下一步就是将数据构造成树形的结构。

写一个Util方法,它的作用就是 将用户的数据构造成TreeNode,并设置好treeNode里面的各种属性。最重要的就是设置好parent和Children属性。
算法有多种,这里说一下我的思路。 根据用户数据构造出TreeNode,然后将TreeNode放入一个map中,以Id为key,treeNode为value,最后再次遍历TreeNode,在map中根据pid找到它的父节点。

public static 
List
> convertDataToTreeNode(List
datas) { List
> nodes = new ArrayList<>(); Map
> map = new HashMap(); for (NodeId nodeId : datas) { TreeNode treeNode = new TreeNode(nodeId); nodes.add(treeNode); map.put(nodeId.getId(), treeNode); } Iterator
> iterator = nodes.iterator(); while(iterator.hasNext()){ TreeNode
treeNode = iterator.next(); String pId = treeNode.getPId(); TreeNode
parentNode = map.get(pId); if (parentNode != null) { parentNode.getChildren().add(treeNode); treeNode.setParent(parentNode); iterator.remove(); } } return nodes; }复制代码

同时,我们还需要一个方法,当结点展开或者关闭的时候,我们需要获取结点下面的子结点,动态的添加或者删除。有人就问了,不是可以通过treeNode.getChildren()方法直接获取吗? 这样只对了一半。因为如果我仅仅通过treeNode.getChildren()获取子节点,只能获取到该结点下的子节点,却不能获取子结点的子节点。所有我们要递归去获取。

public static 
List
> getNodeChildren(TreeNode
node) { List
> result = new ArrayList<>(); getRNodeChildren(result, node); return result; } private static
void getRNodeChildren(List
> result, TreeNode
node) { List
> children = node.getChildren(); for (TreeNode n : children) { result.add(n); if (n.isExpand() && !n.isLeaf()) { getRNodeChildren(result, n); } } }复制代码

现在方法有了,数据结构也有了。下面就交给recycerView了。

RecyclerView实现动态添加删除结点

推荐一个比较好用的recycler适配器,这次的TreeView就是基于这个适配器的。

项目地址是

思考一下,需要在适配器里面做点什么?

其实适配器和适配普通RecyclerView差不多,唯一我们需要多做的就是添加一个点击事件,当点击结点的时候,判断一下当前状态,如果当前是关闭,那么就展开,并且添加子结点到数据源。
同时应该在初始化item的时候根据level为item设置一个padding距离,这样能让树形控件看起来具有层级关系。

public class SingleLayoutTreeAdapter
extends BaseQuickAdapter
, BaseViewHolder> { public interface OnTreeClickedListener
{ void onNodeClicked(View view, TreeNode
node, int position); void onLeafClicked(View view, TreeNode
node, int position); } private OnTreeClickedListener onTreeClickedListener; public SingleLayoutTreeAdapter(int layoutResId, @Nullable final List
> dataToBind) { super(layoutResId, dataToBind); setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { TreeNode
node = dataToBind.get(position); if (!node.isLeaf()) { List
> children = TreeDataUtils.getNodeChildren(node); if (node.isExpand()) { dataToBind.removeAll(children); node.setExpand(false); notifyItemRangeRemoved(position + 1, children.size()); } else { dataToBind.addAll(position + 1, children); node.setExpand(true); notifyItemRangeInserted(position + 1, children.size()); } if (onTreeClickedListener != null) { onTreeClickedListener.onNodeClicked(view, node, position); } } else { if (onTreeClickedListener != null) { onTreeClickedListener.onLeafClicked(view, node, position); } } } }); } @Override protected void convert(BaseViewHolder helper, TreeNode
item) { int level = item.getLevel(); ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) helper.itemView.getLayoutParams(); layoutParams.leftMargin = getTreeNodeMargin() * level; } public void setOnTreeClickedListener(OnTreeClickedListener onTreeClickedListener) { this.onTreeClickedListener = onTreeClickedListener; } protected int getTreeNodeMargin() { return DpUtil.dip2px(this.mContext, 10); }}复制代码

完整的代码实现在

转载于:https://juejin.im/post/5bd400536fb9a05cef177877

你可能感兴趣的文章
人工智能第一次作业
查看>>
数据结构 链栈
查看>>
爬虫一 发请求&定制请求&异常处理&配置代理
查看>>
DC7css设计表单样式
查看>>
POJ 1160:Post Office 邮局经典DP
查看>>
Apache配置虚拟目录和多主机头
查看>>
基于MySQL自增ID字段增量扫描研究
查看>>
linux新的API signalfd、timerfd、eventfd使用说明
查看>>
Grunt 与WebStrom 集成
查看>>
window.open(转)
查看>>
FPGA Verilog HDL 系列实例--------步进电机驱动控制
查看>>
android开发教程
查看>>
关于字符串的一些简单编码题
查看>>
vs2012
查看>>
ORA-00119: invalid specification for system parameter REMOTE_LISTENER
查看>>
Python3学习笔记- 变量
查看>>
AES 前后端数据传输加密
查看>>
作业一(王德仙)
查看>>
CCF NOI1057 石头剪刀布
查看>>
POJ3009 Curling 2.0【DFS】
查看>>