RecyclerView流式布局

最近在做需求的时候,碰到有各种筛选项的界面,类似这样

RecyclerView流式布局_第1张图片 

这种筛选界面也比较常见,一般我们都采用RecyclerView来实现,当筛选项比较少的时候,我们选择使用线性布局来实现,当筛选项比较多,但是每个筛选项长度一样时,我们可以采用网格布局。但是这次的需求,筛选项有长有短,并且当每一行还有空间时,不能进行换行,因此RecyclerView自带的瀑布流布局也不能满足需求。

我们知道RecyclerView的布局是由LayoutManager负责,因此如果要实现这种效果,我把它称之为流式布局,需要自定义LayoutManager。首先我们来看一下LayoutManager的几个重要的方法

方法名 作用
isAutoMeasureEnabled 这个方法表示是否采用自动测量,一般都是true,否则就需要自己重写onMeasure方法。
generateDefaultLayoutParams 这是个抽象方法,用于给ItemView设置布局参数,必须实现。

onMeasure

测量RecyclerView大小
onLayoutChildren 对ItemView进行布局,Item如何摆放,全看这个方法如何实现

那么我们要重写LayoutManager就简单需要以下几个步骤

重写isAutoMeasureEnabled

   override fun isAutoMeasureEnabled(): Boolean {
        return true
    }

只需要返回true,代表RecyclerView自动测量。RecyclerView的三个默认实现的布局管理器也是返回true的。

重写generateDefaultLayoutParams

 override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return RecyclerView.LayoutParams(
            RecyclerView.LayoutParams.WRAP_CONTENT,
            RecyclerView.LayoutParams.WRAP_CONTENT
        )
    }

没有什么特殊要求,直接返回自适应即可,给Item设置默认的布局参数。这个方式必须实现。

重写onLayoutChildren

由于isAutoMeasureEnabled直接返回true,因此没有必要重写onMeasure方法,启动自动测量。但我们需要重点实现onLayoutChildren方法,这是我们自定义LayoutManager的核心所在。

  override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State?) {
        //这里是在布局前将所有在显示的HoldView从RecyclerView中剥离,将其放在缓存中,方便重新布局时使用。
        detachAndScrapAttachedViews(recycler)
        var curLineWidth = 0 //curLineWidth 累加item布局时的x轴偏移
        var curLineTop = 0 //curLineTop 累加item布局时的y轴偏移
        var lastLineMaxHeight = 0
        for (i in 0 until itemCount) {
            val view: View = recycler.getViewForPosition(i)
            //获取每个item的布局参数,计算每个item的占用位置时需要加上margin
            val params = view.layoutParams as RecyclerView.LayoutParams
            addView(view)
            measureChildWithMargins(view, 0, 0)
            val width = getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin
            val height = getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin
            curLineWidth += width //累加当前行已有item的宽度
            if (curLineWidth <= getWidth()) { //如果累加的宽度小于等于RecyclerView的宽度,不需要换行
                layoutDecorated(
                    view,
                    curLineWidth - width + params.leftMargin,
                    curLineTop + params.topMargin,
                    curLineWidth - params.rightMargin,
                    curLineTop + height - params.bottomMargin
                ) //布局item的真实位置
                //比较当前行多有item的最大高度,用于换行后计算item在y轴上的偏移量
                lastLineMaxHeight = Math.max(lastLineMaxHeight, height)
            } else { //换行
                curLineWidth = width
                if (lastLineMaxHeight == 0) {
                    lastLineMaxHeight = height
                }
                //记录当前行top
                curLineTop += lastLineMaxHeight
                layoutDecorated(
                    view,
                    params.leftMargin,
                    curLineTop + params.topMargin,
                    width - params.rightMargin,
                    curLineTop + height - params.bottomMargin
                )
                lastLineMaxHeight = height
            }
        }
    }

经过这三步就可以完整的实现一个自定义的LayoutManager。

下面是我自定义的流式布局完整代码

class FlowLayoutManager : RecyclerView.LayoutManager() {


    override fun isAutoMeasureEnabled(): Boolean {
        return true
    }

    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
        return RecyclerView.LayoutParams(
            RecyclerView.LayoutParams.WRAP_CONTENT,
            RecyclerView.LayoutParams.WRAP_CONTENT
        )
    }

    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State?) {
        //这里是在布局前将所有在显示的HoldView从RecyclerView中剥离,将其放在缓存中,方便重新布局时使用。
        detachAndScrapAttachedViews(recycler)
        var curLineWidth = 0 //curLineWidth 累加item布局时的x轴偏移
        var curLineTop = 0 //curLineTop 累加item布局时的y轴偏移
        var lastLineMaxHeight = 0
        for (i in 0 until itemCount) {
            val view: View = recycler.getViewForPosition(i)
            //获取每个item的布局参数,计算每个item的占用位置时需要加上margin
            val params = view.layoutParams as RecyclerView.LayoutParams
            addView(view)
            measureChildWithMargins(view, 0, 0)
            val width = getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin
            val height = getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin
            curLineWidth += width //累加当前行已有item的宽度
            if (curLineWidth <= getWidth()) { //如果累加的宽度小于等于RecyclerView的宽度,不需要换行
                layoutDecorated(
                    view,
                    curLineWidth - width + params.leftMargin,
                    curLineTop + params.topMargin,
                    curLineWidth - params.rightMargin,
                    curLineTop + height - params.bottomMargin
                ) //布局item的真实位置
                //比较当前行多有item的最大高度,用于换行后计算item在y轴上的偏移量
                lastLineMaxHeight = Math.max(lastLineMaxHeight, height)
            } else { //换行
                curLineWidth = width
                if (lastLineMaxHeight == 0) {
                    lastLineMaxHeight = height
                }
                //记录当前行top
                curLineTop += lastLineMaxHeight
                layoutDecorated(
                    view,
                    params.leftMargin,
                    curLineTop + params.topMargin,
                    width - params.rightMargin,
                    curLineTop + height - params.bottomMargin
                )
                lastLineMaxHeight = height
            }
        }
    }
}

你可能感兴趣的:(android)