一、概述
前段时间有个项目的需要在Android端显示一个复选的多层树形控件,主要展示一个公司的组织架构,类似总部下面有各个部门,部门之下是组和员工等。另外需要加上展开与回收部门详情、关闭部分已开展的布局、勾选等功能。
效果图如下:
二、思路分析
毫无疑问,对于这种数据可能达到几千几万行的列表视图,我们需要选择recyclerview等具有回收item功能的控件,因此Item的状态保持放在Model中而不是View中。
由于原始数据是树形结构的,我们需要先将树形结构转换为列表数据,类似根结点 - 父节点1 - 子结点1 - 子节点2 - 父节点2......这种形式 - 这恰恰是树的前序遍历
实现思路 - 为了更简洁明白,左右颠倒处理
三、具体实现
简单的节点实现
public abstract class SimpleTreeNode { //层级 protected int hierarchy; //父节点 protected K parent = null; //子节点 protected final List<K> children = new ArrayList<>(); protected boolean isSelected; // 是否被选中 protected boolean isExpand; // 是否展开 } 前序遍历则发生在adapter的getItem和getItemCount的时候 public T getItem(int position) { int[] cur = {position}; return getNode(topGroups, cur); } /** * 先序遍历 - 获取指定位置的节点 * * @param nodes nodes * @param position itemPosition 数组只是为了实现手动box实现共享position * @return MultiSelectNode or null */ protected T getNode(List<T> nodes, final int[] position) { for (T node : nodes) { if (position[0] == 0) { return node; } position[0]--; if (node.getChildren().size() > 0) { T finalNode = getNode(node.getChildren(), position); if (finalNode != null) { return finalNode; } } } return null; } /** * 先序遍历 - 获取展示的总长度 (isExpand = true) * * @param nodes nodes * @return int */ protected int getTreeSize(List<T> nodes) { int size = 0; for (T node : nodes) { size++; size += getTreeSize(node.getChildren()); } return size; }
对于如何实现展开和收缩的功能,我尝试了两种方式:
在渲染item的时候判断node.isExpand = false时,对item进行Gone处理,实际处理发现列表卡顿非常严重:假设所有的item都是隐藏的,那么因为列表没有显示全,所有的item都会进行渲染一遍....
数据遍历的时候将非展开的数据过滤掉:这种方式完美可行,只需要修改下遍历方法即可
protected int getTreeSize(List<T> nodes) { int size = 0; for (T node : nodes) { size++; // 展开过滤 if (node.isExpand()) { size += getTreeSize(node.getChildren()); } } return size; } protected T getNode(List<T> nodes, final int[] position) { for (T node : nodes) { if (position[0] == 0) { return node; } position[0]--; // 展开过滤 if (node.isExpand() && node.getChildren().size() > 0) { T finalNode = getNode(node.getChildren(), position); if (finalNode != null) { return finalNode; } } } return null; }
以上多级树形列表的展开与隐藏便完成了,剩下的便是对树节点的一些操作:例如一个item展开的时候对其他同级item隐藏;一个item被勾选或取消勾选的时候改变其父节点和子节点的状态等。对于这些操作,我采用了类似Motion Event的方式 - 用事件传递与分发来处理。
比如展开的时候同级的item隐藏,其实便是通知兄弟节点设置expand为false。
通知兄弟节点
勾选的操作稍麻烦,可能需要递归通知父节点检查更新,以及递归通知子节点勾选操作,取消勾选亦如此。
关键代码如下
/** * Class: SimpleTreeNode * Author: zwgg * Date: 2017/10/16 * Time: 10:35 * 简单的树节点模板类 * 这个自限定泛型可能有点费解:用于以基类导出类作为自身的泛型,以实现模板功能 * 例如:ClassNameA extend SimpleTreeNode< ClassNameA , T > * @see Enum */ public abstract class SimpleTreeNode<K extends SimpleTreeNode<K, T>, T extends TreeNodeEvent> { //层级 protected int hierarchy; //父节点 protected K parent = null; //子节点 protected final List<K> children = new ArrayList<>(); public SimpleTreeNode() { } public SimpleTreeNode(int hierarchy) { this.hierarchy = hierarchy; } public void bindingParent(K parent) { this.parent = parent; } public void bindingChild(K child) { this.children.add(child); } public void bindingChildren(List<K> children) { this.children.clear(); this.children.addAll(children); } public void dataBinding(K parent, K child) { parent.bindingChild(child); child.bindingParent(parent); } public int getHierarchy() { return hierarchy; } public void setHierarchy(int hierarchy) { this.hierarchy = hierarchy; } /** * 通知父节点 * @param event event */ public void notifyParent(T event) { if (parent != null) { event.setNotifyType(TreeNodeEvent.NOTIFY_PARENT); parent.onEvent(event); } } /** * 通知子节点 * @param event event */ public void notifyChildren(T event) { event.setNotifyType(TreeNodeEvent.NOTIFY_CHILDREN); for (K child : children) { child.onEvent(event); } } /** * 通知兄弟节点 - 需要具有相同的Parent * @param event event */ public void notifyBrother(T event) { if (parent != null) { event.setNotifyType(TreeNodeEvent.NOTIFY_BROTHER); for (K child : parent.children) { if (child != this) { child.onEvent(event); } } } } public abstract void onEvent(T event); public List<K> getChildren() { return children; } }
业务节点
public class MultiSelectNode<T extends MultiSelectNode<T>> extends SimpleTreeNode<T, MultiSelectEvent> { private boolean isSelected; // 是否被选中 private boolean isExpand; // 是否展开 /** * @param hierarchy view层级 - 用于产生viewType */ public MultiSelectNode(int hierarchy) { super(hierarchy); } /** * 事件处理方法 * * @param event 传递得到的事件 */ @Override public void onEvent(MultiSelectEvent event) { switch (event.getNotifyType()) { case TreeNodeEvent.NOTIFY_CHILDREN: // 父节点通知子节点改变选择状态 if (event.getEventType() == MultiSelectEvent.EVENT_SET_SELECTED) { // 如果子节点选择状态有变,则继续通知下层节点改变状态 if (event.isSelected() != isSelected()) { setSelected(event.isSelected()); notifyChildren(event); } } break; case TreeNodeEvent.NOTIFY_PARENT: // 子节点选择状态更改,则通知父节点重新根据所有子节点设置自身状态 if (event.getEventType() == MultiSelectEvent.EVENT_SET_SELECTED) { if (recheckSelected() != isSelected()) { setSelected(!isSelected()); // 如果父节点有变,则继续递归通知 notifyParent(event); } } break; case TreeNodeEvent.NOTIFY_BROTHER: // 通知兄弟节点改变扩展状态 if (event.getEventType() == MultiSelectEvent.EVENT_SET_EXPAND) { if (event.isExpand() != isExpand()) { setExpand(event.isExpand()); } } break; default: break; } } /** * 关闭兄弟节点扩展 * * @param isExpand 是否扩展 */ public void setOtherGroupsExpand(boolean isExpand) { MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_EXPAND); event.setExpand(isExpand); notifyBrother(event); } /** * 通知父节点根据子节点设置状态 * 注:选择具有递归性,如果父类状态有变会继续通知父类 */ public void setParentRecheckSelected() { MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_SELECTED); notifyParent(event); } /** * 通知子节点设置选择状态 * 注:选择具有递归性,会设置所有孩子以及孩子的孩子状态 * * @param isSelected 是否选择 */ public void setChildrenSelected(boolean isSelected) { MultiSelectEvent event = new MultiSelectEvent(MultiSelectEvent.EVENT_SET_SELECTED); event.setSelected(isSelected); notifyChildren(event); } /** * 根据子节点设置自身状态 * * @return isSelected boolean */ public boolean recheckSelected() { for (MultiSelectNode child : children) { if (!child.isSelected()) { return false; } } return true; } public boolean isExpand() { return isExpand; } public void setExpand(boolean expand) { isExpand = expand; } public boolean isSelected() { return isSelected; } public void setSelected(boolean selected) { isSelected = selected; } }
Event事件
public class TreeNodeEvent { public static final int NOTIFY_PARENT = 1; public static final int NOTIFY_CHILDREN = 2; public static final int NOTIFY_BROTHER = 3; private int notifyType; public TreeNodeEvent(){ } public TreeNodeEvent(int notifyType) { this.notifyType = notifyType; } public int getNotifyType() { return notifyType; } public void setNotifyType(int notifyType) { this.notifyType = notifyType; } } public class MultiSelectEvent extends TreeNodeEvent { public static final int EVENT_SET_SELECTED = 1; public static final int EVENT_SET_EXPAND = 2; //事件类型 private int eventType; private boolean isSelected; private boolean isExpand; }
详细可见Github: https://github.com/zwgg/MultiSelectList
总结
以上所述是小编给大家介绍的Android 绘制多级树形选择列表实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对小牛知识库网站的支持!
本文向大家介绍Android实现多级树形选择列表,包括了Android实现多级树形选择列表的使用技巧和注意事项,需要的朋友参考一下 项目中有多个地方要用到多级列表的菜单,最开始我用的是ExpandableListView,但问题是ExpandableListView只支持两级列表,于是我就用ExpandableListView嵌套ExpandableListView,但非常麻烦,而且关键的是具体分
利用UITableView 显示多级树形目录。可以用于文件查看系统。 借助以下两个属性实现多级树形目录 @property(nonatomic) NSInteger indentationLevel; // adjust content indent. default is 0 @property(nonatomic) CGFloat indentationWidth; // width for
问题内容: 我需要创建一个程序来绘制形状(用户使用单选按钮选择),以及是否填充形状(用户使用复选框选择)。这是我到目前为止的代码: 我遇到的错误是表达式和标识符的非法开头。如果我应该采用其他方式,请告诉。 问题答案: 我认为您需要回到基础… 这行不通… 不使用a 作为参数,而是使用 您的所有通话都无法正常进行,因为您尚未实现 我不确定您希望通过此操作实现什么… 但这是行不通的。@Overrid
本文向大家介绍js实现无限级树形导航列表效果代码,包括了js实现无限级树形导航列表效果代码的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了js实现无限级树形导航列表效果代码。分享给大家供大家参考。具体如下: 这是一款js实现无限级树形下拉导航菜单,简洁实用,用到一个已封装好的JS类,有用的大家借鉴一下。 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js
本文向大家介绍Android 列表选择框 Spinner详解及实例,包括了Android 列表选择框 Spinner详解及实例的使用技巧和注意事项,需要的朋友参考一下 Android 列表选择框 Spinner详解及实例 Spinner 是 Android 的列表选择框,不过 spinner 并不需要显示下拉列表,而是相当于弹出一个菜单供用户选择。 Spinner 属性: ● android:sp
本文向大家介绍基于 Vue 的树形选择组件的示例代码,包括了基于 Vue 的树形选择组件的示例代码的使用技巧和注意事项,需要的朋友参考一下 本文介绍了基于 Vue 的树形选择组件。分享给大家,具体如下: 系统要求:Vue 2 基本特性 完美的多级联动效果 支持无限多的分级 有 全选、半选、不选 三种状态 截图展示 代码如下: 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多