RecyclerView精确计算滚动距离

获取RecyclerView滚动距离的几种方法如下

第一种 computeVerticalScrollOffset方法

var dy = recycler.computeVerticalScrollOffset()

当RecyclerView的每个item高度都相同时,能正确的获取到滚动的距离dy,但是当每个item高度不一致时,获取到的值是不正确的,具体原因可以看computeVerticalScrollOffset方法的源码。

第二种 通过OnScrollListene监听累加

recycler.addOnScrollListener(object : RecyclerView.OnScrollListener(){
            var mDy = 0
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                mDy += dy
            }
        })

这种方法在一般情况下也能正确的获得滚动的距离dy,但是当RecyclerView的item有变多或变少但是不触发OnScrollListener的时候,累加的mDy就会有误差。例如:使用adapter.notifyItemInserted(0)和adapter.notifyItemRemoved(0)方法

第三种 继承LayoutManager自己计算

GridLayoutManager如下

class OffsetGridLayoutManager(
    context: Context?,
    spanCount: Int,
    orientation: Int,
    reverseLayout: Boolean
) : GridLayoutManager(context, spanCount, orientation, reverseLayout) {

    private val heightMap = HashMap()

    override fun onLayoutCompleted(state: RecyclerView.State?) {
        super.onLayoutCompleted(state)
        val firstVisible = findFirstVisibleItemPosition()
        val lastVisible = findLastVisibleItemPosition()
        if (firstVisible >= 0 && lastVisible >= 0 && lastVisible >= firstVisible) {
            for (i in firstVisible until lastVisible) {
                getChildAt(i)?.let {
                    val layoutParams = it.layoutParams as LayoutParams
                    if (layoutParams.spanIndex == 0) { //每行的最左边一个,一行只累加一个高度
                        heightMap[i] = it.height
                    } else {
                        heightMap[i] = 0
                    }
                }
            }
        }
    }

    override fun computeVerticalScrollOffset(state: RecyclerView.State): Int {
        if (childCount == 0) return 0
        return try {
            val firstVisiblePosition = findFirstVisibleItemPosition()
            val viewByPosition = findViewByPosition(firstVisiblePosition)
            var offset = 0
            for (i in 0 until firstVisiblePosition) {
                offset += heightMap[i] ?: 0
            }
            offset -= viewByPosition?.top ?: 0
            offset
        } catch (e: Exception) {
            0
        }
    }
}

重写onLayoutCompleted方法并保存每一个View的高度(一行有多列的时候,只保存第一列view的高度,其他view的高度记录为0,因为不管一行有几列,行的高度都只会有一个view高度)
然后重写computeVerticalScrollOffset方法,计算高度。取得当前第一个可见的view的position,将该view前面所有view的高度累加,再加上该view向上滚动的距离,就是最终的滚动距离

同理LinearLayoutManager的话修改onLayoutCompleted就可以

class OffsetLinearLayoutManager(
    context: Context?,
    attrs: AttributeSet?,
    defStyleAttr: Int,
    defStyleRes: Int
) : LinearLayoutManager(context, attrs, defStyleAttr, defStyleRes) {

    private val heightMap = HashMap()

    override fun onLayoutCompleted(state: RecyclerView.State?) {
        super.onLayoutCompleted(state)
        val firstVisible = findFirstVisibleItemPosition()
        val lastVisible = findLastVisibleItemPosition()
        if (firstVisible >= 0 && lastVisible >= 0 && lastVisible >= firstVisible) {
            for (i in firstVisible until lastVisible) {
                getChildAt(i)?.let {
                    heightMap[i] = it.height
                }
            }
        }
    }

    override fun computeVerticalScrollOffset(state: RecyclerView.State): Int {
        if (childCount == 0) return 0
        return try {
            val firstVisiblePosition = findFirstVisibleItemPosition()
            val viewByPosition = findViewByPosition(firstVisiblePosition)
            var offset = 0
            for (i in 0 until firstVisiblePosition) {
                offset += heightMap[i] ?: 0
            }
            offset -= viewByPosition?.top ?: 0
            offset
        } catch (e: Exception) {
            0
        }
    }
}

你可能感兴趣的:(RecyclerView精确计算滚动距离)