Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView LinearLayoutManager computeVerticalScrollOffset() not returning correct value

I am working with a RecyclerView and a corresponding LinearLayoutManager. I was adding some custom scrolling features to part of my app where I would translate a custom header object along with the scroll of the RecyclerView modeled after this project: https://github.com/boxme/ParallaxHeaderViewPager (which uses ListView instead of RecyclerView).

I ran into a weird issue though. It would scroll just fine for a while but then it would jump a few hundred pixels. I added log statements to see the offset computed by LinearLayoutManager.computeVerticalScrollOffset() as I scrolled as sure enough, the offset was randomly jumping from like 320 to 1200 then it would continue computing the offset appropriately from that point.

See my answer below on how I solved this!

like image 222
rakoonise Avatar asked May 20 '15 22:05

rakoonise


2 Answers

Thanks to this thread finally found a way to resolve this accurately, posting below the code for the overriden LinearLayoutManager, sorry its kotlin though :(, also you can read about this in more detail here

class LinearLayoutManagerWithAccurateOffset(context: Context?) : LinearLayoutManager(context) {

    // map of child adapter position to its height.
    private val childSizesMap = mutableMapOf<Int, Int>()

    override fun onLayoutCompleted(state: RecyclerView.State?) {
        super.onLayoutCompleted(state)
        for (i in 0 until childCount) {
            val child = getChildAt(i)
            childSizesMap[getPosition(child)] = child.height
        }
    }

    override fun computeVerticalScrollOffset(state: RecyclerView.State?): Int {
        if (childCount == 0) {
            return 0
        }
        val firstChildPosition = findFirstVisibleItemPosition()
        val firstChild = findViewByPosition(firstChildPosition)
        var scrolledY: Int = -firstChild.y.toInt()
        for (i in 0 until firstChildPosition) {
            scrolledY += childSizesMap[i] ?: 0
        }
        return scrolledY
    }

}

UPDATE

Be aware that this does not support saving state, so onConfigurationChange/recovering from process death, the state will get out of sync and the scrolling will look weird, the way to fix this is to store the childSizesMap in bundle by overriding onSaveInstanceState

like image 78
Bhargav Avatar answered Oct 16 '22 10:10

Bhargav


The issue ended up being the fact that I had a very large invisible item in my list followed by a bunch of smaller items. It turns out that LinearLayoutManager.computeVerticalScrollOffset() takes average row height into account when calculating. This caused an issue for me since that large item at the top was throwing off the average height of the rows. I ended up solving this by adding a few smaller invisible items on top instead of one large one to keep the average row height accurate.

I hope this helps anyone who faces a similar issue!

like image 33
rakoonise Avatar answered Oct 16 '22 09:10

rakoonise