美文网首页程序员Android开发经验谈Android知识
Android - 如何绘制多级树形选择列表

Android - 如何绘制多级树形选择列表

作者: 周兔子 | 来源:发表于2018-02-06 11:35 被阅读110次

    一、概述

    前段时间有个项目的需要在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;
    }
    

    对于如何实现展开和收缩的功能,我尝试了两种方式:

    1. 在渲染item的时候判断node.isExpand = false时,对item进行Gone处理,实际处理发现列表卡顿非常严重:假设所有的item都是隐藏的,那么因为列表没有显示全,所有的item都会进行渲染一遍....

    2. 数据遍历的时候将非展开的数据过滤掉:这种方式完美可行,只需要修改下遍历方法即可

       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 - 如何绘制多级树形选择列表

        本文链接:https://www.haomeiwen.com/subject/thqgzxtx.html