Skip to content

Instantly share code, notes, and snippets.

@mankum93
Last active December 10, 2017 11:12
Show Gist options
  • Save mankum93/1e4deedf647567ff9baa2263cda8ac54 to your computer and use it in GitHub Desktop.
Save mankum93/1e4deedf647567ff9baa2263cda8ac54 to your computer and use it in GitHub Desktop.
/**
* This Behavior corrects the bug in RecyclerView that doesn't report scroll consumption
* properly by plugging in a scroll listener to RecyclerView that properly reports the
* same(on reaching the top)
*
* This is courtesy of @mak-sing on SO:(top rated answer is FLAWED!)
* https://stackoverflow.com/questions/30923889/flinging-with-recyclerview-appbarlayout
*/
public final class CorrectiveAppBarLayoutFlingBehavior extends AppBarLayout.Behavior {
private Map<RecyclerView, RecyclerViewScrollListener> scrollListenerMap = new HashMap<>(); //keep scroll listener map, the custom scroll listener also keep the current scroll Y position.
public CorrectiveAppBarLayoutFlingBehavior() {
}
public CorrectiveAppBarLayoutFlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
*
* @param coordinatorLayout
* @param child The child that attached the behavior (AppBarLayout)
* @param target The scrolling target e.g. a recyclerView or NestedScrollView
* @param velocityX
* @param velocityY
* @param consumed The fling should be consumed by the scrolling target or not
* @return
*/
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, float velocityX, float velocityY, boolean consumed) {
if (target instanceof RecyclerView) {
final RecyclerView recyclerView = (RecyclerView) target;
if (scrollListenerMap.get(recyclerView) == null) {
RecyclerViewScrollListener recyclerViewScrollListener = new RecyclerViewScrollListener(coordinatorLayout, child, this);
scrollListenerMap.put(recyclerView, recyclerViewScrollListener);
recyclerView.addOnScrollListener(recyclerViewScrollListener);
}
scrollListenerMap.get(recyclerView).setVelocity(velocityY);
consumed = scrollListenerMap.get(recyclerView).getScrolledY() > 0; //recyclerView only consume the fling when it's not scrolled to the top
}
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);
}
private static class RecyclerViewScrollListener extends RecyclerView.OnScrollListener {
private int scrolledY;
private int scrollState;
private float velocity;
private WeakReference<CoordinatorLayout> coordinatorLayoutRef;
private WeakReference<AppBarLayout> childRef;
private WeakReference<CorrectiveAppBarLayoutFlingBehavior> behaviorWeakReference;
public RecyclerViewScrollListener(CoordinatorLayout coordinatorLayout, AppBarLayout child, CorrectiveAppBarLayoutFlingBehavior barBehavior) {
coordinatorLayoutRef = new WeakReference<CoordinatorLayout>(coordinatorLayout);
childRef = new WeakReference<AppBarLayout>(child);
behaviorWeakReference = new WeakReference<CorrectiveAppBarLayoutFlingBehavior>(barBehavior);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
scrollState = newState;
}
public void setVelocity(float velocity) {
this.velocity = velocity;
}
public int getScrolledY() {
return scrolledY;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
scrolledY += dy;
if (scrolledY <= 0 && (scrollState != RecyclerView.SCROLL_STATE_IDLE)
&& childRef.get() != null &&
coordinatorLayoutRef.get() != null &&
behaviorWeakReference.get() != null) {
if(scrollState == RecyclerView.SCROLL_STATE_SETTLING){
// If the RecyclerView is settling on reaching top then we don't need
// a fling with full velocity, just a scroll. We do a fling faking a scroll
velocity = velocity * 0.35f;
}
//manually trigger the fling when it's scrolled at the top
behaviorWeakReference.get().onNestedFling(coordinatorLayoutRef.get(), childRef.get(), recyclerView, 0, velocity, false);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment