注册

Android树形结构,项目通用

效果图


通用多选树.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、链式调用的通用适配器有时间再更新。


作者:重拾丢却的梦
来源:juejin.cn/post/7434799466974134284

0 个评论

要回复文章请先登录注册