Android多级树形选择列表案例 - 手把手教你快速实现

公司项目中有一个选择联系人的界面,一看里面关系极其复杂,最多时有5层关系嵌套,层数还不一定,有的是第五级是人员,有的是第四级是人员,崩溃中……原来的实现方式是分了三个Activity去分别加载,个人觉得太过臃肿麻烦,选个人要调四次页面,太繁琐了。就想能不能把它整到一个页面中去,既能全选所有人又能实现单选几个人。

刚开始尝试着用 ExpandableListView 实现,效果是实现了但全选状态传递不好弄,如何点击某一层节点,让它的所有孩子都选中?整了半天没整好。心想这轮子肯定有人造过了,本着不重复造轮子的理念,去网上找找看吧。

看了 n 篇文章后,总结一下就是两种解决方案,一种是用 ExpandableListView 实现,还没有见到有案例实现全选的。另一种是直接用 ListView 实现 n 级嵌套,还能全选全不选!就第二种了。

用 ListView 实现的文章几乎所有案例都参照了 鸿神 的那篇 Android 打造任意层级树形控件 考验你的数据结构和设计, 默默的献上膝盖~

鸿神的这篇文章大概看了下,因为后面有很多人都在这个基础上做的优化,原理也写的很详细,索性直接研究后者吧。几经筛选最终挑出了这篇:更快实现Android多级树形选择列表
借鉴了上面的经验之后,顺利的做了出来。

先上图:

选择联系人.gif

分析

实现原理我就不多说啦,上面两篇文章里都讲的很清楚了。简单来讲就是把所有节点都当成一个 Node 对象,Node 对象里有该节点的 id, 它的父节点的 id:pId, 它所有子节点的 list 集合: children, 该节点的层级 level 等等。在设置数据的时候就对数据进行处理,把层级关系排好,按层级依次显示。当选中某个节点时,将它的父节点设置为选中,再将它的所有子节点循环一遍都设置为选中,就解决了全选问题。

用法

本案例中我将选择联系人的操作封装到了一个 Activity 里面,用的时候很简单,只需启动 Activity 的时候传两个参数就可以:

Intent intent = new Intent(this, SelectReceiverActivity.class);
//要请求的数据类型,将项目中用到的类型都封装到枚举类 ReceiverType 里
intent.putExtra("type", ReceiverType.TEACHER);
//本次请求的标记,用于当一个页面要多次调用选人界面时,拿到选择结果的时候做区分
intent.putExtra("flag", "record");
startActivity(intent);

选择的结果通过 EventBus 传递,不了解 EventBus 的请自行补习~

EventBus 的接收事件

@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(SelectReceiverEvent event){
    String flag = event.getFlag();
	/* 如果没传flag可省去判空操作,也可省去判断flag的操作
	   直接根据需要对数据进行处理 */
    if(flag == null){
        return;
    }

    if(TextUtils.equals("flag1", flag)){
        //你自己的操作

    }else if(TextUtils.equals("flag2", flag)){
        //你自己的操作

    }

}

实现

温馨提示:下面的代码可直接复制到你的项目中,根据需要进行删改。手把手教你怎么快速集成到项目中。

先偷个懒,为了能直接用 zhangke3016 的四个工具类,先建个跟他项目一样的包:com.multilevel.treelist,然后将 Node.java , OnTreeNodeClickListener.java , TreeHelper.java , TreeListViewAdapter.java 四个类直接拷到这个包下。

目录结构.png

上代码

工具类篇

跟业务关系不大,可直接拷贝

Node.java
这是节点对象,里面封装了能用到的所有信息,是多级列表的核心类

package com.multilevel.treelist;

import java.util.ArrayList;
import java.util.List;

public class Node<T,B> {

    /**
     * 传入的实体对象
     */
    public B bean;
    /**
     * 设置开启 关闭的图片
     */
    public int iconExpand=-1, iconNoExpand = -1;

    private T id;
    /**
     * 根节点pId为0
     */
    private T pId ;

    private String name;

    /**
     * 当前的级别
     */
    private int level;

    /**
     * 是否展开
     */
    private boolean isExpand = false;

    private int icon = -1;

    /**
     * 下一级的子Node
     */
    private List<Node> children = new ArrayList<>();

    /**
     * 父Node
     */
    private Node parent;
    /**
     * 是否被checked选中
     */
    private boolean isChecked;
    /**
     * 是否为新添加的
     */
    public boolean isNewAdd = true;

    /**
     * 该分组下的人数
     */
    private int count;

    /**
     * 是否是人,1=true, 0=false
     */
    private int isPeople;

    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean isChecked) {
        this.isChecked = isChecked;
    }

    public Node() {}

    public Node(T id, T pId, String name) {
        super();
        this.id = id;
        this.pId = pId;
        this.name = name;
    }

    public Node(T id, T pId, String name, B bean) {
        super();
        this.id = id;
        this.pId = pId;
        this.name = name;
        this.bean = bean;
    }

    public Node(T id, T pId, String name, int count) {
        this.id = id;
        this.pId = pId;
        this.name = name;
        this.count = count;
    }

    public Node(T id, T pId, String name, int count, int isPeople) {
        this.id = id;
        this.pId = pId;
        this.name = name;
        this.count = count;
        this.isPeople = isPeople;
    }

    public int getIcon()
    {
        return icon;
    }

    public void setIcon(int icon)
    {
        this.icon = icon;
    }

    public T getId()
    {
        return id;
    }

    public void setId(T id)
    {
        this.id = id;
    }

    public T getpId()
    {
        return pId;
    }

    public void setpId(T pId)
    {
        this.pId = pId;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public boolean isExpand() {
        return isExpand;
    }

    public List<Node> getChildren() {
        return children;
    }

    public void setChildren(List<Node> children) {
        this.children = children;
    }

    public Node getParent() {
        return parent;
    }

    public void setParent(Node parent) {
        this.parent = parent;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getIsPeople() {
        return isPeople;
    }

    public void setIsPeople(int isPeople) {
        this.isPeople = isPeople;
    }

    /**
     * 是否为跟节点
     *
     * @return
     */
    public boolean isRoot() {
        return parent == null;
    }

    /**
     * 判断父节点是否展开
     *
     * @return
     */
    public boolean isParentExpand() {
        if (parent == null)
            return false;
        return parent.isExpand();
    }

    /**
     * 是否是叶子界点
     *
     * @return
     */
    public boolean isLeaf()
    {
        return children.size() == 0;
    }

    /**
     * 获取level
     */
    public int getLevel() {

        return parent == null ? 0 : parent.getLevel() + 1;
    }

    /**
     * 设置展开
     *
     * @param isExpand
     */
    public void setExpand(boolean isExpand) {
        this.isExpand = isExpand;
        if (!isExpand) {
            for (Node node : children) {
                node.setExpand(isExpand);
            }
        }
    }

    @Override
    public String toString() {
        return "Node{" +
                "id=" + id +
                ", pId=&#
  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
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); } }); ```
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值