7

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>
Aleksandar G
  • 1,163
  • 2
  • 20
  • 25
ono
  • 2,984
  • 9
  • 43
  • 85

3 Answers3

5

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.
natario
  • 24,954
  • 17
  • 88
  • 158
  • this works pretty well. offset is always less than zero, except when completely expanded. so `offset > 0` won't work. i did something like `shouldScroll = appbar.getTotalScrollRange() == Math.abs(offset)`. Not getting stackoverflow so it seems to be working. – ono Jun 13 '16 at 18:42
  • Yes, I forgot about it being negative. You did right. – natario Jun 13 '16 at 18:44
  • 1
    This did not work at all for me. Did anyone ever find a solution? It makes appbarlayout borderline unusable. – Jonathan S. Feb 10 '17 at 20:58
0

Maybe you can add layout_behavior="@string/appbar_scrolling_view_behavior" to your RecylerView like this.

<android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" />
ikhsanudinhakim
  • 1,554
  • 16
  • 23
0

Wrapping the RecyclerView in a FrameLayout solves this problem.

You also need move the appbar_scrolling_view_behavior from the RecyclerView to the FrameLayout so it will be positioned below the AppBarLayout properly.

<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>

    <!-- BEGIN SOLUTION -->
    <!-- the layout behavior needs to be set on the FrameLayout, not the RecyclerView -->
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <!--This RecyclerView MUST be wrapped in a FrameLayout-->
        <!--This prevents the RecyclerView from going behind the AppBarLayout-->
        <android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />

    </FrameLayout>
    <!-- END SOLUTION -->

</android.support.design.widget.CoordinatorLayout>
themichaelscott
  • 496
  • 4
  • 10
  • @faezesaghafi I'm sorry this isn't working for you. I've seen it work successfully several times since I implemented it on multiple projects. Did you move the layout_behavior to the FrameLayout? Also, is the rest of the layout correct with elements in the AppBarLayout? – themichaelscott Mar 08 '19 at 06:18