Search code examples
androidandroid-support-libraryandroid-coordinatorlayoutandroid-design-libraryandroid-appbarlayout

Prevent RecyclerView from scrolling under AppBarLayout before AppBarLayout is collapsed


I'm creating a RecyclerView with header where the header collapses as you scroll up the RecyclerView. I can achieve this very closely with the layout below, with a transparent AppBarLayout, and MyCoolView which is the header. The parallax effect works great.

However, if the header is still visible and I fling the RecyclerView, the RV scrolls slowly to the top and some of the items are under the Toolbar until the RV reaches the top of the view. I've been playing around with the scrollFlags but haven't achieved a desirable result. Any suggestions on how to improve the fling experience so the items don't get clipped?

View the video and watch when its flinged --- https://www.dropbox.com/s/jppd6m7zo41k23z/20160609_151309.mp4?dl=0

<android.support.design.widget.CoordinatorLayout>

     <android.support.design.widget.AppBarLayout
         android:background="#00000000">

         <android.support.design.widget.CollapsingToolbarLayout
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

             <com.android.myapp.MyCoolView
                app:layout_collapseMode="parallax"/>

         </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView/>

</android.support.design.widget.CoordinatorLayout>

Solution

  • Possible solution (untested). Add an OnOffsetChangedListener to your AppBarLayout, and keep note of the offset value. First, declare this field:

    private boolean shouldScroll = false;
    

    Then, onCreate:

    AppBarLayout appbar = findViewById(...);
    appbar.addOnOffsetChangedListener(new OnOffsetChangedListener() {
        @Override
        void onOffsetChanged(AppBarLayout appbar, int offset) {
            // Allow recycler scrolling only if we started collapsing.
            this.shouldScroll = offset != 0;
        }
    });
    

    Now, add a scroll listener to your RecyclerView. Whenever it tries to scroll, revert the scroll if the AppBarLayout is still expanded:

    RecyclerView recycler = findViewById(...);
    recycler.addOnScrollListener(new OnScrollListener() {
        @Override
        void onScrolled(RecyclerView recycler, int dx, int dy) {
            // If AppBar is fully expanded, revert the scroll.
            if (!shouldScroll) {
                recycler.scrollTo(0,0);
            }
        }
    });
    

    This might need some tweaking though. I see two issues:

    • Possible stack overflow if scrollTo() calls onScrolled() back. Can be solved with a boolean or by removing/adding the scroll listener
    • Possibly you want to prevent scrolling not only when AppBarLayout is fully expanded, but more generally when AppBarLayout is not collapsed. This means you don’t have to check for offset != 0, but rather for offset == appBarLayout.getTotalScrollRange(). I think.