D提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、实体类的构建
  • 二、RecyclerView适配器
  • 三、树形列表工具类
  • 总结



前言

近期开发遇到一个需求展示部门的属性列表并且要设置选择状态,包含子部门全部选中、子部门部门选中以及子部门全部不选中这三种选择状态,网上查了一下CheckBox的样式发现比较麻烦,故直接使用ImageView三种状态的图片来代替。

效果图:

android 树状图控件 android树形列表_listview


提示:最底下有demo,需要源码的同学可以下载

一、实体类的构建

不解释,各个属性的含义都在注释上

//树节点实体
public class TreeNodeEntity implements Comparable<TreeNodeEntity>{
    //选中情况
    public final static int CHOOSE_NONE = -1;//未选中
    public final static int CHOOSE_PART = 0;//子数据部分选中
    public final static int CHOOSE_ALL = 1;//全部选中

    private String id;  //当前节点ID
    private String pid;//父节点ID
    private int level; // 节点级别
    private boolean isExpand; //节点是否展开
    private boolean isVisibility; //节点是否显示
    private int chooseStatus; //节点选中状态
    private String showText; //节点显示文本
    private List<TreeNodeEntity> childNodeList = new ArrayList<>(); //子节点数据
	
	//初始化实体
    public TreeNodeEntity(String id, String pid, int level, String showText) {
        this.id = id;
        this.pid = pid;
        this.level = level;
        this.showText = showText;
        this.isExpand = false;
        this.isVisibility = false;
        this.chooseStatus = CHOOSE_NONE;
    }
     //实现排序
    @Override
    public int compareTo(TreeNodeEntity entity) {
        return this.getId().compareTo(entity.getId()); //降序
    }
 }

二、RecyclerView适配器

适配器需要根据节点实体的是否显示状态来控制节点的显示隐藏,是否有子节点以及子节点是否展开状态来控制节点右侧的收缩展开图标的样式,节点的选中状态来控制选中样式。
适配器样式代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/nodeItem_iv_box"
        android:src="@mipmap/box_none"
        android:layout_width="20dp"
        android:layout_height="20dp"/>
    <TextView
        android:id="@+id/nodeItem_tv_name"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="20dp"
        android:layout_marginLeft="6dp"
        android:textSize="16sp"/>
    <ImageView
        android:id="@+id/nodeItem_iv_arrow"
        android:src="@mipmap/arrow_down"
        android:layout_width="20dp"
        android:layout_height="20dp"/>
</LinearLayout>

适配器全部代码代码如下(示例):

public class TreeNodeAdapter extends RecyclerView.Adapter<TreeNodeAdapter.ViewHolder> {
    private Context mContext;
    private List<TreeNodeEntity> list;
    public TreeNodeAdapter(Context mContext, List<TreeNodeEntity> list) {
        this.mContext = mContext;
        this.list = list;
    }
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_tree_node,parent,false);
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(TreeNodeAdapter.ViewHolder holder, int position) {
        TreeNodeEntity entity = list.get(position);
        //根据不同LEVEL来控制与左侧的距离
        holder.itemView.setPadding(30*entity.getLevel(),10,10,10);
        holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT));

		//控制节点的选中状态样式
        if (entity.getChooseStatus() == TreeNodeEntity.CHOOSE_PART) {
            holder.iv_box.setImageResource(R.mipmap.box_part);
        } else if(entity.getChooseStatus() == TreeNodeEntity.CHOOSE_NONE){
            holder.iv_box.setImageResource(R.mipmap.box_none);
        }else{
            holder.iv_box.setImageResource(R.mipmap.box_all);
        }
        holder.tv_name.setText(entity.getShowText());


        if(entity.getChildNodeList().isEmpty() ){//子节点为空则隐藏右侧箭头
            holder.iv_arrow.setVisibility(View.GONE);
        }else if(entity.isExpand()){//子节点展开
            holder.iv_arrow.setImageResource(R.mipmap.arrow_up);
        }else{
            holder.iv_arrow.setImageResource(R.mipmap.arrow_down);
        }

        //设置显示隐藏
        if(entity.isVisibility()){
            setVisibility(true, holder.itemView);
        }else{
            setVisibility(false, holder.itemView);
        }

        //选择框点击事件
        holder.iv_box.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //未选中或部分选中 点击变成全选
                if(entity.getChooseStatus() ==TreeNodeEntity.CHOOSE_NONE||entity.getChooseStatus() ==TreeNodeEntity.CHOOSE_PART){
                    TreeNodeUtils.changeNodeCheckStatus(list,entity,TreeNodeEntity.CHOOSE_ALL);
                }else{//全选点击变为全不选
                    TreeNodeUtils.changeNodeCheckStatus(list,entity,TreeNodeEntity.CHOOSE_NONE);
                }
                notifyDataSetChanged();
            }
        });

		//收缩展开的点击事件
       holder.iv_arrow.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TreeNodeUtils.changeNodeExpendStatus(list, entity);
                notifyDataSetChanged();
            }
        });
    }

    @Override
    public int getItemCount() {
        return  list == null ? 0 : list.size();
    }


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


    //设置隐藏、显示
    public void setVisibility(boolean isVisible, View itemView){
        RecyclerView.LayoutParams param = (RecyclerView.LayoutParams)itemView.getLayoutParams();
        if(param == null){
            return;
        }
        if (isVisible){
            param.height = LinearLayout.LayoutParams.WRAP_CONTENT;
            param.width = LinearLayout.LayoutParams.MATCH_PARENT;
            itemView.setVisibility(View.VISIBLE);
        }else{
            itemView.setVisibility(View.GONE);
            param.height = 0;
            param.width = 0;
        }
        itemView.setLayoutParams(param);
    }


    public class ViewHolder extends RecyclerView.ViewHolder{
        TextView tv_name;
        ImageView iv_box,iv_arrow;
        public ViewHolder(View itemView) {
            super(itemView);
            iv_box = itemView.findViewById(R.id.nodeItem_iv_box);
            tv_name = itemView.findViewById(R.id.nodeItem_tv_name);
            iv_arrow = itemView.findViewById(R.id.nodeItem_iv_arrow);
        }
    }
}

其中为了避免 RecyclerView数据刷新混乱在适配器中添加

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

三、树形列表工具类

该工具类主要用户树形列表数据的整理、节点的各种状态修改等功能。
代码如下

public class TreeNodeUtils {
    /**
     * 整理数据,同一级别按照id进行排序,将子节点数据放到父节点后面
     */
    public static void disposalNodeList(List<TreeNodeEntity> nodes){
        if(nodes == null || nodes.size() == 0){
            return ;
        }
        List<TreeNodeEntity> list = new ArrayList<>();
        list.addAll(nodes);//备份存储原来的数据
        nodes.clear();
        List<TreeNodeEntity> rootList = getRootList(list);
        for(TreeNodeEntity node : rootList){
            nodes.add(node);
            List<TreeNodeEntity> childrenList = new ArrayList<>();
            disposalChildrenList(list,childrenList,node);
            nodes.addAll(childrenList);
        }
    }

    //根据级别设置节点的显示状态
    public static void showTreeWithLevel(List<TreeNodeEntity> list ,int level){
        if(list == null || list.size() == 0){
            return ;
        }
        for(TreeNodeEntity node : list){
            if(node.getLevel()<level){//小于显示级别,要显示该节点并设置该节点为展开状态
                node.setVisibility(true);
                node.setExpand(true);
            }
            else if(node.getLevel()==level){//等于显示级别,要显示该节点并设置该节点为收缩状态
                node.setVisibility(true);
                node.setExpand(false);
            }
            else if(node.getLevel()>level){//大于显示级别,要隐藏该节点并设置该节点为收缩状态
                node.setVisibility(false);
                node.setExpand(false);
            }
        }
    }


    /**
     * 更新节点的选中状态(包括此节点的全部子节点和全部父节点)
     * @param list  全部节点数据
     * @param nowNode 当前节点
     * @param checkStatus 选中状态
     */
    public static void changeNodeCheckStatus(List<TreeNodeEntity> list,TreeNodeEntity nowNode,int checkStatus){
        //设置当前节点的选中状态
        nowNode.setChooseStatus(checkStatus);

        //获取当前节点的全部子节点数据并更新子节点数据
        List<TreeNodeEntity> childrenList = new ArrayList<>();
        disposalChildrenList(list,childrenList,nowNode);
        for(TreeNodeEntity node:childrenList){
            node.setChooseStatus(checkStatus);
        }

        //设置父节点
        List<TreeNodeEntity> parentsList = new ArrayList<>();
        getParentList(list, nowNode, parentsList);

        //倒序进行父节点状态设置
        for(int i = parentsList.size();i>0;i--){
            int chooseCount = 0;//子节点选中数量
            TreeNodeEntity node = parentsList.get(i-1);

            for (TreeNodeEntity entity : node.getChildNodeList()) {
                chooseCount+= entity.getChooseStatus();
            }
            if (chooseCount == TreeNodeEntity.CHOOSE_ALL * node.getChildNodeList().size()) {
                node.setChooseStatus(TreeNodeEntity.CHOOSE_ALL);
            } else if (chooseCount == TreeNodeEntity.CHOOSE_NONE * node.getChildNodeList().size()) {
                node.setChooseStatus(TreeNodeEntity.CHOOSE_NONE);
            } else {
                node.setChooseStatus(TreeNodeEntity.CHOOSE_PART);
            }
        }
    }

    /**
     * 更新节点的展开状态(即子节点的显示隐藏状态)
     */
    public static void changeNodeExpendStatus(List<TreeNodeEntity> list,TreeNodeEntity nowNode){
        if(nowNode.isExpand()){//当前为展开状态,要更改为收缩状态即所有子节点隐藏
            nowNode.setExpand(false);
            setChildrenHide(nowNode);
        }else{
            nowNode.setExpand(true);
            setChildrenShow(nowNode);
        }
    }

    /**
     * 整理节点的子节点数据(包括直接和间接的子节点数据)并进行排序
     */
    public static  void disposalChildrenList(List<TreeNodeEntity>  list,List<TreeNodeEntity> allChildrenList,TreeNodeEntity entity){
        List<TreeNodeEntity> childrenList =getChildrenList(list,entity.getId());
        entity.setChildNodeList(childrenList);
        for(TreeNodeEntity node : childrenList){
            allChildrenList.add(node);
            disposalChildrenList(list,allChildrenList,node);
        }
    }

    /**
     * 获取根节点(父节点为空)并进行排序
     */
    public static List<TreeNodeEntity>  getRootList(List<TreeNodeEntity> list){
        List<TreeNodeEntity> list2 = new ArrayList<>();
        for(TreeNodeEntity node : list){//第一级别数据,父节点为空
            if(TextUtils.isEmpty(node.getPid())){
                list2.add(node);
            }
        }
        Collections.sort(list2);
        return list2;
    }

    /**
     * 获取节点的子节点数据
     */
    public static List<TreeNodeEntity>  getChildrenList(List<TreeNodeEntity> list,String id){
        List<TreeNodeEntity> list2 = new ArrayList<>();
        for(TreeNodeEntity node : list){
            if(node.getPid().equals(id)){
                list2.add(node);
            }
        }
        Collections.sort(list2);
        return list2;
    }

    /**
     * 获取节点的所有父节点(包括间接父节点)
     */
    public static void getParentList(List<TreeNodeEntity> nodes, TreeNodeEntity node, List<TreeNodeEntity> results){
        for(TreeNodeEntity item : nodes){
            if(node.getPid().equals(item.getId())){
                results.add(item);
                getParentList(nodes, item, results);
            }
        }
        Collections. sort(results);
    }

    /**
     * 将子节点设置为隐藏状态
     */
    public static  void setChildrenHide(TreeNodeEntity nowNode){
        for(TreeNodeEntity node : nowNode.getChildNodeList()){
            node.setVisibility(false);
            setChildrenHide(node);
        }
    }
    /**
     * 将子节点设置为显示状态,并且根据子节点的收缩展开状态设置次级子节点的显示隐藏
     */
    public static  void setChildrenShow(TreeNodeEntity nowNode){
        for(TreeNodeEntity node : nowNode.getChildNodeList()){
            node.setVisibility(true);
            if(node.isExpand()){
                setChildrenShow(node);
            }else{
                setChildrenHide(node);
            }
        }
    }
}

总结

核心代码就这些,由于是从项目中抽取出来的,不适宜上传整个项目。以下是我整理的一份demo,需要源码的可以下载。