29

Background

Sometimes, all items of the recyclerView are already visible to the user.

In this case, it wouldn't matter to the user to see overscroll effect, because it's impossible to actually scroll and see more items.

The problem

I know that in order to disable overscroll effect on RecyclerView, I can just use:

recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);

but I can't find out when to trigger this, when the scrolling isn't possible anyway.

The question

How can I Identify that all items are fully visible and that the user can't really scroll ?

If it helps to assume anything, I always use LinearLayoutManager (vertical and horizontal) for the RecyclerView.

android developer
  • 114,585
  • 152
  • 739
  • 1,270

6 Answers6

24
<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:overScrollMode="never"/>

Just add android:overScrollMode="never" in XML

Ketan Ramani
  • 4,874
  • 37
  • 42
19

you could give OVER_SCROLL_IF_CONTENT_SCROLLS a try. Accordingly to the documentation

Allow a user to over-scroll this view only if the content is large enough to meaningfully scroll, provided it is a view that can scroll.

Or you could check if you have enough items to trigger the scroll and enable/disable the over scroll mode, depending on it. Eg

boolean notAllVisible = layoutManager.findLastCompletelyVisibleItemPosition() < adapter.getItemCount() - 1;
if (notAllVisible) {
   recyclerView.setOverScrollMode(allVisible ? View.OVER_SCROLL_NEVER);
}
Blackbelt
  • 156,034
  • 29
  • 297
  • 305
  • The flag is perfect ! Thanks. But just out of curiosity, in which phase exactly should I have used the check of findLastCompletelyVisibleItemPosition ? After all, this won't return a good result as long as nothing is about to be shown... – android developer Dec 29 '16 at 14:48
  • only once. I would have posted a Runnable on the RecyclerView (after calling setLayoutManager and setAdapter), and checked it there. Posting the runnable should give the RecyclerView to lay out its children – Blackbelt Dec 29 '16 at 14:52
  • 7
    For those who wonder who wonder why View.OVER_SCROLL_IF_CONTENT_SCROLLS doesn't work. https://issuetracker.google.com/issues/37076456 – Amaksoft Aug 07 '18 at 15:13
  • 1
    Something very important it to do this check on the next laid out after setting the dataset to your adapter, to ensure that the items are rendered. In Kotlin i would write it like that ```adapter.data = data recyclerView.doOnNextLayout { val areAllItemsVisible = viewManager.findLastCompletelyVisibleItemPosition() == viewAdapter.itemCount - 1 if (areAllItemsVisible) { recyclerView.overScrollMode = View.OVER_SCROLL_NEVER } }``` – Vaios May 24 '19 at 12:08
4

Since android:overScrollMode="ifContentScrolls" is not working for RecyclerView(see https://issuetracker.google.com/issues/37076456) I found some kind of a workaround which want to share with you:

class MyRecyclerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : RecyclerView(context, attrs, defStyleAttr) {

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        val canScrollVertical = computeVerticalScrollRange() > height
        overScrollMode = if (canScrollVertical) OVER_SCROLL_ALWAYS else OVER_SCROLL_NEVER
    }
}
android developer
  • 114,585
  • 152
  • 739
  • 1,270
Nokuap
  • 2,289
  • 2
  • 17
  • 17
  • Can you please share a sample project on Github? Maybe you could also write about it on the issue tracker – android developer Jun 24 '20 at 11:23
  • Also, what about `R.styleable.MyRecyclerView` ? Where's the definition of it? – android developer Jun 25 '20 at 07:38
  • Seems to work well. But is it possible though to avoid modifying the property ? Seems here you've just always handled it, and always modify the property so the original is lost. You also handled only vertically, but forgot about horizontally, and forgot to use R.attr.recyclerViewStyle as defStyleAttr, or just use all CTORs (which is the recommended way to extend safely, as this resource is marked as private). – android developer Jun 28 '20 at 11:19
  • If you wish, I've made a few changes of this code here: https://stackoverflow.com/a/62621912/878126 . It's still a workaround though. I've given you +1 for the effort. – android developer Jun 28 '20 at 11:31
1

You could try something like this:

totalItemCount = linearLayoutManager.getItemCount();
firstVisibleItem = linearLayoutManager.findFirstCompletelyVisibleItemPosition()
lastVisibleItem = linearLayoutManager.findLastCompletelyVisibleItemPosition();

if(firstVisibleItem == 0 && lastVisibleItem -1 == totalItemCount){
    // trigger the overscroll effect
}

Which you could add in the onScrolled() of an OnScrollListener that you add on your RecyclerView.

megaturbo
  • 680
  • 6
  • 24
0

Longer workaround, based on here (to solve this issue), handles more cases, but still a workaround:

/**a temporary workaround to make RecyclerView handle android:overScrollMode="ifContentScrolls"  */
class NoOverScrollWhenNotNeededRecyclerView : RecyclerView {
    private var enableOverflowModeOverriding: Boolean? = null
    private var isOverFlowModeChangingAccordingToSize = false

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    override fun setOverScrollMode(overScrollMode: Int) {
        if (!isOverFlowModeChangingAccordingToSize)
            enableOverflowModeOverriding = overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS
        else isOverFlowModeChangingAccordingToSize = false
        super.setOverScrollMode(overScrollMode)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        if (enableOverflowModeOverriding == null)
            enableOverflowModeOverriding = overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS
        if (enableOverflowModeOverriding == true) {
            val canScrollVertical = computeVerticalScrollRange() > height
            val canScrollHorizontally = computeHorizontalScrollRange() > width
            isOverFlowModeChangingAccordingToSize = true
            overScrollMode = if (canScrollVertical || canScrollHorizontally) OVER_SCROLL_ALWAYS else OVER_SCROLL_NEVER
        }
    }
}
android developer
  • 114,585
  • 152
  • 739
  • 1,270
0

Write custom RecyclerView.OnScrollListener() for working with overScrollMode

/**
 * Workaround because [View.OVER_SCROLL_IF_CONTENT_SCROLLS] not working properly.
 *
 * [showHeader]/[showFooter] - for customization, if need show only specific scroll edge.
 */
class RecyclerOverScrollListener(
    private val showHeader: Boolean = true,
    private val showFooter: Boolean = true
) : RecyclerView.OnScrollListener() {

    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        super.onScrolled(recyclerView, dx, dy)

        val lm = recyclerView.layoutManager as? LinearLayoutManager ?: return

        val isFirstVisible = lm.findFirstCompletelyVisibleItemPosition() == 0
        val isLastVisible = lm.findLastCompletelyVisibleItemPosition() == lm.itemCount - 1
        val allVisible = isFirstVisible && isLastVisible

        recyclerView.overScrollMode = if (allVisible) {
            View.OVER_SCROLL_NEVER
        } else {
            val showHeaderEdge = showHeader && isFirstVisible && !isLastVisible
            val showFooterEdge = showFooter && !isFirstVisible && isLastVisible

            if (showHeader && showFooter || showHeaderEdge || showFooterEdge) {
                View.OVER_SCROLL_ALWAYS
            } else {
                View.OVER_SCROLL_NEVER
            }
        }
    }
}

How implement for RecyclerView:

recyclerView.addOnScrollListener(RecyclerOverScrollListener(showFooter = false))

Also don't forget about specify edge color inside styles:

<item name="android:colorEdgeEffect">@color/yourRippleColor</item>
SerjantArbuz
  • 982
  • 1
  • 12
  • 16