好久没写了...好了进入正题,今天介绍下以前的写的一个多级目录。如图(大致效果):
这次依旧是用kotlin编写,采用recycleview实现,对数据进行动态更新达到展开收起的目的。下面介绍实现步骤:
1.构建基本数据类
记录item(目录或菜单)所需基本信息(MultilevelMenuBase),属性如下:
/**
* Created by xinheng on 2019-11-11 22:56.
* describe:多级菜单适配器
*/
open class MultilevelMenuBase {
/**
* 等级
*/
var rank = 1
/**
* 子元素数量(仅限下一级)
*/
var childSize = 0
/**
* 是否展开
*/
var isShow = false
/**
* 初始位置(对应最初的集合)
*/
var oldPosition = 0
}
2.重写recycleview的适配器
①数据为MultilevelMenuBase的子类(Module)的集合(moduleList),以及初始数据集合(allModuleList)
②Holer包含一个点击的view(clickShowView),它的作用的是点击展开或关闭目录(菜单)
③对外抛出抽象方法比如:holderview的创建、holderview的绑定等,用于扩展,子类实现
④目录(菜单)的收起和展开对应数据的删除和添加
父类适配器代码(这个没加注释。。。)如下:
package com.xinheng.menu
import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
/**
* Created by xinheng on 2019/11/11 22:59.
* describe:多级菜单适配器
*/
abstract class MultilevelMenuParentAdapter<Module : MultilevelMenuBase, Holder : MultilevelMenuParentAdapter.MultilevelMenuParentHolder> :
RecyclerView.Adapter<Holder>() {
private var moduleList: MutableList<Module>? = null
protected var allModuleList: MutableList<Module>? = null
/**
* 初始化数据
* @param allList 全部数据
* @param showType 展开类型 true第一次数据全部展开显示,false只显示一级菜单
*/
fun initAllModuleList(allList: MutableList<Module>? = null, showType: Boolean = false) {
allModuleList = allList
moduleList = ArrayList()
allList?.forEachIndexed { index, module ->
module.oldPosition = index
module.isShow = showType
if (!showType && module.rank == 1) {
moduleList!!.add(module)
} else {
moduleList!!.add(module)
}
}
notifyDataSetChanged()
}
fun getPositionModule(position: Int) = moduleList!![position]
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder =
childCreateViewHolder(parent, viewType).apply {
clickShowView.setOnClickListener(childShowViewClickListener)
}
override fun getItemViewType(position: Int): Int = moduleList!![position].rank
abstract fun childCreateViewHolder(parent: ViewGroup, viewType: Int): Holder
override fun getItemCount(): Int = moduleList?.size ?: 0
override fun onBindViewHolder(holder: Holder, position: Int) {
childBindViewHolder(holder, position)
holder.clickShowView.setTag(R.id.show_position, position)
}
abstract fun childBindViewHolder(holder: Holder, position: Int)
private val childShowViewClickListener = View.OnClickListener {
val position = it.getTag(R.id.show_position)?.let { item ->
if (item is Int)
item
else
-1
} ?: -1
if (position == -1) return@OnClickListener
val itemModule = moduleList!![position]
val childSize = itemModule.childSize
if (childSize == 0) return@OnClickListener
dealWithClickView(it,!itemModule.isShow)
if (itemModule.isShow) {
itemModule.isShow = false
closeMenu(itemModule.rank, position)
} else {
itemModule.isShow = true
showMenu(itemModule.rank, itemModule.oldPosition, childSize, position)
}
}
abstract fun dealWithClickView(clickView: View, showStatue: Boolean)
private fun showMenu(rank: Int, oldPosition: Int, childSize: Int, clickPosition: Int) {
//0 12 34 5 6
val arrayList = ArrayList<Module>(childSize)
(oldPosition + 1 until allModuleList!!.size).forEach {
val module = allModuleList!![it]
//Log.e("TAG", "index=$it")
if (module.rank == rank + 1) {
arrayList.add(module)
} else if (module.rank == rank) {
return@forEach
}
}
val positionStart = clickPosition + 1
moduleList!!.addAll(positionStart, arrayList)
notifyItemRangeInserted(positionStart, childSize)
notifyItemRangeChanged(positionStart, childSize)
}
private fun closeMenu(rank: Int, clickPosition: Int) {
val iterator = moduleList!!.iterator()
var childSize = 0
while (iterator.hasNext()) {
val next = iterator.next()
if (next.rank > rank) {
++childSize
next.isShow = false
iterator.remove()
}
}
val positionStart = clickPosition + 1
notifyItemRangeRemoved(positionStart, childSize)
notifyItemRangeChanged(positionStart, childSize)
}
abstract class MultilevelMenuParentHolder(view: View) : RecyclerView.ViewHolder(view) {
//var iv_show = view.findViewById<View>(R.id.iv_show)
//var tv_content = view.findViewById<TextView>(R.id.tv_content)
//控制view展开、关闭的view
var clickShowView = initClickShowView()
abstract fun initClickShowView(): View
}
}
3.应用
这里随便构造了一些数据进行实现。
①实现基本数据类(MultilevelMenuBase)
/**
* Created by xinheng on 2019/11/12 1:26.
* describe:
*/
class Book : MultilevelMenuBase {
constructor()
constructor(text: String, rank: Int, size: Int) {
this.text = text
this.rank = rank
this.childSize = size
}
//这里仅加了一个属性,可以添加多个其他属性
var text = ""
}
②构造假数据集合。若从服务器获得数据结构不一样,需要进行转换成我们需要的。
private fun iniData(): ArrayList<Book> {
val arrayList = ArrayList<Book>()
arrayList.add(Book("第1级", 1, 3))
arrayList.add(Book("第2级", 2, 0))
arrayList.add(Book("第2级", 2, 2))
arrayList.add(Book("第3级", 3, 0))
arrayList.add(Book("第3级", 3, 2))
arrayList.add(Book("第4级", 4, 0))
arrayList.add(Book("第4级", 4, 0))
arrayList.add(Book("第2级", 2, 0))
return arrayList
}
③实现自己的recycleview适配器、holder、目录(菜单)item的xml布局(xml就不粘贴了)。在构建holder的时候,根据数据等级进行对子目录(子菜单)相对上一级的距离进行修改。
/**
* Created by xinheng on 2019/11/12 1:27.
* describe:
*/
class BookAdapter : MultilevelMenuParentAdapter<Book, BookAdapter.BookHolder>() {
override fun childCreateViewHolder(parent: ViewGroup, viewType: Int): BookHolder = BookHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.layout_menu_item,
parent,
false
)
).apply {
if (viewType == 1) return@apply
val layoutParams = tv_content.layoutParams as LinearLayout.LayoutParams
val marginStart = layoutParams.marginStart
layoutParams.marginStart = marginStart * viewType
tv_content.layoutParams = layoutParams
}
override fun childBindViewHolder(holder: BookHolder, position: Int) {
val book = getPositionModule(position)
holder.tv_content.text = book.text
if(book.childSize==0){
holder.clickShowView.visibility=View.INVISIBLE
}else{
holder.clickShowView.visibility=View.VISIBLE
dealWithClickView(holder.clickShowView,book.isShow)
}
}
override fun dealWithClickView(clickView: View, showStatue: Boolean) {
(clickView as TextView).text=if(showStatue) " - " else " + "
}
class BookHolder(view: View) :
MultilevelMenuParentAdapter.MultilevelMenuParentHolder(view) {
override fun initClickShowView(): View = itemView.findViewById(R.id.iv_show)
var tv_content = view.findViewById<TextView>(R.id.tv_content)
}
}
4.总结
这里仅仅是简单的介绍了构建多级目录(菜单)的思路。还有好多可以扩展的地方,比如:每个item的布局以及多样式item(不同级别的item布局不一样)、子目录相对上一级的位置等。这个下次再说吧。
理论上可以无限制目录等级,但是根据recycleview的特性,等级比较高时缓存的数量比较多,还是要采取其他措施避免占用较高内存。