效果图
思路
-
树形展开时,将该节点的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、链式调用的通用适配器有时间再更新。