Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flinging with RecyclerView + AppBarLayout

I am using the new CoordinatorLayout with AppBarLayout and CollapsingToolbarLayout. Below AppBarLayout, I have a RecyclerView with a list of content.

I have verified that fling scrolling works on the RecyclerView when I am scrolling up and down the list. However, I would also like the AppBarLayout to smoothly scroll during expansion.

When scrolling up to expand the CollaspingToolbarLayout, scrolling immediately stops once lifting your finger off the screen. If you scroll up in a quick motion, sometimes the CollapsingToolbarLayout re-collapses as well. This behavior with the RecyclerView seems to function much differently than when using a NestedScrollView.

I've tried to set different scroll properties on the recyclerview but I haven't been able to figure this out.

Here is a video showing some of the scrolling issues. https://youtu.be/xMLKoJOsTAM

Here is an example showing the issue with the RecyclerView (CheeseDetailActivity). https://github.com/tylerjroach/cheesesquare

Here is the original example that uses a NestedScrollView from Chris Banes. https://github.com/chrisbanes/cheesesquare

like image 258
tylerjroach Avatar asked Sep 30 '22 12:09

tylerjroach


2 Answers

The answer of Kirill Boyarshinov was almost correct.

The main problem is that the RecyclerView sometimes is giving incorrect fling direction, so if you add the following code to his answer it works correctly:

public final class FlingBehavior extends AppBarLayout.Behavior {
    private static final int TOP_CHILD_FLING_THRESHOLD = 3;
    private boolean isPositive;

    public FlingBehavior() {
    }

    public FlingBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (velocityY > 0 && !isPositive || velocityY < 0 && isPositive) {
            velocityY = velocityY * -1;
        }
        if (target instanceof RecyclerView && velocityY < 0) {
            final RecyclerView recyclerView = (RecyclerView) target;
            final View firstChild = recyclerView.getChildAt(0);
            final int childAdapterPosition = recyclerView.getChildAdapterPosition(firstChild);
            consumed = childAdapterPosition > TOP_CHILD_FLING_THRESHOLD;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        isPositive = dy > 0;
    }
}

I hope that this helps.

like image 115
Manolo Garcia Avatar answered Oct 07 '22 06:10

Manolo Garcia


Seems that v23 update did not fix it yet.

I have found sort of of hack to fix it with flinging down. The trick is to reconsume fling event if ScrollingView's top child is close to the beginning of data in Adapter.

public final class FlingBehavior extends AppBarLayout.Behavior {

    public FlingBehavior() {
    }

    public FlingBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
        if (target instanceof ScrollingView) {
            final ScrollingView scrollingView = (ScrollingView) target;
            consumed = velocityY > 0 || scrollingView.computeVerticalScrollOffset() > 0;
        }
        return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
    }
}

Use it in your layout like that:

 <android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_behavior="your.package.FlingBehavior">
    <!--your views here-->
 </android.support.design.widget.AppBarLayout>

EDIT: Fling event reconsuming is now based on verticalScrollOffset instead of amount of items on from top of RecyclerView.

EDIT2: Check target as ScrollingView interface instance instead of RecyclerView. Both RecyclerView and NestedScrollingView implement it.

like image 69
Kirill Boyarshinov Avatar answered Oct 07 '22 05:10

Kirill Boyarshinov