11

I implemented a collapsingtoolbar layout with a recyclerview as shown in the sample code attached. My issue is that, when I fling the list downward, it does not go all the way to the top.

What happens is that, the scrolling stops right at the point where the AppBarLayout is supposed to end.

The effect that I want is upon flinging the list downward, the list will go all the way to the top AND reveal/expand the AppBarLayout

My minSdk is 14. Any help or suggestion is greatly appreciated.

<?xml version="1.0" encoding="utf-8"?>

<android.support.design.widget.AppBarLayout>

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

        <LinearLayout
            app:layout_collapseMode="parallax">

            //some elements

        </LinearLayout>

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

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

<android.support.v7.widget.RecyclerView
    app:layout_behavior="@string/appbar_scrolling_view_behavior"/> //value android.support.design.widget.AppBarLayout$ScrollingViewBehavior

<android.support.v7.widget.Toolbar
    app:popupTheme="@style/AppTheme.PopupOverlay"
    app:layout_collapseMode="parallax" />

momoja
  • 938
  • 9
  • 23
  • Are you using CoordinatorLayout? – Nedko Nov 08 '15 at 19:32
  • Yes the main parent is a coordinator – momoja Nov 09 '15 at 00:50
  • do you have any solutions now, momoja? – Wayne Dec 03 '15 at 09:37
  • @Wayne Yes, I just put a scroll listener on my recycler view and call appBarLayout.setExpanded when I reach the threshold I want – momoja Dec 04 '15 at 07:54
  • I faced a similar issue but my requirements were simpler so it probably won't work in your case - I just wanted the toolbar to appear on top of all the content regardless whether the user flings or scrolls. In the end I ditched the coordinator layout and used a traditional ScrollView > Relative Layout > Toolbar + main content below Toolbar. Simple but works for me. – Kevin Lee Jan 25 '16 at 05:23

2 Answers2

14

I had similar problem and I used a simple trick to expand AppBarLayout when RecyclerView fling to top (you need to have support library >= 23.x.x)

mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    int firstVisiblePosition = linearLayoutManager.findFirstCompletelyVisibleItemPosition();
                    if (firstVisiblePosition == 0) {
                        mAppBarLayout.setExpanded(true, true);
                    }
                }
            }
});
kazhiu
  • 749
  • 9
  • 21
9

You can fully expand or collapse the App Bar with the setExpanded() method. One implementation could involve overriding dispatchTouchEvent() in your Activity class, and auto-collapsing/expanding your App Bar based on whether it is collapsed past the halfway point:

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_UP) {
        float per = Math.abs(mAppBarLayout.getY()) / mAppBarLayout.getTotalScrollRange();
        boolean setExpanded = (per <= 0.5F);
        mAppBarLayout.setExpanded(setExpanded, true);
    }
    return super.dispatchTouchEvent(event);
}

In respect to automatically scrolling to the last position on a fling, I have put some code on GitHub that shows how to programmatically smooth scroll to a specific location that may help. Calling a scroll to list.size() - 1 on a fling for instance could replicate the behaviour. Parts of this code by the way are adapted from the StylingAndroid and Novoda blogs:

public class RecyclerLayoutManager extends LinearLayoutManager {

    private AppBarManager mAppBarManager;
    private int visibleHeightForRecyclerView;

    public RecyclerLayoutManager(Context context) {
        super(context);
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        View firstVisibleChild = recyclerView.getChildAt(0);
        final int childHeight = firstVisibleChild.getHeight();
        int distanceInPixels = ((findFirstVisibleItemPosition() - position) * childHeight);
        if (distanceInPixels == 0) {
            distanceInPixels = (int) Math.abs(firstVisibleChild.getY());
        }
        //Called Once
        if (visibleHeightForRecyclerView == 0) {
            visibleHeightForRecyclerView = mAppBarManager.getVisibleHeightForRecyclerViewInPx();
        }
        //Subtract one as adapter position 0 based
        final int visibleChildCount = visibleHeightForRecyclerView/childHeight - 1;

        if (position <= visibleChildCount) {
            //Scroll to the very top and expand the app bar
            position = 0;
            mAppBarManager.expandAppBar();
        } else {
            mAppBarManager.collapseAppBar();
        }

        SmoothScroller smoothScroller = new SmoothScroller(recyclerView.getContext(), Math.abs(distanceInPixels), 1000);
        smoothScroller.setTargetPosition(position);
        startSmoothScroll(smoothScroller);
    }

    public void setAppBarManager(AppBarManager appBarManager) {
        mAppBarManager = appBarManager;
    }

    private class SmoothScroller extends LinearSmoothScroller {
        private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;
        private final float distanceInPixels;
        private final float duration;

        public SmoothScroller(Context context, int distanceInPixels, int duration) {
            super(context);
            this.distanceInPixels = distanceInPixels;
            float millisecondsPerPx = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());
            this.duration = distanceInPixels < TARGET_SEEK_SCROLL_DISTANCE_PX ?
                    (int) (Math.abs(distanceInPixels) * millisecondsPerPx) : duration;
        }

        @Override
        public PointF computeScrollVectorForPosition(int targetPosition) {
            return RecyclerLayoutManager.this
                    .computeScrollVectorForPosition(targetPosition);
        }

        @Override
        protected int calculateTimeForScrolling(int dx) {
            float proportion = (float) dx / distanceInPixels;
            return (int) (duration * proportion);
        }
    }
}

Edit:

AppBarManager in the above code snippet refers to an interface used to communicate with the AppBarLayout in an Activity. Collapse/expand app bar methods do just that, with animations. The final method is used to calculate the number of RecyclerView rows visible on screen:

AppBarManager.java

public interface AppBarManager {

    void collapseAppBar();
    void expandAppBar();
    int getVisibleHeightForRecyclerViewInPx();

}

MainActivity.java

public class MainActivity extends AppCompatActivity implements AppBarManager{

@Override
public void collapseAppBar() {
    mAppBarLayout.setExpanded(false, true);
}

@Override
public void expandAppBar() {
    mAppBarLayout.setExpanded(true, true);
}

@Override
public int getVisibleHeightForRecyclerViewInPx() {

    if (mRecyclerFragment == null) mRecyclerFragment =
            (RecyclerFragment) getSupportFragmentManager().findFragmentByTag(RecyclerFragment.TAG);

    int windowHeight, appBarHeight, headerViewHeight;
    windowHeight = getWindow().getDecorView().getHeight();
    appBarHeight = mAppBarLayout.getHeight();
    headerViewHeight = mRecyclerFragment.getHeaderView().getHeight();
    return windowHeight - (appBarHeight + headerViewHeight);
}
PPartisan
  • 8,173
  • 4
  • 29
  • 48
  • 1
    Hi, isn't the setexpanded method only available on api 23? – momoja Nov 09 '15 at 00:50
  • @momoja It's part of the design support library, so no. You may need v23 of said library, which may also mean your `targetSdkVersion`/`compileSdkVersion` needs to be 23, but you can use the library on lower API devices. – PPartisan Nov 09 '15 at 10:35
  • what is `AppBarManager` here ? – Ajay S Jan 05 '16 at 09:56
  • @TGMCians `AppBarManager` is an `interface` primarily used to call the expand/collapse animations on the `AppBarLayout` in `MainActivity` - I've added the relevant code to my answer. – PPartisan Jan 05 '16 at 10:13
  • @PPartisan Thanks .. can you let me know what is `RecyclerFragment` – Ajay S Jan 05 '16 at 10:24
  • @TGMCians Just the name I gave to the fragment that hosts the `RecyclerView` - in this case I only use it to get a `View` that sits on top of the `RecyclerView` so I can factor in its height when determining how much space the `RecyclerView` has. It probably wont be applicable for most people. You can view the code for the class [here](https://github.com/PPartisan/ScrollHandleExample/blob/master/app/src/main/java/com/werdpressed/partisan/scrollhandleexample/RecyclerFragment.java) – PPartisan Jan 05 '16 at 10:28
  • @PPartisan I added your complete code but `smoothScrollToPosition` never executes... what is the reason ?? I have set this `RecyclerLayoutManager recyclerLayoutManager = new RecyclerLayoutManager(getActivity()); mListViewOffers.setLayoutManager(recyclerLayoutManager);` – Ajay S Jan 05 '16 at 12:22
  • @TGMCians You need to explicitly call `smoothScrollToPosition(int target)`, so to scroll to the end you would call (assuming `mListViewOffers` is a `RecyclerView`) - `mListViewOffers.smoothScrollToPosition(adapter.getCount() - 1);`. My answer suggests calling this in response to a fling gesture (i.e. inside a [`GestureDetector`](http://developer.android.com/training/gestures/detector.html) – PPartisan Jan 05 '16 at 12:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/99816/discussion-between-tgmcians-and-ppartisan). – Ajay S Jan 05 '16 at 12:47
  • @PPartisan chat is not working for me now.. can you please answer of that question – Ajay S Jan 05 '16 at 12:56
  • @TGMCians Ah, OK. Well I posted an answer for that question that's hopefully what you were after! – PPartisan Jan 05 '16 at 14:18