Last active
December 29, 2018 19:42
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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