FlowLayoutManager 流式布局

RecycleView的流式布局管理器

import android.graphics.Rect
import android.util.Log
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class FlowLayoutManager : RecyclerView.LayoutManager() {

    companion object {
        const val TAG = "FlowLayoutManager2"
    }

    var widthFlow = 0
    var heightFlow = 0

    private var left = 0
    private var top = 0
    private var right = 0

    private var useMaxWidth = 0
    private var verticalScrollOffset = 0

    var totalHeight = 0
        private set
    private var row = Row()
    private val lineRows: MutableList<Row> = mutableListOf()

    private val allItemFrames = SparseArray<Rect>()

    override fun isAutoMeasureEnabled(): Boolean {
        return true
    }

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

    override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {
        Log.d(TAG, "onLayoutChildren")
        totalHeight = 0
        var cuLineTop = top
        //当前行使用的高度
        var cuLineWidth = 0
        var itemLeft: Int
        var itemTop: Int
        var maxHeightItem = 0
        row = Row()
        lineRows.clear()
        allItemFrames.clear()
        removeAllViews()
        if (itemCount == 0) {
            recycler?.let {
                detachAndScrapAttachedViews(it)
            }
            verticalScrollOffset = 0
            return
        }
        if (childCount == 0 && state?.isPreLayout == true) {
            return
        }

        //onLayoutChildren方法在RecyclerView 初始化时 会执行两遍
        recycler?.let {
            detachAndScrapAttachedViews(it)
        }
        if (childCount == 0) {
            widthFlow = width
            heightFlow = height
            left = paddingLeft
            right = paddingRight
            top = paddingTop
            useMaxWidth = widthFlow - left - right
        }

        for (i in 0 until itemCount) {
            Log.d(TAG, "index:$i")
            val childAt = recycler?.getViewForPosition(i) ?: continue
            if (View.GONE == childAt.visibility) {
                continue
            }
            measureChildWithMargins(childAt, 0, 0)
            val childWidth = getDecoratedMeasuredWidth(childAt)
            val childHeight = getDecoratedMeasuredHeight(childAt)
            if ((cuLineWidth + childWidth) <= useMaxWidth) {
                itemLeft = left + cuLineWidth
                itemTop = cuLineTop
                val frame = allItemFrames.get(i) ?: Rect()
                frame.set(itemLeft, itemTop, itemLeft + childWidth, itemTop + childHeight)
                allItemFrames.put(i, frame)
                cuLineWidth += childWidth
                maxHeightItem = maxHeightItem.coerceAtLeast(childHeight)
                row.views.add(Item(childHeight, childAt, frame))
                row.cuTop = cuLineTop
                row.maxHeight = maxHeightItem
            } else {
                // 换行
                formatAboveRow()
                cuLineTop += maxHeightItem
                totalHeight += maxHeightItem
                itemTop = cuLineTop
                itemLeft = left
                val frame = allItemFrames.get(i) ?: Rect()
                frame.set(itemLeft, itemTop, itemLeft + childWidth, itemTop + childHeight)
                allItemFrames.put(i, frame)
                cuLineWidth = childWidth
                maxHeightItem = childHeight
                row.views.add(Item(childHeight, childAt, frame))
                row.cuTop = cuLineTop
                row.maxHeight = maxHeightItem
            }
            //不要忘了最后一行进行刷新下布局
            if (i == itemCount - 1) {
                formatAboveRow()
                totalHeight += maxHeightItem
            }
        }
        totalHeight = totalHeight.coerceAtLeast(getVerticalSpace())
        Log.d(TAG, "onLayoutChildren totalHeight:$totalHeight")
        fillLayout(state)
    }

    private fun fillLayout(state: RecyclerView.State?) {
        if (state?.isPreLayout == true || itemCount == 0) {
            // 跳过preLayout,preLayout主要用于支持动画
            return
        }
        //对所有的行信息进行遍历
        for (j in 0 until lineRows.size) {
            val row = lineRows[j]
            val views = row.views
            for (i in 0 until views.size) {
                val scrap = views[i].view
                measureChildWithMargins(scrap, 0, 0)
                addView(scrap)
                val frame = views[i].rect
                //将这个item布局出来
                layoutDecoratedWithMargins(
                    scrap,
                    frame.left ,
                    frame.top - verticalScrollOffset,
                    frame.right ,
                    frame.bottom - verticalScrollOffset
                )
            }
        }
    }

    private fun formatAboveRow() {
        var lineNeedWidth = 0
        val views = row.views
        for (i in 0 until views.size) {
            //计算行高居中
            val item = views[i]
            val view = item.view
            val position = getPosition(view)
            if (allItemFrames[position].top < row.cuTop + (row.maxHeight - item.useHeight) / 2) {
                val frame = allItemFrames[position] ?: Rect()
                frame.set(
                    allItemFrames[position].left,
                    row.cuTop + (row.maxHeight - item.useHeight) / 2,
                    allItemFrames[position].right,
                    row.cuTop + (row.maxHeight - item.useHeight) / 2 + getDecoratedMeasuredHeight(view)
                )
                allItemFrames.put(position,frame)
                item.rect = frame
                views[i] = item
            }
            //计算行宽居中
            lineNeedWidth += (item.rect.right - item.rect.left)
        }
        val off = (useMaxWidth - lineNeedWidth) / 2
        for (item in views) {
            item.rect.left += off
            item.rect.right += off
        }
        lineRows.add(row)
        row = Row()
    }

    override fun canScrollVertically(): Boolean {
        return true
    }

    override fun scrollVerticallyBy(
        dy: Int,
        recycler: RecyclerView.Recycler?,
        state: RecyclerView.State?
    ): Int {
        return super.scrollVerticallyBy(dy, recycler, state)
    }

    private fun getVerticalSpace(): Int {
        return height - paddingBottom - paddingTop
    }

    //行信息的定义
    class Row(val views: MutableList<Item> = mutableListOf()) {
        //每一行的头部坐标
        var cuTop: Int = 0
        //每一行需要占据的最大高度
        var maxHeight: Int = 0
    }

    //每个item的定义
    class Item(val useHeight: Int, val view: View, var rect: Rect)
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RecyclerView本身并不支持布局,但是可以通过自定义LayoutManager来实现布局。下面是一个简单的实现步骤: 1. 创建一个继承自RecyclerView.LayoutManager的自定义LayoutManager。 2. 在自定义LayoutManager中重写onLayoutChildren方法,实现布局的摆放。 3. 在onMeasure方法中计算RecyclerView的宽高。 4. 在RecyclerView.Adapter中根据需要调整item的宽高,以适配布局。 以下是一个简单的示例代码: ```java public class FlowLayoutManager extends RecyclerView.LayoutManager { private int mTotalHeight; @Override public RecyclerView.LayoutParams generateDefaultLayoutParams() { return new RecyclerView.LayoutParams( RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT); } @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { if (state.getItemCount() == 0) { return; } detachAndScrapAttachedViews(recycler); int offsetX = getPaddingLeft(); int offsetY = getPaddingTop(); int lineMaxHeight = 0; int width = getWidth(); for (int i = 0; i < getItemCount(); i++) { View child = recycler.getViewForPosition(i); addView(child); measureChildWithMargins(child, 0, 0); int childWidth = getDecoratedMeasuredWidth(child); int childHeight = getDecoratedMeasuredHeight(child); if (offsetX + childWidth > width - getPaddingRight()) { offsetX = getPaddingLeft(); offsetY += lineMaxHeight; lineMaxHeight = 0; } layoutDecorated(child, offsetX, offsetY, offsetX + childWidth, offsetY + childHeight); offsetX += childWidth; lineMaxHeight = Math.max(lineMaxHeight, childHeight); } mTotalHeight = offsetY + lineMaxHeight + getPaddingBottom(); } @Override public boolean canScrollVertically() { return true; } @Override public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (getChildCount() == 0 || dy == 0) { return 0; } int scrolled = 0; if (dy > 0) { View lastChild = getChildAt(getChildCount() - 1); int lastVisiblePos = getPosition(lastChild); if (lastVisiblePos == getItemCount() - 1 && lastChild.getBottom() - dy < getHeight() - getPaddingBottom()) { dy = lastChild.getBottom() - getHeight() + getPaddingBottom(); } } else { View firstChild = getChildAt(0); int firstVisiblePos = getPosition(firstChild); if (firstVisiblePos == 0 && firstChild.getTop() - dy > getPaddingTop()) { dy = firstChild.getTop() - getPaddingTop(); } } offsetChildrenVertical(-dy); scrolled += dy; fill(recycler); return scrolled; } private void fill(RecyclerView.Recycler recycler) { int topOffset = getPaddingTop(); int leftOffset = getPaddingLeft(); int lineMaxHeight = 0; int width = getWidth(); for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); int childWidth = getDecoratedMeasuredWidth(child); int childHeight = getDecoratedMeasuredHeight(child); if (leftOffset + childWidth > width - getPaddingRight()) { leftOffset = getPaddingLeft(); topOffset += lineMaxHeight; lineMaxHeight = 0; } layoutDecorated(child, leftOffset, topOffset, leftOffset + childWidth, topOffset + childHeight); leftOffset += childWidth; lineMaxHeight = Math.max(lineMaxHeight, childHeight); } detachAndScrapAttachedViews(recycler); } @Override public int computeVerticalScrollOffset(RecyclerView.State state) { return computeVerticalScrollExtent(state); } @Override public int computeVerticalScrollExtent(RecyclerView.State state) { return getHeight() - getPaddingTop() - getPaddingBottom(); } @Override public int computeVerticalScrollRange(RecyclerView.State state) { return mTotalHeight; } } ``` 使用时只需要将RecyclerView的LayoutManager设置为自定义的FlowLayoutManager即可实现布局

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值