Android多级目录、菜单

好久没写了...好了进入正题,今天介绍下以前的写的一个多级目录。如图(大致效果):

这次依旧是用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的特性,等级比较高时缓存的数量比较多,还是要采取其他措施避免占用较高内存。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值