快速实现Android多级树形列表,这个库是在鸿洋多级树形列表demo中修改而来。

解决的问题

1. 支持ID为int类型和String类型。

2. 支持多级复选框选中,使用只需一行代码。

3. 支持动态更新数据并保持原有展开/关闭状态。

4. 支持ListView、RecyclerView。

项目地址:https://github.com/zhangke3016/MultilevelTreeList

一、概述

这几天项目中有一个多级列表的菜单,最开始给我的感觉应该就是嵌套ListView,或者用ExpandableListView,但问题是ExpandableListView只支持两级列表,而且关键的是具体分几级是不确定的,也就是可能一级,可能多级,这要是五六级嵌套ListView,想想那酸爽。。。最终在偷懒心态的驱使下到网上查查看有没有类似的,也确实查到鸿洋大佬之前写的一篇关于实现Android多级树形列表的文章,实现很巧妙,使用一个ListView就可以实现多级列表效果,就download下demo,在demo基础上做了部分修改,功能顺利实现。

其实到这里应该就结束了,但使用过程中遇到的一些问题让我觉得这个可以进一步优化,比如我要做个多级复选列表,每次处理子级选中与父级选中搞得很累,生怕哪个递归错了。而且在新增数据的时候刷新页面也需要自己处理,直接刷新没有效果,再加上现在RecyclerView已经用的越来越多了。就想着在周末好好总结下,封装个方便使用的库,方便下一次有类似需求的时候使用。说到底,还是为了下次可以偷偷懒呗。如果小伙伴有类似需求,也可以直接拿来用~

先看下效果吧:

这篇文章主要介绍这个库如何使用,如果对具体实现细节感兴趣,可以查看源码或者搜索鸿洋的博客。

二、具体使用

我们关联列表树需要有三个必须元素,当前id、父级id即pid,显示的内容。id和pid可以为int或者String以及其他类型。要显示的内容需要包装一下:

//id pid name  FileNode为实际用的实体Bean对象
mlist.add(new Node("223","0","我也是添加的root节点",new FileNode()));

对于ListView,需要继承自TreeListViewAdapter,如:

public class SimpleTreeAdapter extends TreeListViewAdapter
{
    public SimpleTreeAdapter(ListView mTree, Context context, List<Node> datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {
        super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);
    }

    public SimpleTreeAdapter(ListView mTree, Context context, List<Node> datas,
                             int defaultExpandLevel) {
        super(mTree, context, datas, defaultExpandLevel);
    }

    @Override
    public View getConvertView(final Node node , int position, View convertView, ViewGroup parent)
    {

       final ViewHolder viewHolder ;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item, parent, false);
            viewHolder = new ViewHolder();
            viewHolder.cb = (CheckBox) convertView
                    .findViewById(R.id.cb_select_tree);
            viewHolder.label = (TextView) convertView
                    .findViewById(R.id.id_treenode_label);
            viewHolder.icon = (ImageView) convertView.findViewById(R.id.icon);
            convertView.setTag(viewHolder);

        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.cb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setChecked(node,viewHolder.cb.isChecked());
            }
        });

        if (node.isChecked()){
            viewHolder.cb.setChecked(true);
        }else {
            viewHolder.cb.setChecked(false);
        }

        if (node.getIcon() == -1) {
            viewHolder.icon.setVisibility(View.INVISIBLE);
        } else {
            viewHolder.icon.setVisibility(View.VISIBLE);
            viewHolder.icon.setImageResource(node.getIcon());
        }

        viewHolder.label.setText(node.getName());

        return convertView;
    }

    private final class ViewHolder
    {
        ImageView icon;
        CheckBox cb;
        TextView label;
    }

}

对于RecyclerView,需继承自TreeRecyclerAdapter,如:

public class SimpleTreeRecyclerAdapter extends TreeRecyclerAdapter {

    public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List<Node> datas, int defaultExpandLevel, int iconExpand, int iconNoExpand) {
        super(mTree, context, datas, defaultExpandLevel, iconExpand, iconNoExpand);
    }

    public SimpleTreeRecyclerAdapter(RecyclerView mTree, Context context, List<Node> datas, int defaultExpandLevel) {
        super(mTree, context, datas, defaultExpandLevel);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new MyHoder(View.inflate(mContext, R.layout.list_item,null));
    }

    @Override
    public void onBindViewHolder(final Node node, RecyclerView.ViewHolder holder, int position) {

        final MyHoder viewHolder = (MyHoder) holder;
        //todo do something
        viewHolder.cb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setChecked(node,viewHolder.cb.isChecked());
            }
        });

        if (node.isChecked()){
            viewHolder.cb.setChecked(true);
        }else {
            viewHolder.cb.setChecked(false);
        }

        if (node.getIcon() == -1) {
            viewHolder.icon.setVisibility(View.INVISIBLE);
        } else {
            viewHolder.icon.setVisibility(View.VISIBLE);
            viewHolder.icon.setImageResource(node.getIcon());
        }

        viewHolder.label.setText(node.getName());

    }

    class MyHoder extends RecyclerView.ViewHolder{

        public CheckBox cb;

        public TextView label;

        public ImageView icon;
        public MyHoder(View itemView) {
            super(itemView);

            cb = (CheckBox) itemView
                    .findViewById(R.id.cb_select_tree);
            label = (TextView) itemView
                    .findViewById(R.id.id_treenode_label);
            icon = (ImageView) itemView.findViewById(R.id.icon);

        }

    }
}

初始化

ListView:

        //第一个参数  ListView
        //第二个参数  上下文
        //第三个参数  数据集
        //第四个参数  默认展开层级数 0为不展开
        //第五个参数  展开的图标
        //第六个参数  闭合的图标
         mAdapter = new SimpleTreeAdapter(mTree, ListViewActivity.this,
                        mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec);
         mTree.setAdapter(mAdapter);

RecyclerView

        //第一个参数  RecyclerView
        //第二个参数  上下文
        //第三个参数  数据集
        //第四个参数  默认展开层级数 0为不展开
        //第五个参数  展开的图标
        //第六个参数  闭合的图标
        mAdapter = new SimpleTreeRecyclerAdapter(mTree, RecyclerViewActivity.this,
                mDatas, 1,R.mipmap.tree_ex,R.mipmap.tree_ec);

        mTree.setAdapter(mAdapter);

添加数据,可以保持原有选中或者展开状态:

        List<Node> mlist = new ArrayList<>();
        mlist.add(new Node("223","0","我也是添加的root节点",new FileNode()));
        mAdapter.addData(0,mlist);

获取选中内容:如果node的isChecked()为true,即为选中状态。

    StringBuilder sb = new StringBuilder();
        //获取排序过的nodes
        //如果不需要刻意直接用 mDatas既可
        final List<Node> allNodes = mAdapter.getAllNodes();
        for (int i = 0; i < allNodes.size(); i++) {
            if (allNodes.get(i).isChecked()){
                sb.append(allNodes.get(i).getName()+",");
            }
        }
        String strNodesName = sb.toString();
        if (!TextUtils.isEmpty(strNodesName))
            Toast.makeText(this, strNodesName.substring(0, strNodesName.length()-1),Toast.LENGTH_SHORT).show();

控制父子之间联动的选中与取消状态,只需调用setChecked方法既可,注意如果在setOnCheckedChangeListener中处理会有问题:因为如果要子节点/父节点选中或者取消需要刷新页面,而刷新页面又会触发viewHolder.cb.setChecked(true/false);的判断从而又会进入setOnCheckedChangeListener,会导致如果父节点选中某些子节点取消不了的情况。

 //viewHolder.cb 为CheckBox
 viewHolder.cb.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                setChecked(node,viewHolder.cb.isChecked());
            }
        });

三、简单介绍

通过一个ListView来展示所有数据,每一级内容的显示根据当前展示数据的等级缩进一定的padding值,让我们看起来有缩进效果。

使用过程中感觉不是很舒服的地方在于最终用于显示在界面实体Bean并不是我们传进去的数据,而是经过转化并且过滤的数据,这样最直接的影响就是在我新增数据的数据之后,拿着Adapter来刷新的时候,并没有任何效果。因为我们没有将后面新加的数据进行转化。

而我们如何能在不改变原有数据结构的基础上,添加我们的新内容,并保持原有的选中或者展开正常呢?我的想法是这样的,如果可以直接给它传入转化后的Node节点类型数据就好了,我想到了继承,让实体类去继承基类Node,但一旦继承Node则意味着实体类就不能再继承其他类了,感觉不是很灵活,而且也影响了实体类本身的结构。后来想到了包装设计模式的一些东西,那我就在实体类外再包上一层,也就是将实体类传给Node,最终我们使用的还是Node,但也可以用node.bean很轻松的取出实体类做其他操作,并且实体类本身的结构并没有被破坏。

在此基础上,因为我们的Node不需要转化重新创建,那么它就可以保存一些状态比如展开、选中等等,而在新加入数据时只需标记下新加入的数据,只需对新加入的数据进行初始化状态,已有老数据不进行状态改变:

 if (node.isNewAdd && defaultExpandLeval >= currentLevel) {
            node.setExpand(true);
        }

这样,我们可以保持动态更新添加数据又可以保持原有的展开或选中状态不发生变化,实现我们的需求。

四、源码

项目地址:https://github.com/zhangke3016/MultilevelTreeList

更快实现Android多级树形选择列表的更多相关文章

  1. Android 多级树形菜单

    在Android里要实现树形菜单,都是用ExpandableList(也有高手自己继承ListView或者LinearLayout来做),但是ExpandableList一般只能实现2级树形菜单... ...

  2. android开发者您还在为模拟器犯愁吗?神级android模拟器---Genymotion一个更快、接近完美的模拟器……

    摘要:Android系统非常特别,App须要进行模拟化測试.即使这样仍然有解决的办法---虚拟化技术. 之前的模拟器比方eclipse自带的是非常慢的一种,并且模拟器的版本号并非最新的.开机.能够说差 ...

  3. Gradle更小、更快构建APP的奇淫技巧

    本文已获得原作者授权同意,翻译以及转载原文链接:Build your Android app Faster and Smaller than ever作者:Jirawatee译文链接:Gradle更小 ...

  4. 利用更快的r-cnn深度学习进行目标检测

    此示例演示如何使用名为“更快r-cnn(具有卷积神经网络的区域)”的深度学习技术来训练对象探测器. 概述 此示例演示如何训练用于检测车辆的更快r-cnn对象探测器.更快的r-nnn [1]是r-cnn ...

  5. Android基础TOP4_2:弹窗式选择列表

    Activity: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmln ...

  6. 设计师和开发人员更快完成工作需求的20个惊人的jqury插件教程(上)

    [转] 设计师和开发人员更快完成工作需求的20个惊人的jqury插件教程(上) jquery的功能总是那么的强大,用他可以开发任何web和移动框架,在浏览器市场,他一直是占有重要的份额,今天,就给大家 ...

  7. 更轻更快的Vue.js 2.0与其他框架对比(转)

    更轻更快的Vue.js 2.0 崭露头角的JavaScript框架Vue.js 2.0版本已经发布,在狂热的JavaScript世界里带来了让人耳目一新的变化. Vue创建者尤雨溪称,Vue 2.0  ...

  8. 精通Web Analytics 2.0 (9) 第七章:失败更快:爆发测试与实验的能量

    精通Web Analytics 2.0 : 用户中心科学与在线统计艺术 第七章:失败更快:爆发测试与实验的能量 欢迎来到实验和测试这个棒极了的世界! 如果Web拥有一个超越所有其他渠道的巨大优势,它就 ...

  9. 让DB2跑得更快——DB2内部解析与性能优化

    让DB2跑得更快——DB2内部解析与性能优化 (DB2数据库领域的精彩强音,DB2技巧精髓的热心分享,资深数据库专家牛新庄.干毅民.成孜论.唐志刚联袂推荐!)  洪烨著 2013年10月出版 定价:7 ...

随机推荐

  1. 【Jhipster】升级/修改 数据库结构

    前提 1.jhipster环境,jdk1.8,yeoman,node.js安装环境参考官方wiki,环境问题参考我的博客,如果出现注册中心空白页,请参考博客 2.首先需要启动jhipster基础服务, ...

  2. NGUI----简单聊天系统一

    1:聊天背景的创建 新建一个场景-----保存场景 NGUI---->Create-----Panel 选中UIRoot,然后新建一个sprite 选择图集 效果如下图 添加一个可拖拽的功能 选 ...

  3. ASP.NET Core + Docker + Jenkins + gogs + CentOS 从零开始搭建持续集成

    为什么不用gitlab? 没有采用gitlab,因为gitlab比较吃配置,至少得2核4G的配置.采用go语言开发的gogs来代替,搭建方便(不到10分钟就能安装完成),资源消耗低,功能也比较强大,也 ...

  4. jQuery中的事件监听小记

    一,一个事件监听的简便写法 最近发现一个jQuery中事件监听的简洁写法,感觉方便好多.同时也深感自己基础薄弱,好多东西竟然都模棱两可.因此,记录的同时,也对jQuery事件监听做个小的总结 原文链接 ...

  5. [NOI2016]循环之美

    Description 牛牛是一个热爱算法设计的高中生.在他设计的算法中,常常会使用带小数的数进行计算.牛牛认为,如果在 k  进制下,一个数的小数部分是纯循环的,那么它就是美的.现在,牛牛想知道:对 ...

  6. [POI2006]ORK-Ploughing

    Description Byteasar想耕种他那块矩形的田,他每次能耕种矩形的一边(上下左右都行),在他每次耕完后,剩下的田也一定是矩形,每块小区域边长为1,耕地的长宽分别为m和n,不幸的是Byte ...

  7. 【NOIP2012TG】solution

    D1T1(Vigenere) 题意:给你一个原串与一个密码串,问你按照题意规则加密后的密文. 解题思路:暴力模拟. #include <stdio.h> ],c[],u1[],u2[]; ...

  8. 决战 状压dp

    决定在这个小巷里排兵布阵.小巷可以抽象成一个们彼此之间并不是十分和♂谐.具体来说,一个哲学家会有一个的矩形.每一位哲学家会占据一个格子.然而哲学家的01矩阵来表示他自己的守备范围.哲学家自己位于这个矩 ...

  9. 例10-6 uva1635(唯一分解定理)

    题意:给定n个数a1,a2····an,依次求出相邻两个数值和,将得到一个新数列,重复上述操作,最后结果将变为一个数,问这个数除以m的余数与那些数无关? 思路:最后观察期规律符合杨辉三角,那么,问题就 ...

  10. 例10-3 uva10375(唯一分解定理)

    题意:已知C(m,n) = m!/(n!(m-n)!),已知p,q,r,s,求C(p,q)/C(r,s) 思路: 全部分解成质因子,相乘则加,除则减 #include <iostream> ...