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

2023-10-27

公司项目中有一个选择联系人的界面,一看里面关系极其复杂,最多时有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=" + pId +
                ", name='" + name + '\'' +
                ", level=" + level +
                ", isPeople=" + isPeople +
                ", count=" + count +
                '}';
    }
}

TreeHelper.java
节点对象 Node 的工具类,对 Node 相关操作的封装

package com.multilevel.treelist;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;

public class TreeHelper {

    /**
     * 传入node  返回排序后的Node
     *
     * @param datas
     * @param defaultExpandLevel
     * @return
     * @throws IllegalArgumentException
     * @throws IllegalAccessException
     */
    public static List<Node> getSortedNodes(List<Node> datas,
                                            int defaultExpandLevel) {
        List<Node> result = new ArrayList<Node>();
        // 设置Node间父子关系
        List<Node> nodes = convetData2Node(datas);
        // 拿到根节点
        List<Node> rootNodes = getRootNodes(nodes);
        // 排序以及设置Node间关系
        for (Node node : rootNodes) {
            addNode(result, node, defaultExpandLevel, 1);
        }
        return result;
    }

    /**
     * 过滤出所有可见的Node
     *
     * @param nodes
     * @return
     */
    public static List<Node> filterVisibleNode(List<Node> nodes) {
        List<Node> result = new ArrayList<Node>();

        for (Node node : nodes) {
            // 如果为跟节点,或者上层目录为展开状态
            if (node.isRoot() || node.isParentExpand()) {
                setNodeIcon(node);
                result.add(node);
            }
        }
        return result;
    }

    /**
     * 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系
     */
    private static List<Node> convetData2Node(List<Node> nodes) {

        for (int i = 0; i < nodes.size(); i++) {
            Node n = nodes.get(i);
            for (int j = i + 1; j < nodes.size(); j++) {
                Node m = nodes.get(j);
                    if (m.getpId() == n.getId()) {
                        n.getChildren().add(m);
                        m.setParent(n);
                    } else if (m.getId() == n.getpId()) {
                        m.getChildren().add(n);
                        n.setParent(m);
                    }
            }
        }

        return nodes;
    }

    private static List<Node> getRootNodes(List<Node> nodes) {
        List<Node> root = new ArrayList<Node>();
        for (Node node : nodes) {
            if (node.isRoot())
                root.add(node);
        }
        return root;
    }

    /**
     * 把一个节点上的所有的内容都挂上去
     */
    private static <T,B> void addNode(List<Node> nodes, Node<T,B> node,
                                      int defaultExpandLeval, int currentLevel) {
        nodes.add(node);

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

        if (node.isLeaf())
            return;
        for (int i = 0; i < node.getChildren().size(); i++) {
            addNode(nodes, node.getChildren().get(i), defaultExpandLeval,
                    currentLevel + 1);
        }
    }

    /**
     * 设置节点的图标
     *
     * @param node
     */
    private static void setNodeIcon(Node node) {
        if (node.getChildren().size() > 0 && node.isExpand()) {
            node.setIcon(node.iconExpand);
        } else if (node.getChildren().size() > 0 && !node.isExpand()) {
            node.setIcon(node.iconNoExpand);
        } else {
            node.setIcon(-1);
        }
    }
}

TreeListViewAdapter.java
对 ListView 的 Adapter 的封装

package com.multilevel.treelist;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;

public abstract class TreeListViewAdapter extends BaseAdapter {

    protected Context mContext;
    /**
     * 存储所有可见的Node
     */
    protected List<Node> mNodes = new ArrayList<>();
    protected LayoutInflater mInflater;

    private List<Node> mDatas = new ArrayList<>();

    private ListView mTree;

    /**
     * 存储所有的Node
     */
    protected List<Node> mAllNodes = new ArrayList<>();

    /**
     * 点击的回调接口
     */
    private OnTreeNodeClickListener onTreeNodeClickListener;
    /**
     * 默认不展开
     */
    private int defaultExpandLevel = 0;
    /**
     * 展开与关闭的图片
     */
    private int iconExpand = -1, iconNoExpand = -1;

    public void setOnTreeNodeClickListener(
            OnTreeNodeClickListener onTreeNodeClickListener) {
        this.onTreeNodeClickListener = onTreeNodeClickListener;
    }

    public TreeListViewAdapter(ListView mTree, Context context, List<Node> datas,
                               int defaultExpandLevel, int iconExpand, int iconNoExpand) {

        this.iconExpand = iconExpand;
        this.iconNoExpand = iconNoExpand;
        this.mDatas = datas;
        this.defaultExpandLevel = defaultExpandLevel;
        mContext = context;
        this.mTree = mTree;

        initData();
    }

    private void initData() {
        for (Node node : mDatas) {
            node.getChildren().clear();
            node.iconExpand = iconExpand;
            node.iconNoExpand = iconNoExpand;
        }

        /**
         * 对所有的Node进行排序
         */
        mAllNodes = TreeHelper.getSortedNodes(mDatas, defaultExpandLevel);
        /**
         * 过滤出可见的Node
         */
        mNodes = TreeHelper.filterVisibleNode(mAllNodes);
        mInflater = LayoutInflater.from(mContext);
        /**
         * 设置节点点击时,可以展开以及关闭;并且将ItemClick事件继续往外公布
         */
        mTree.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view,
                                    int position, long id) {
                expandOrCollapse(position);

                if (onTreeNodeClickListener != null) {
                    onTreeNodeClickListener.onClick(mNodes.get(position),
                            position);
                }
            }

        });
    }

    /**
     * @param mTree
     * @param context
     * @param datas
     * @param defaultExpandLevel 默认展开几级树
     */
    public TreeListViewAdapter(ListView mTree, Context context, List<Node> datas,
                               int defaultExpandLevel) {
        this(mTree, context, datas, defaultExpandLevel, -1, -1);
    }

    /**
     * 清除掉之前数据并刷新  重新添加
     *
     * @param mlists
     * @param defaultExpandLevel 默认展开几级列表
     */
    public void addDataAll(List<Node> mlists, int defaultExpandLevel) {
        mAllNodes.clear();
        addData(-1, mlists, defaultExpandLevel);
    }

    /**
     * 在指定位置添加数据并刷新 可指定刷新后显示层级
     *
     * @param index
     * @param mlists
     * @param defaultExpandLevel 默认展开几级列表
     */
    public void addData(int index, List<Node> mlists, int defaultExpandLevel) {
        this.defaultExpandLevel = defaultExpandLevel;
        notifyData(index, mlists);
    }

    /**
     * 在指定位置添加数据并刷新
     *
     * @param index
     * @param mlists
     */
    public void addData(int index, List<Node> mlists) {
        notifyData(index, mlists);
    }

    /**
     * 添加数据并刷新
     *
     * @param mlists
     */
    public void addData(List<Node> mlists) {
        addData(mlists, defaultExpandLevel);
    }

    /**
     * 添加数据并刷新 可指定刷新后显示层级
     *
     * @param mlists
     * @param defaultExpandLevel
     */
    public void addData(List<Node> mlists, int defaultExpandLevel) {
        this.defaultExpandLevel = defaultExpandLevel;
        notifyData(-1, mlists);
    }

    /**
     * 添加数据并刷新
     *
     * @param node
     */
    public void addData(Node node) {
        addData(node, defaultExpandLevel);
    }

    /**
     * 添加数据并刷新 可指定刷新后显示层级
     *
     * @param node
     * @param defaultExpandLevel
     */
    public void addData(Node node, int defaultExpandLevel) {
        List<Node> nodes = new ArrayList<>();
        nodes.add(node);
        this.defaultExpandLevel = defaultExpandLevel;
        notifyData(-1, nodes);
    }

    /**
     * 刷新数据
     *
     * @param index
     * @param mListNodes
     */
    private void notifyData(int index, List<Node> mListNodes) {
        for (int i = 0; i < mListNodes.size(); i++) {
            Node node = mListNodes.get(i);
            node.getChildren().clear();
            node.iconExpand = iconExpand;
            node.iconNoExpand = iconNoExpand;
        }
        for (int i = 0; i < mAllNodes.size(); i++) {
            Node node = mAllNodes.get(i);
            node.getChildren().clear();
            node.isNewAdd = false;
        }
        if (index != -1) {
            mAllNodes.addAll(index, mListNodes);
        } else {
            mAllNodes.addAll(mListNodes);
        }
        /**
         * 对所有的Node进行排序
         */
        mAllNodes = TreeHelper.getSortedNodes(mAllNodes, defaultExpandLevel);
        /**
         * 过滤出可见的Node
         */
        mNodes = TreeHelper.filterVisibleNode(mAllNodes);
        //刷新数据
        notifyDataSetChanged();
    }

    /**
     * 获取排序后所有节点
     *
     * @return
     */
    public List<Node> getAllNodes() {
        if (mAllNodes == null)
            mAllNodes = new ArrayList<Node>();
        return mAllNodes;
    }

    /**
     * 相应ListView的点击事件 展开或关闭某节点
     *
     * @param position
     */
    public void expandOrCollapse(int position) {
        Node n = mNodes.get(position);

        if (n != null) {// 排除传入参数错误异常
            if (!n.isLeaf()) {
                n.setExpand(!n.isExpand());
                mNodes = TreeHelper.filterVisibleNode(mAllNodes);
                notifyDataSetChanged();// 刷新视图
            }
        }
    }

    @Override
    public int getCount() {
        return mNodes.size();
    }

    @Override
    public Object getItem(int position) {
        return mNodes.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Node node = mNodes.get(position);
        convertView = getConvertView(node, position, convertView, parent);
        // 设置内边距
        convertView.setPadding(node.getLevel() * CommonUtils.dp2px(mContext, 20), 3, 3, 3);
        return convertView;
    }

    /**
     * 设置多选
     *
     * @param node
     * @param checked
     */
    protected void setChecked(final Node node, boolean checked) {
        node.setChecked(checked);
        setChildChecked(node, checked);
        if (node.getParent() != null)
            setNodeParentChecked(node.getParent(), checked);
        notifyDataSetChanged();
    }

    /**
     * 设置是否选中
     *
     * @param node
     * @param checked
     */
    public <T, B> void setChildChecked(Node<T, B> node, boolean checked) {
        if (!node.isLeaf()) {
            node.setChecked(checked);
            for (Node childrenNode : node.getChildren()) {
                setChildChecked(childrenNode, checked);
            }
        } else {
            node.setChecked(checked);
        }
    }

    private void setNodeParentChecked(Node node, boolean checked) {
        if (checked) {
            node.setChecked(checked);
            if (node.getParent() != null)
                setNodeParentChecked(node.getParent(), checked);
        } else {
            List<Node> childrens = node.getChildren();
            boolean isChecked = false;
            for (Node children : childrens) {
                if (children.isChecked()) {
                    isChecked = true;
                }
            }
            //如果所有自节点都没有被选中 父节点也不选中
            if (!isChecked) {
                node.setChecked(checked);
            }
            if (node.getParent() != null)
                setNodeParentChecked(node.getParent(), checked);
        }
    }

    public abstract View getConvertView(Node node, int position,
                                        View convertView, ViewGroup parent);

    public void setData(List<Node> datas) {
        this.mDatas = datas;
        initData();
    }

}

OnTreeNodeClickListener.java
监听节点点击的监听器

package com.multilevel.treelist;

public interface OnTreeNodeClickListener {
    void onClick(Node node, int position);
}

将以上四个类拷贝进项目中去,算是工具类,一般情况下这几个类不用变,Node 的属性可根据需要增删。下面就是项目的具体实现了:

具体业务实现篇

下面跟业务强相关,需要根据自己的项目做相应修改

SelectReceiverActivity.java
实现多级菜单的最终封装体,可直接被调用

/**
 * 选择联系人的Activity
 * 用法:直接启动该Activity,intent传参数 "type",值为枚举值ReceiverType
 * flag 参数可传可不传,是用来标记是谁请求的。主要用于一个页面请求多次来做区分
 * 传值用EventBus。在需要接受选人结果的页面注册EventBus,并接收事件SelectReceiverEvent
 * SelectReceiverEvent事件封装了 选择的人数count 和 选择的人的id拼成的字符串
 */

public class SelectReceiverActivity extends BaseActivity {

    private ListView lv;
    private List<Node> mDatas = new ArrayList<>();

    private static final String ALL = "all";
    private static final String TEACHER = "teacher";
    private static final String STUDENT = "student";
    private static final String PARENT = "parent";
    private ReceiverSelectService service;
    private String token;
    private CloseableRxServiceExecutor executor;
    private Context context;
    private SimpleTreeAdapter mAdapter;
    private CustomProgressDialog progressDialog;
    private CommonTitleView titleView;

    private int studentTotal;  //学生总人数
    private int parentTotal;  //家长总人数
    private int teacherTotal;  //老师总人数
    private Intent intent;
    private String userTypes = "";
    private String flag;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_select_receiver2);

        context = MyApplication.getContext();
        initUserTypes();
        initView();
        initData();
    }

    private void initUserTypes() {
        Intent intent = getIntent();
        //flag用来标记是谁启动了它,如果flag不为空,Event将flag返回,作为区别的标识
        flag = intent.getStringExtra("flag");
        ReceiverType type = (ReceiverType) intent.getSerializableExtra("type");
        //自己的逻辑。根据传的 type 请求不同的数据

    }

    private void initView() {
        lv = (ListView) findViewById(R.id.lv);
        titleView = (CommonTitleView) findViewById(R.id.title_view);

    }

    private void initData() {
        initTitle();
		
		//网络请求的服务类,用的 retrofit 框架
        service = ApiManager.getReceiverSelectService();
		//RxJava 封装的工具类
        executor = new CloseableRxServiceExecutor();
		//当前用户的token
        token = StringUtils.getToken(this);

		//加载中进度条
        progressDialog = CustomProgressDialog.create(this, CommonUtils.getResString(R.string.load_data));
        progressDialog.show();

		//Retrofit + RxJava 网络请求
        Single<DataModel> single = service.getAppUser(token, userTypes);
        executor.execute(single, new Action1<DataModel>() {
            @Override
            public void call(DataModel dataModel) {
                setListData(dataModel);
            }
        }, new Action1<Throwable>() {
            @Override
            public void call(Throwable throwable) {
                progressDialog.dismiss();
                ToastUtils.showToast(CommonUtils.getResString(R.string.request_data_error));
            }
        });
    }

	/**
     * 解析服务器返回的数据。让服务器把所有的人都包装到json里返回了
     * 所以拿到数据后要拆分开
     * 这里要替换为你自己的逻辑
     */
    private void setListData(DataModel dataModel) {
        List<GradeModel> studentData = dataModel.getStudent();
        List<GradeModel> parentData = dataModel.getParent();
        List<GradeModel> teacherData = dataModel.getTeacher();

        if (studentData != null && studentData.size() != 0) {
            //遍历学生
            for (GradeModel model : studentData) {
                studentTotal += model.getCount();
				//将年级数据封装进集合,第三级菜单
                mDatas.add(new Node(model.getId(), STUDENT, model.getName(), model.getCount(), model.getIsPeople()));
                //遍历班级
                List<ClassModel> classList = model.getList();
                if (classList != null && classList.size() != 0) {
                    for (ClassModel classModel : classList) {
                        String classId = classModel.getId();
						//将班级数据封装进集合,第四级菜单
                        mDatas.add(new Node(classId, model.getId(), classModel.getName(), classModel.getCount(), classModel.getIsPeople()));
                        //遍历人员
                        List<StudentModel> studentList = classModel.getList();
                        if (studentList != null && studentList.size() != 0) {
                            for (StudentModel stuModel : studentList) {
								//将人员数据封装进集合,第五级菜单
                                Node stuNode = new Node(stuModel.getId(), classId, stuModel.getName(), 0, stuModel.getIsPeople());
                                mDatas.add(stuNode);
                            }
                        }
                    }
                }
            }
			//创建第二级菜单
            mDatas.add(new Node(STUDENT, ALL, "学生", studentTotal));
        }

        if (parentData != null && parentData.size() != 0) {
            //遍历家长
            for (GradeModel model : parentData) {
                parentTotal += model.getCount();
                mDatas.add(new Node(model.getId(), PARENT, model.getName(), model.getCount(), model.getIsPeople()));
                //遍历班级
                List<ClassModel> classList = model.getList();
                if (classList != null && classList.size() != 0) {
                    for (ClassModel classModel : classList) {
                        String classId = classModel.getId();
                        mDatas.add(new Node(classId, model.getId(), classModel.getName(), classModel.getCount(), classModel.getIsPeople()));
                        //遍历人员
                        List<StudentModel> studentList = classModel.getList();
                        if (studentList != null && studentList.size() != 0) {
                            for (StudentModel stuModel : studentList) {
                                Node stuNode = new Node(stuModel.getId(), classId, stuModel.getName(), 0, stuModel.getIsPeople());
                                mDatas.add(stuNode);
                            }
                        }
                    }
                }
            }
            mDatas.add(new Node(PARENT, ALL, "家长", parentTotal));
        }

        if (teacherData != null && teacherData.size() != 0) {
            //遍历老师
            for (GradeModel model : teacherData) {
                teacherTotal += model.getCount();
                mDatas.add(new Node(model.getId(), TEACHER, model.getName(), model.getCount(), model.getIsPeople()));
                //遍历班级
                List<ClassModel> classList = model.getList();
                if (classList != null && classList.size() != 0) {
                    for (ClassModel classModel : classList) {
                        String classId = classModel.getId();
                        mDatas.add(new Node(classId, model.getId(), classModel.getName(), classModel.getCount(), classModel.getIsPeople()));
                        //遍历人员
                        List<StudentModel> studentList = classModel.getList();
                        if (studentList != null && studentList.size() != 0) {
                            for (StudentModel stuModel : studentList) {
                                Node stuNode = new Node(stuModel.getId(), classId, stuModel.getName(), 0, stuModel.getIsPeople());
                                mDatas.add(stuNode);
                            }
                        }
                    }
                }
            }
            mDatas.add(new Node(TEACHER, ALL, "教师", teacherTotal));
        }

        progressDialog.dismiss();

        //所有人数
        int allTotal = parentTotal + studentTotal + teacherTotal;
        //创建第一级菜单
		mDatas.add(new Node(ALL, "-1", "全体人员", allTotal));
		
		//给 ListView 设置 Adapter
        mAdapter = new SimpleTreeAdapter(lv, context, mDatas, 1, R.drawable.icon_sub, R.drawable.icon_add);
        lv.setAdapter(mAdapter);

    }

    private void initTitle() {
        titleView.setTitleShowType(CommonTitleView.ShowType.MIDDLE_TEXT_AND_RIGHT_TEXT);
        titleView.setMiddleText("选择接收人");
        titleView.setRightText("确定");
        titleView.setOnBackButtonClick(new CommonTitleView.OnBackButtonClick() {
            @Override
            public void onBackClick() {
                finish();
            }
        });

        //点击确定按钮
        titleView.setOnRightTextClick(new CommonTitleView.OnRightTextClick() {
            @Override
            public void onTextClick() {

                if (mAdapter == null) {
                    return;
                }

                List<Node> allNodes = mAdapter.getAllNodes();
                List<String> selectNodesId = new ArrayList<String>();
                for (Node node : allNodes) {
                    if (node.isChecked() && (node.getIsPeople() == 1)) {
                        selectNodesId.add(node.getId().toString());
                    }
                }

                //去重操作
                List list = CommonUtils.deleteRepeat(selectNodesId);
                //格式化
                String ids = list.toString();
                String substring = ids.substring(1, ids.length() - 1);
                String result = substring.replace(", ", ",");

                //用EventBus发送选中的ids 和 一共选了多少人
                if (flag == null) {
                    EventBus.getDefault().post(new SelectReceiverEvent(list.size(), result));
                } else {
                    //flag不为空,返回
                    EventBus.getDefault().post(new SelectReceiverEvent(list.size(), result, flag));
                }

                finish();
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        //取消请求
        try {
            executor.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

		//关闭加载中进度条
        if (progressDialog != null && progressDialog.isShowing()) {
            progressDialog.dismiss();
        }
    }
}

SimpleTreeAdapter
自定义的Adapter,继承了 TreeListViewAdapter

public class SimpleTreeAdapter extends TreeListViewAdapter {

    private Context context;

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

    @Override
    public View getConvertView(final Node node, int position, View convertView, ViewGroup parent) {
        View view;
        final ViewHolder holder;
        if (convertView == null) {
            LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.item_receiver_select_title, null);
            holder = new ViewHolder();
            holder.tvName = (TextView) view.findViewById(R.id.tv_name);
            holder.tvCount = (TextView) view.findViewById(R.id.tv_count);
            holder.cb = (CheckBox) view.findViewById(R.id.check_box);
            holder.iv = (ImageView) view.findViewById(R.id.iv_right);
            view.setTag(holder);
        } else {
            view = convertView;
            holder = (ViewHolder) view.getTag();
        }

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

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

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

        holder.tvName.setText(node.getName());

        //不是学生,都显示当前分类下有多少人
        if (node.getIsPeople() != 1) {
            holder.tvCount.setVisibility(View.VISIBLE);
            holder.tvCount.setText("(" + node.getCount() + ")");
        } else {
            holder.tvCount.setVisibility(View.GONE);
        }

        return view;
    }

    class ViewHolder {
        TextView tvName, tvCount;
        CheckBox cb;
        ImageView iv;
    }
}

资源文件 item_receiver_select_title.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/selector_list_item_bg_gray2gray"
    android:descendantFocusability="blocksDescendants"
    android:orientation="horizontal">

    <CheckBox
        android:id="@+id/check_box"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="5dp"
        android:button="@drawable/selector_checkbox" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:paddingBottom="10dp"
        android:paddingTop="10dp">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:gravity="center_vertical"
            android:text="项目"
            android:textColor="@color/black"
            android:textSize="15sp" />

        <TextView
            android:id="@+id/tv_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="5dp"
            android:gravity="center_vertical"
            android:text="(20)"
            android:textColor="@color/elec_file_gray_bg"
            android:textSize="12sp"
            android:visibility="gone" />

    </LinearLayout>
    
    <ImageView
        android:id="@+id/iv_right"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_gravity="center"
        android:layout_marginRight="5dp"
        android:src="@drawable/icon_add" />

</LinearLayout>

附上 list 去重方法

/**
 * 去除list集合中的重复元素
 * @param list 要去重的 list
 * @return 返回去重后的 list
 */
public static List deleteRepeat(List list){
    List newList = new ArrayList<>();
    Set set = new HashSet();
    for (Object obj : list) {
        if(set.add(obj)){
            newList.add(obj);
        }
    }
    return newList;
}

好啦,以上就是实现多级树形选择列表的方法,直接拷到项目中稍加修改就能用。


做博客迁移,其他博客平台的文章迁移过来。前两年写的代码,还有很多需要改进的地方,大佬们请轻喷~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Linux下修改MySQL密码(只需5步)

    针对忘记root密码方式 1 编辑 etc my cnf文件 vi etc my cnf 2 在 mysqld 下面添加一条命令 skip grant tables 3 输入 wq 保存修改并退出 4 开始修改root密码 4 1 进入My
  • 位、字、字节的区别

    1 位和字节的关系 位 bit 比特 字节 Byte 拜特 1 Byte 8 bit 计算机内存中 最小的存储单位是 位 bit 8个 位 构成一个 字节 byte 字节是内存的基本单位 也是编址单位 例 某计算机的内存是2GB 指的就是该
  • node实现静态文件服务器,nodejs静态资源服务器

    nodejs静态资源服务器 1 http 是nodejs的服务模块 2 url 是url路由模块 3 fs 是文件服务器模块 1 nodejs服务器的创建 主机IP const ip 192 168 1 105 端口号 const port
  • 使用Jsoup登录网站抓取网页内容

    Jsoup可以很方便的模拟浏览器登录 然后根据登录获得sessionid继续做请求来抓取网页的内容 登录的示例代码如下 Connection Response res Jsoup connect http www example com l
  • Java搭积木游戏

    题目 小明最近喜欢搭数字积木 一共有10块积木 每个积木上有一个数字 0 9 搭积木规则 每个积木放到其它两个积木的上面 并且一定比下面的两个积木数字小 最后搭成4层的金字塔形 必须用完所有的积木 下面是两种合格的搭法 0 0 1 2 3
  • 将vue项目上传到gitee(使用cmd)

    本文档适用于 要上传的vue项目是没有上传过gitee的 最下面有无解释版 但是最好看一遍有解释的 1 在gitee新建一个仓库 创建后有一个这个页面 不要关掉 特别有用 2 在下载gitee的地址打开cmd 新建仓库全局设置 这里我下在了
  • 【代码】python Flask实现程序运行结果展示在网页

    目录 1 代码 2 结果 1 代码 from flask import Flask request jsonify import json app Flask name app route img recog methods GET POS
  • java 容易犯错_java面试题最容易犯错

    1 static 和 final 的用法 static 的作用从三个方面来谈 分别是静态变量 静态方法 静态类 静态变量 声明为 static 的静态变量实质上就是全局变量 当声明一个对象时 并不产生static 变量的拷贝 而是该类所有实
  • 一文读懂HTML和CSS的关系

    Web开发是一个很依赖经验的领域 然而这对初学者很不友好 知识一旦脱离了应用场景就会变得晦涩 空洞 且知识本身也满足 二八定律 抓大放小 是提高学习效率的关键 下文向大家介绍了HTML和CSS之间的关系 内容选自 HTML 5与CSS 3核
  • Mybatis-Plus时间范围查询

    方式一 通过apply方法 来实现时间范围查询 该方法可用于数据库函数 动态入参的params对应前面applySql内部的 index 部分 这样是不会有sql注入风险的 反之会有 apply String applySql Object
  • Jenkins 持续集成「编译打包、代码检查、单元测试、环境部署、软件测试​」

    Jenkins 就是常说的 CI 平台 持续集成 持续集成 CI 是一种实践 可以让团队在持续的基础上收到反馈并进行改进 不必等到开发周期后期才寻找和修复缺陷 改进肯定是自己改进 反馈是谁提供呢 最先应用在开发团队中 也就是 打包 大型项目
  • crmeb PRO v1.2用户隐私协议问题

    注意 行数仅供参考 为了快速找到修改的地方 1 文件地址 app controller admin v1 setting SystemGroupData php行数 308 获取用户协议内容 return mixed public func
  • React通过docx-preview预览Word文档

    前言 在基于React的Web应用中 我们经常遇到需要预览和展示Word文档的需求 而docx preview是一个优秀的React组件库 可以帮助我们实现在Web页面上预览Word文档的功能 本文将介绍如何使用docx preview组件
  • React 路由使用-详细介绍

    路由初使用 抽象路由模块 src page Article index js const Article gt return div p 文章页 p div export default Article src router index j
  • selenium框架解析

    seleium框架解析 文章目录 seleium框架解析 前言 一 selenium驱动浏览器原理 二 selenium常用操作 1 四大操作 2 三大切换 3 三大等待 4 下拉框操作 5 时间控件操作 6 滚动条操作 7 文件操作 前言
  • java类的静态成员和非静态成员_Java SE之[静态成员/类成员]与[非静态成员/实例成员]【static】...

    定义 静态成员 又称类成员 使用static修饰符的方法和变量 非静态成员 又称实例成员 未使用static修饰符的方法和变量 结论 注 jdk1 8 测试源码 public class Main private int x 34 非静态变
  • linux path 多个目录,Linux下多路径Multipath的简单配置

    Linux下多路径Multipath的简单配置 1 启用Multipath 1 启动multipathd服务 service multipathd start 或者 etc init d multipathd start 2 修改multi
  • python房价预测_Python——决策树实战:california房价预测

    Python 决策树实战 california房价预测 编译环境 Anaconda Jupyter Notebook 首先 导入模块 1 importpandas as pd2 importmatplotlib pyplot as plt3
  • 手写算法-python代码实现Kmeans

    手写算法 python代码实现Kmeans 原理解析 代码实现 实例演示 sklearn对比 总结 原理解析 今天 我们来讲一下Kmeans 一种无监督聚类算法 也是最为经典的基于划分的聚类方法 它的思想是 对于给定的样本集 按照样本之间的

随机推荐

  • java中的 Set转List

    构造Map数据 Map
  • Mysql详解

    一 数据库的基本概念 数据库的英文单词 DataBase DB 数据库 用来存储和管理数据的仓库 数据库的特点 持久化存储数据 其实数据库就是一个文件系统 方便存储和管理数据 使用了统一的方式操作数据库 SQL 常见的数据库软件 MySQL
  • 两阶段最小二乘法_最小二乘法(Least Squares)简介

    最小二乘法简介 最小二乘法 Least Squares 是回归分析中的一种标准方法 它是用来近似超定系统 Overdetermined System 答案的一种方法 超定系统是指数学中的一种概念 一组包含未知数的方程组中 如果方程的数量大于
  • arm平台编译adb

    参考github https github com bonnyfone adb arm 本来的目的是在arm平台编译以后能够使用adb shell获取shell权限 然后就可以避开其他权限无法执行su的问题 最开始 先修改了android源
  • kodi刮削器 中文_手把手教您设置KODI播放器,3分钟打造家庭影院级媒体库,流畅播放NAS里的原盘电影!...

    创作立场声明 此文为比较基础的KODI播放器的保姆级安装和配置教程 希望可以帮助一些初入家庭影音的值友们 关于KODI KODI是一款播放器 也是一款媒体库管理软件 不仅在电视上可以安装 还可以在windows电脑上进行安装 当下载高清 高
  • Vue3记录

    Vue3快速上手 1 Vue3简介 2020年9月18日 Vue js发布3 0版本 代号 One Piece 海贼王 耗时2年多 2600 次提交 30 个RFC 600 次PR 99位贡献者 github上的tags地址 https g
  • 【1】TypeScript入门——基本认知

    一 基本认知 1 优点 TypeScript 更加可靠 它与使用 JavaScript 相比 不仅支持在任何地方直观地获取组件的接口定义 还能对属性 状态中的值是否为空进行自动检测并给出提示 容错处理 甚至还支持对 React JSX元素接
  • Java创建数组的方法

    最近学Java 一点小心得 希望和大家分享一下 第一次写文章 写的不好希望大家谅解 当然我也会尽力写好这篇文章 Java创建数组的方法大致有三种 说明 这里以int为数据类型 以arr为数组名来演示 一 声明并赋值 int arr 1 2
  • 嵌入式学习——c语言数据的输入输出

    嵌入式学习 c语言数据的输入输出 一 输入输出概念 1 2C语言本身不提供输入输出语句 1 3 include头文件放在程序中 二 printf输出数据 2 2格式字符 三 scanf输入数据 3 1scanf一般格式 四 输入输出函数 4
  • Spring Boot之分离测试和生产环境的应用配置

    多环境应用配置 将默认不变的配置 设置在application properties文件中 新建开发环境下的属性文件application dev properties 将开发中的配置 设置在该文件中 新建生产环境下的属性文件applica
  • “点两下”就能开发一个AI应用!百度砸亿元基金、千万算力要搞插件生态

    金磊 发自 武汉量子位 公众号 QbitAI 这年头 要想开发一个AI应用 怎么搞 只需要简单的 点击 动作就可以了 例如你想开发一个AI作画的App 只需要先点击与之相匹配的能力 简笔成画 根据手绘草图和语言 生成符合要求的图片 言语之美
  • fiddler设置好代理后不能上网(方法二)

    1 打开fiddler在工具栏找到Tools gt options Connections选项如下图 将Allow remote computers to connect 勾选上 然后记住默认8888这个端口 可更改 设置手机代理时会用到
  • 2023高教社数学建模国赛A题 - 定日镜场的优化设计 - 思路

    问题1 计算年平均光学效率和输出热功率 建立模型的坐标系 以圆形区域中心为原点 正东方向为x轴 正北方向为y轴 垂直地面向上为z轴 计算吸收塔和定日镜的位置 吸收塔建于圆形定日镜场中心 根据给定的数据确定定日镜的位置 计算每个定日镜的光学效
  • 使用阿里云OSS实现文件的上传、下载、删除及修改功能

    一 配置OSS相关配置信息 1 要配置 OSS 相关配置信息 您可以按照以下步骤操作 登录阿里云控制台 进入 OSS 控制台 创建一个新的 OSS Bucket 并记录下以下信息 Bucket 名称 Bucket 所属地域 AccessKe
  • 如何解决git上传文件出错[rejected] master -> master (fetch first) error: failed to push some refs to '

    rejected master gt master fetch first error failed to push some refs to git gitee co 上传到码云的时候 报了这个错误 rejected master gt
  • 使用sklearn学习多项式回归(三)

    目录 1 什么是线性 1 1 变量之间的线性关系 1 2 数据间的线性与非线性 1 3 线性模型与非线性模型 1 4 使用分箱处理非线性问题 2 多项式回归PolynomialFeatures 2 1 什么是多项式回归 2 2 多项式回归处
  • 使用 Openssl 验证自签名证书

    原文地址 http blog csdn net kmyhy article details 6546072 iOS的 security framework 框架前面已经介绍 这个框架提供有限的功能 使用它能做到的 比你想象的要少 笔者一直想
  • html input 禁用缓存

    多数浏览器默认会缓存input的值 只有使用ctl F5强制刷新的才可以清除缓存记录 如果不想让浏览器缓存input的值 有2种方法 input 的属性autocomplete 默认为on 其含义代表是否让浏览器自动记录之前输入的值 很多时
  • word编辑公式简单方法

    安装Python包 pix2tex 在终端输入 pip install pix2tex gui i https pypi doubanio com simple 就可以安装 安装好以后 在终端输入pix2tex gui就可以启动 第一次启动
  • Android多级树形选择列表案例 - 手把手教你快速实现

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