快速实现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. Django通过pycharm创建后,如何登录admin后台?

    问题背景: 使用pycharm创建完成django项目(项目名称为:mydjangopro,app名称为my_blog) , 本想登录后台直接输入地址:http://127.0.0.1:8000/ad ...

  2. DBSCAN

    DBSCAN,英文全写为Density-based spatial clustering of applications with noise ,是在 1996 年由Martin Ester, Han ...

  3. [Kaggle] dogs-vs-cats之制作数据集[1]

    Step 0:导入必要的库 import tensorflow as tfimport os Step 1:获取图片文件名以及对应的标签 首先是读取给定路径下所有图片的名称以及对应的标签.os.lis ...

  4. MongoDB 更新文档

    MongoDB 使用 update() 和 save() 方法来更新集合中的文档.接下来让我们详细来看下两个函数的应用及其区别. update() 方法 update() 方法用于更新已存在的文档.语 ...

  5. 阿里云、腾讯云开通端口 telnet不通的原因

    1.安全组是否已经开通相对应的端口: 阿里云:https://help.aliyun.com/document_detail/25471.html 腾讯云:http://bbs.qcloud.com/ ...

  6. CodeForces 916E Jamie and Tree(树链剖分+LCA)

    To your surprise, Jamie is the final boss! Ehehehe. Jamie has given you a tree with n vertices, numb ...

  7. bootStrap Table 如何使用

    最近在使用bootStrap Table 的表格功能有一些自己的理解写下来分享一下主要用的是一个bootStrapTable 和 jquery 的混合开发 具体怎样引入bootStrap Table ...

  8. mongo 服务化与删除

    MONGO  服务化 使用超级用户进入cmd到D:\mongodb\bin> 日志文件需要提前创建 mongod --bind_ip 0.0.0.0 --logpath D:\mongodb\d ...

  9. servlet学习总结

    一.web工程结构 1.HTTP协议(hyper text transfer protocol)(超文本传输协议) 机制:请求/响应 机制(request/response)(HttpServletR ...

  10. [ZJOI 2014]力

    Description 给出n个数qi,给出Fj的定义如下: $$F_j = \sum_{i<j}\frac{q_i q_j}{(i-j)^2 }-\sum_{i>j}\frac{q_i ...