Android树形结构,项目通用
效果图
思路
- 树形展开时,将该节点的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、链式调用的通用适配器有时间再更新。
作者:重拾丢却的梦
来源:juejin.cn/post/7434799466974134284
来源:juejin.cn/post/7434799466974134284