Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expand appbarlayout when recyclerview is scrolled/fling to top

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

like image 284
momoja Avatar asked Nov 08 '15 18:11

momoja


2 Answers

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);
                    }
                }
            }
});
like image 68
kazhiu Avatar answered Sep 30 '22 16:09

kazhiu


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);
}
like image 27
PPartisan Avatar answered Sep 30 '22 18:09

PPartisan