airbnb / epoxy

Epoxy is an Android library for building complex screens in a RecyclerView
https://goo.gl/eIK82p
Apache License 2.0
8.5k stars 733 forks source link

Incorrect handling of findLastCompletelyVisibleItemPosition() in StickyHeaderLinearLayoutManager #1008

Open slmlt opened 4 years ago

slmlt commented 4 years ago

When findLastCompletelyVisibleItemPosition() is called on StickyHeaderLinearLayoutManager it always returns the position of current sticky header view if one is visible. This behavior breaks any logic built around detecting last displayed items, most common example being list pagination.

The root of the problem seems to be in:

private fun createStickyHeader(recycler: RecyclerView.Recycler, position: Int) {
    ...
    addView(stickyHeader)
    ...
}

stickyHeader is added to LM without providing index, in which case LM uses mChildHelper.getChildCount(). Later, when LM is cycling through children in reverse order to find last visible item, sticky header view comes up first since its index is getChildCount(). Providing a 0 index to addView() probably is not an option becauses it will break findFirstCompletelyVisibleItemPosition().

It seems that adding an override of findLastCompletelyVisibleItemPosition() to StickyHeaderLinearLayoutManager is needed to fix its behavior.

dmytroivanovv commented 3 years ago

Here is the temporary solution to the problem:

  1. Copy layout manager to your project StickyHeaderLinearLayoutManager (I do not know why it is not open for the inheritance)
  2. Override methods like findXXXVisibleItemPosition as I did below.
class CustomStickyHeaderLinearLayoutManager @JvmOverloads constructor(
    context: Context,
    orientation: Int = RecyclerView.VERTICAL,
    reverseLayout: Boolean = false
) : LinearLayoutManager(context, orientation, reverseLayout) {

    override fun findLastVisibleItemPosition(): Int {
        return restoreView {
            super.findLastVisibleItemPosition()
        }
    }

    override fun findFirstVisibleItemPosition(): Int {
        return restoreView {
            super.findFirstVisibleItemPosition()
        }
    }

    override fun findFirstCompletelyVisibleItemPosition(): Int {
        return restoreView {
            super.findFirstCompletelyVisibleItemPosition()
        }
    }

    override fun findLastCompletelyVisibleItemPosition(): Int {
        return restoreView {
            super.findLastCompletelyVisibleItemPosition()
        }
    }

   /* other methods from copied class */
}
slmlt commented 3 years ago

@dmytroivanovv Thanks, I have completely missed the restoreView method. Reattaching a view is kinda hacky though :)