Skip to content

Instantly share code, notes, and snippets.

@Sdghasemi
Last active December 29, 2018 19:42
Show Gist options
  • Save Sdghasemi/bcaaf15ecc8760fdb0ffe816f31fae5d to your computer and use it in GitHub Desktop.
Save Sdghasemi/bcaaf15ecc8760fdb0ffe816f31fae5d to your computer and use it in GitHub Desktop.
Java version of a RecyclerView that respects the nested scrolling of its children (Credits to original kotlin version: https://medium.com/widgetlabs-engineering/scrollable-nestedscrollviews-inside-recyclerview-ca65050d828a)
import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class NestedRecyclerView extends RecyclerView implements NestedScrollingParent {
private View mNestedScrollTarget = null;
private boolean mNestedScrollTargetIsBeingDragged = false;
private boolean mNestedScrollTargetWasUnableToScroll = false;
private boolean mSkipsTouchInterception = false;
public NestedRecyclerView(Context context) {
super(context);
}
public NestedRecyclerView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public NestedRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean temporarilySkipsInterception = mNestedScrollTarget != null;
if (temporarilySkipsInterception) {
// If a descendant view is scrolling we set a flag to temporarily skip our onInterceptTouchEvent implementation
mSkipsTouchInterception = true;
}
// First dispatch, potentially skipping our onInterceptTouchEvent
boolean handled = super.dispatchTouchEvent(ev);
if (temporarilySkipsInterception) {
mSkipsTouchInterception = false;
// If the first dispatch yielded no result or we noticed that the descendant view is unable to scroll in the
// direction the user is scrolling, we dispatch once more but without skipping our onInterceptTouchEvent.
// Note that RecyclerView automatically cancels active touches of all its descendants once it starts scrolling
// so we don't have to do that.
if (!handled || mNestedScrollTargetWasUnableToScroll) {
handled = super.dispatchTouchEvent(ev);
}
}
return handled;
}
// Skips RecyclerView's onInterceptTouchEvent if requested
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
return !mSkipsTouchInterception && super.onInterceptTouchEvent(e);
}
@Override
public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
if (target == mNestedScrollTarget && !mNestedScrollTargetIsBeingDragged) {
if (dyConsumed != 0) {
// The descendant was actually scrolled, so we won't bother it any longer.
// It will receive all future events until it finished scrolling.
mNestedScrollTargetIsBeingDragged = true;
mNestedScrollTargetWasUnableToScroll = false;
} else if (dyUnconsumed != 0) {
// The descendant tried scrolling in response to touch movements but was not able to do so.
// We remember that in order to allow RecyclerView to take over scrolling.
mNestedScrollTargetWasUnableToScroll = true;
target.getParent().requestDisallowInterceptTouchEvent(false);
}
}
}
@Override
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) {
if ((axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0) {
// A descendant started scrolling, so we'll observe it.
mNestedScrollTarget = target;
mNestedScrollTargetIsBeingDragged = false;
mNestedScrollTargetWasUnableToScroll = false;
}
super.onNestedScrollAccepted(child, target, axes);
}
// We only support vertical scrolling.
@Override
public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int nestedScrollAxes) {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onStopNestedScroll(@NonNull View child) {
// The descendant finished scrolling. Clean up!
mNestedScrollTarget = null;
mNestedScrollTargetIsBeingDragged = false;
mNestedScrollTargetWasUnableToScroll = false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment