Android树形结构,项目通用

1,600 阅读3分钟

效果图

通用多选树.gif

思路

  • 树形展开时,将该节点的childList添加到该节点下,并更新树结构

    mDataList.addAll(position, childList)
    notifyItemRangeInserted(position, childList.size)
    
  • 树形关闭时,树的数据结构移除该节点的childList的数量,并更新树结构

    for (i in 0 until childList.size) {
        mDataList[position].isExpand=false
        mDataList.removeAt(position)
    }
    notifyItemRangeRemoved(position, childList.size)
    
  • 对于含CheckBox的树形结构,每一个节点都需要监听他的状态,当状态和上一次的状态不一样时,则进行更新。更新不仅更新本节点,还需要递归的方式更新他的子节点;因为当前的选中状态还会牵连到他的父节点,他的父节点变更的话还会牵扯到再上一层,所以也需要递归的方式来更新。

    private fun updateNodeState(bean: T) {
        //更新子节点
        updateChildState(bean)
    ​
        //更新父节点状态
        updateParentState(bean)
    ​
        notifyDataSetChanged()
    }
    

    更新子节点

    private fun updateChildState(bean: T) {
        for (child in bean.getChildList()) {
            //更新子节点状态
            child.checkState = bean.checkState
            //递归更新子节点
            updateChildState(child)
        }
    }
    

    更新父节点

    private fun updateParentState(bean: T) {
        //找到父节点并更新
        mDataList.forEach { parent ->
            if (bean.getMyId() in parent.getChildList().map { it.getMyId() }) {
                //全部选中
                val allChecked =
                    parent.getChildList().all { it.checkState == TriStateCheckBox.State.CHECKED }
                val allUnChecked =
                    parent.getChildList().all { it.checkState == TriStateCheckBox.State.UNCHECKED }
    ​
                if (allChecked) {
                    parent.checkState = TriStateCheckBox.State.CHECKED
                } else if (allUnChecked) {
                    parent.checkState = TriStateCheckBox.State.UNCHECKED
                } else {
                    parent.checkState = TriStateCheckBox.State.PARTIALLY_CHECKED
                }
                //递归更新父节点
                updateParentState(parent)
            }
        }
    }
    
  • 设置选中项时,可以先获取到selectList中的所有叶子节点,然后再更新整个树形结构的mDataList选项

    //获取所有的叶子节点
    private fun getLeafNodeList(selectedList: List<T>):List<T>{
        val result = mutableListOf<T>()
        for (bean in selectedList){
            if (bean.hasChild()){
                result.addAll(getLeafNodeList(bean.getChildList()))
            }else{
                result.add(bean)
            }
        }
        return result
    }
    
    fun setSelectedList(selectedList:List<T>){
        //选中的叶子节点列表
        val selectedChildNodeList = getLeafNodeList(selectedList).toMutableList()
        //通过递归的方式检查子列表  
        updateSelectedTree(mDataList, selectedChildNodeList)
        notifyDataSetChanged()
    }
    
  • 因为想通过泛型的方式,适用于任何项目,所以我们搞一个抽象类,包含该节点的层级、是否展开、选中状态等属性

    abstract class TreeBaseBean<T>{
        
        //层级
        var level:Int=0
        //是否展开
        var isExpand = false
        //当前节点状态
        var checkState: TriStateCheckBox.State = TriStateCheckBox.State.UNCHECKED
    ​
        //判断是否有子节点
        fun hasChild():Boolean = !getChildList().isNullOrEmpty()
        
        //获取子节点列表
        abstract fun getChildList():List<T>
        //获取当前节点id
        abstract fun getMyId():Any
        //获取父节点id
        abstract fun getMyParentId():Any?
    ​
    }
    

步骤

处理数据

将项目中的树形数据结构继承自TreeBaseBean,重写该抽象类中的方法

data class MenuBean(
    var id: String = "",
    var parentId: Any? = null,
    var menuName: String = "",
    var menuType: String = "",
    var router: String = "",
    var sort: Int = 0,
    var icon: String = "",
    var sonList: List<MenuBean> = listOf(),
    var status: String = "",
    var userid: String=""
): TreeBaseBean<MenuBean>() {
​
    override fun getChildList(): List<MenuBean> {
        return sonList
    }
​
    override fun getMyId(): Any {
        return id
    }
​
    override fun getMyParentId(): Any? {
        return parentId
    }
}

2、建立自己的ItemView,确保里面包含有一个命名为ivArrow的箭头图片,一个命名为mCheckBox的CheckBox或自定义三种状态的TriStateCheckBox

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/root">
​
    <ImageView
        android:id="@+id/ivArrow"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:src="@drawable/ic_keyboard_arrow_right_black_18dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="@id/mCheckBox"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="@id/mCheckBox" />
​
    <com.sxygsj.treefinalcase.TriStateCheckBox
        android:id="@+id/mCheckBox"
        android:layout_width="20dp"
        android:layout_height="20dp"
        app:layout_constraintLeft_toRightOf="@id/ivArrow"
        app:layout_constraintTop_toTopOf="@id/tvCheckName"
        app:layout_constraintBottom_toBottomOf="@id/tvCheckName"/>
​
    <TextView
        android:id="@+id/tvCheckName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="内容"
        app:layout_constraintLeft_toRightOf="@id/mCheckBox"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginLeft="5dp"
        android:paddingVertical="5dp"/>
    
</androidx.constraintlayout.widget.ConstraintLayout>

使用适配器

//数据项
private val dataList= mutableListOf<MenuBean>()
//设置的选中项
private val selectedList = mutableListOf<MenuBean>()
​
private lateinit var adapter: ChainCommonTreeCheckboxAdapter<MenuBean>
private fun initRcy() {
    adapter = ChainCommonTreeCheckboxAdapter.Builder<MenuBean>()
        .setData(dataList)
        .setLayoutId(R.layout.item_checkbox_tree)
        .addBindView { itemView, bean, level ->
            itemView.findViewById<TextView>(R.id.tvCheckName).setText("层级${level}:"+bean.menuName)
        }
        .addItemClickListener {
            Toast.makeText(this,"点击了:${it.menuName}", Toast.LENGTH_SHORT).show()
        }
        .setPadding(16)
        .create()
​
​
    binding.apply {
        mRcy.layoutManager = LinearLayoutManager(this@ChainMultiActivity)
        mRcy.adapter=adapter
    }
​
}
//设置选中项
adapter.setSelectedList(selectedList)
//默认获取选中和部分选中节点
val list = adapter.getSelectedList()
//获取所有的叶子节点(具体想要获取怎么的选中项,可以通过lambda方式来自己设定规则)
val list = adapter.getSelectedList { bean ->
        bean.checkState == TriStateCheckBox.State.CHECKED&&!bean.hasChild()
}

总结

其实整体的关键操作是数据的处理,怎么通过递归的方式,联动更新各个节点的状态是最重要的,关键代码在思路里已整理,剩余的三种状态的CheckBox、链式调用的通用适配器有时间再更新。