RecyclerView實現豎向無限循環滾動的列表

通過重寫RecyclerView.LayoutManager實現

直接使用該LayoutManger即可

package com.example.testrecy.test

import android.util.Log
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler

class MyLayoutManager : RecyclerView.LayoutManager() {
    private var looperEnable = true

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

    override fun canScrollHorizontally(): Boolean {
        return false
    }

    override fun canScrollVertically(): Boolean {
        return true
    }

    override fun onLayoutChildren(
        recycler: Recycler,
        state: RecyclerView.State
    ) {
        if (itemCount <= 0) {
            return
        }
        //preLayout主要支持動畫,直接跳過
        if (state.isPreLayout) {
            return
        }
        //將視圖分離放入scrap緩存中,以準備重新對view進行排版
        detachAndScrapAttachedViews(recycler)
        var actualHeight = 0
        for (i in 0 until itemCount) {
            //初始化,將在屏幕內的view填充
            val itemView = recycler.getViewForPosition(i)
            addView(itemView)
            //測量itemView的寬高
            measureChildWithMargins(itemView, 0, 0)
            val width = getDecoratedMeasuredWidth(itemView)
            val height = getDecoratedMeasuredHeight(itemView)
            //根據itemView的寬高進行佈局
            layoutDecorated(itemView, 0, actualHeight, width, actualHeight + height)
            actualHeight += height
            //如果當前佈局過的itemView的寬度總和大於RecyclerView的寬,則不再進行佈局
            if (actualHeight > getHeight()) {
                break
            }
        }
    }

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

        //1.上下滑動的時候,填充子view
        val travel = fill(dy, recycler)
        if (travel == 0) {
            return 0
        }

        //2.滾動
        offsetChildrenVertical(travel * -1)

        //3.回收已經離開界面的
        recyclerHideView(dy, recycler)
        return travel
    }

    /**
     * 左右滑動的時候,填充
     */
    private fun fill(dy: Int, recycler: Recycler): Int {
        var translateY = dy
        if (translateY > 0) {
            //標註1.向上滾動
            val lastView = getChildAt(childCount - 1) ?: return 0
            val lastPos = getPosition(lastView)
            //標註2.可見的最後一個itemView完全滑進來了,需要補充新的
            if (lastView.bottom < height) {
                var scrap: View? = null
                //標註3.判斷可見的最後一個itemView的索引,
                // 如果是最後一個,則將下一個itemView設置爲第一個,否則設置爲當前索引的下一個
                if (lastPos == itemCount - 1) {
                    if (looperEnable) {
                        scrap = recycler.getViewForPosition(0)
                    } else {
                        translateY = 0
                    }
                } else {
                    scrap = recycler.getViewForPosition(lastPos + 1)
                }
                if (scrap == null) {
                    return translateY
                }
                //標註4.將新的itemView add進來並對其測量和佈局
                addView(scrap)
                measureChildWithMargins(scrap, 0, 0)
                val width = getDecoratedMeasuredWidth(scrap)
                val height = getDecoratedMeasuredHeight(scrap)
                layoutDecorated(
                    scrap, 0, lastView.bottom,
                    width, lastView.bottom + height
                )
                return translateY
            }
        } else {
            //向下滾動
            val firstView = getChildAt(0) ?: return 0
            val firstPos = getPosition(firstView)
            if (firstView.top >= 0) {
                var scrap: View? = null
                if (firstPos == 0) {
                    if (looperEnable) {
                        scrap = recycler.getViewForPosition(itemCount - 1)
                    } else {
                        translateY = 0
                    }
                } else {
                    scrap = recycler.getViewForPosition(firstPos - 1)
                }
                if (scrap == null) {
                    return 0
                }
                addView(scrap, 0)
                measureChildWithMargins(scrap, 0, 0)
                val width = getDecoratedMeasuredWidth(scrap)
                val height = getDecoratedMeasuredHeight(scrap)
                layoutDecorated(
                    scrap, 0, firstView.top - height,
                    width, firstView.top
                )
            }
        }
        return translateY
    }

    /**
     * 回收界面不可見的view
     */
    private fun recyclerHideView(
        dy: Int,
        recycler: Recycler
    ) {
        for (i in 0 until childCount) {
            val view = getChildAt(i) ?: continue
            if (dy > 0) {
                //向上滾動,移除一個上邊不在內容裏的view
                if (view.bottom < 0) {
                    removeAndRecycleView(view, recycler)
                    Log.d(
                        TAG,
                        "循環: 移除 一個view  childCount=$childCount"
                    )
                }
            } else {
                //向下滾動,移除一個下邊不在內容裏的view
                if (view.top > height) {
                    removeAndRecycleView(view, recycler)
                    Log.d(
                        TAG,
                        "循環: 移除 一個view  childCount=$childCount"
                    )
                }
            }
        }
    }

    fun setLooperEnable(looperEnable: Boolean) {
        this.looperEnable = looperEnable
    }

    companion object {
        private const val TAG = "MyLayoutManager"
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章