更快实现Android多级树形选择列表

在这里插入图片描述

图片demo下载地址:https://download.csdn.net/download/Imshuyuan/85464136

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

解决的问题:

支持ID为int类型和String类型。
支持多级复选框选中,使用只需一行代码。
支持动态更新数据并保持原有展开/关闭状态。
支持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://www.jianshu.com/p/090904d2b689

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android ListView 可以通过自定义适配器来实现多级树形列表。具体步骤如下: 1. 定义每个节点的数据模型,包括节点名称、节点ID、父节点ID等信息。 2. 自定义适配器,继承自 BaseAdapter,重写 getView() 方法,在该方法中根据节点的深度来确定每个节点的缩进距离,并根据节点状态显示不同的图标。 3. 在 Activity 的 onCreate() 方法中初始化树形数据,使用适配器将数据绑定到 ListView 上。 4. 在适配器中实现节点的展开和折叠功能,可以通过设置节点的可见性来实现。 5. 在 ListView 的 OnItemClickListener 中监听每个节点的点击事件,根据节点状态来判断是展开还是折叠。 示例代码如下: 1. 定义节点数据模型 ```java public class TreeNode { private int nodeId; private int parentId; private String nodeName; // 省略 getter 和 setter 方法 } ``` 2. 自定义适配器 ```java public class TreeAdapter extends BaseAdapter { private List<TreeNode> nodes; private LayoutInflater inflater; public TreeAdapter(Context context, List<TreeNode> nodes) { this.nodes = nodes; inflater = LayoutInflater.from(context); } @Override public int getCount() { return nodes.size(); } @Override public Object getItem(int position) { return nodes.get(position); } @Override public long getItemId(int position) { return nodes.get(position).getNodeId(); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.tree_item, null); holder = new ViewHolder(); holder.icon = convertView.findViewById(R.id.icon); holder.name = convertView.findViewById(R.id.name); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } TreeNode node = nodes.get(position); holder.name.setText(node.getNodeName()); int level = getLevel(node); holder.icon.setPadding(30 * (level + 1), holder.icon.getPaddingTop(), holder.icon.getPaddingRight(), holder.icon.getPaddingBottom()); if (hasChildren(node)) { holder.icon.setImageResource(node.isExpanded() ? R.drawable.icon_minus : R.drawable.icon_plus); } else { holder.icon.setImageResource(R.drawable.icon_node); } return convertView; } private int getLevel(TreeNode node) { int level = 0; while (node.getParentId() != 0) { level++; node = findNodeById(node.getParentId()); } return level; } private boolean hasChildren(TreeNode node) { for (TreeNode n : nodes) { if (n.getParentId() == node.getNodeId()) { return true; } } return false; } private TreeNode findNodeById(int nodeId) { for (TreeNode node : nodes) { if (node.getNodeId() == nodeId) { return node; } } return null; } static class ViewHolder { ImageView icon; TextView name; } } ``` 3. 初始化树形数据 ```java private void initTreeData() { nodes = new ArrayList<>(); nodes.add(new TreeNode(1, 0, "Node 1")); nodes.add(new TreeNode(2, 0, "Node 2")); nodes.add(new TreeNode(3, 1, "Node 1-1")); nodes.add(new TreeNode(4, 1, "Node 1-2")); nodes.add(new TreeNode(5, 3, "Node 1-1-1")); nodes.add(new TreeNode(6, 3, "Node 1-1-2")); nodes.add(new TreeNode(7, 4, "Node 1-2-1")); nodes.add(new TreeNode(8, 2, "Node 2-1")); nodes.add(new TreeNode(9, 2, "Node 2-2")); } ``` 4. 实现节点展开和折叠功能 ```java private void toggleNode(int position) { TreeNode node = nodes.get(position); if (node.isExpanded()) { collapseNode(node); } else { expandNode(node); } notifyDataSetChanged(); } private void expandNode(TreeNode node) { node.setExpanded(true); for (TreeNode n : nodes) { if (n.getParentId() == node.getNodeId()) { n.setVisible(true); } } } private void collapseNode(TreeNode node) { node.setExpanded(false); for (TreeNode n : nodes) { if (n.getParentId() == node.getNodeId()) { n.setVisible(false); collapseNode(n); } } } ``` 5. 监听节点点击事件 ```java listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { toggleNode(position); } }); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值