From 92b1d56dc508a0b211b08e683deed0d5738d46ff Mon Sep 17 00:00:00 2001 From: Adetunji Dahunsi Date: Sun, 16 Feb 2020 18:31:51 -0500 Subject: [PATCH] Used OnTouchListener for nested scrolling in ViewPager2 --- .../testapp/NestedScrollableHost.kt | 72 ++++++++----------- .../ParallelNestedScrollingActivity.kt | 1 + .../res/layout/item_nested_recyclerviews.xml | 27 +++---- 3 files changed, 40 insertions(+), 60 deletions(-) diff --git a/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt b/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt index 819ffb40..f498ad51 100644 --- a/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt +++ b/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt @@ -16,77 +16,62 @@ package androidx.viewpager2.integration.testapp +import android.annotation.SuppressLint import android.content.Context -import android.util.AttributeSet -import android.view.MotionEvent -import android.view.View -import android.view.ViewConfiguration -import android.widget.FrameLayout +import android.view.* import androidx.viewpager2.widget.ViewPager2 -import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL import kotlin.math.absoluteValue import kotlin.math.sign /** - * Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem + * Class to scroll a scrollable component inside a ViewPager2. Provided as a solution to the problem * where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as - * ViewPager2. The scrollable element needs to be the immediate and only child of this host layout. + * ViewPager2. * * This solution has limitations when using multiple levels of nested scrollable elements * (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2). */ -class NestedScrollableHost : FrameLayout { - constructor(context: Context) : super(context) - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) +private class NestedScrollableHost(context: Context) : View.OnTouchListener { - private var touchSlop = 0 + private var touchSlop = ViewConfiguration.get(context).scaledTouchSlop private var initialX = 0f private var initialY = 0f - private val parentViewPager: ViewPager2? - get() { - var v: View? = parent as? View - while (v != null && v !is ViewPager2) { - v = v.parent as? View - } - return v as? ViewPager2 - } - - private val child: View? get() = if (childCount > 0) getChildAt(0) else null + private val View.parentViewPager: ViewPager2? + get() = generateSequence(this as? ViewParent, ViewParent::getParent) + .filterIsInstance() + .firstOrNull() - init { - touchSlop = ViewConfiguration.get(context).scaledTouchSlop - } - - private fun canChildScroll(orientation: Int, delta: Float): Boolean { + private fun View.canScroll(orientation: Int, delta: Float): Boolean { val direction = -delta.sign.toInt() return when (orientation) { - 0 -> child?.canScrollHorizontally(direction) ?: false - 1 -> child?.canScrollVertically(direction) ?: false + ViewPager2.ORIENTATION_HORIZONTAL -> canScrollHorizontally(direction) + ViewPager2.ORIENTATION_VERTICAL -> canScrollVertically(direction) else -> throw IllegalArgumentException() } } - override fun onInterceptTouchEvent(e: MotionEvent): Boolean { - handleInterceptTouchEvent(e) - return super.onInterceptTouchEvent(e) + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(view: View, e: MotionEvent): Boolean { + handleInterceptTouchEvent(view, e) + return false } - private fun handleInterceptTouchEvent(e: MotionEvent) { - val orientation = parentViewPager?.orientation ?: return + private fun handleInterceptTouchEvent(view: View, e: MotionEvent) { + val orientation = view.parentViewPager?.orientation ?: return // Early return if child can't scroll in same direction as parent - if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) { + if (!view.canScroll(orientation, -1f) && !view.canScroll(orientation, 1f)) { return } if (e.action == MotionEvent.ACTION_DOWN) { initialX = e.x initialY = e.y - parent.requestDisallowInterceptTouchEvent(true) + view.parent.requestDisallowInterceptTouchEvent(true) } else if (e.action == MotionEvent.ACTION_MOVE) { val dx = e.x - initialX val dy = e.y - initialY - val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL + val isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL // assuming ViewPager2 touch-slop is 2x touch-slop of child val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f @@ -95,18 +80,21 @@ class NestedScrollableHost : FrameLayout { if (scaledDx > touchSlop || scaledDy > touchSlop) { if (isVpHorizontal == (scaledDy > scaledDx)) { // Gesture is perpendicular, allow all parents to intercept - parent.requestDisallowInterceptTouchEvent(false) + view.parent.requestDisallowInterceptTouchEvent(false) } else { // Gesture is parallel, query child if movement in that direction is possible - if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) { + if (view.canScroll(orientation, if (isVpHorizontal) dx else dy)) { // Child can scroll, disallow all parents to intercept - parent.requestDisallowInterceptTouchEvent(true) + view.parent.requestDisallowInterceptTouchEvent(true) } else { // Child cannot scroll, allow all parents to intercept - parent.requestDisallowInterceptTouchEvent(false) + view.parent.requestDisallowInterceptTouchEvent(false) } } } } } -} \ No newline at end of file +} + +fun ViewGroup.allowSameDirectionScrollingInViewPager2() = + setOnTouchListener(NestedScrollableHost(context)) diff --git a/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt b/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt index c20b77dd..e524fe03 100644 --- a/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt +++ b/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/ParallelNestedScrollingActivity.kt @@ -68,6 +68,7 @@ class ParallelNestedScrollingActivity : Activity() { private fun RecyclerView.setUpRecyclerView(orientation: Int) { layoutManager = LinearLayoutManager(context, orientation, false) adapter = RvAdapter(orientation) + allowSameDirectionScrollingInViewPager2() } class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { diff --git a/ViewPager2/app/src/main/res/layout/item_nested_recyclerviews.xml b/ViewPager2/app/src/main/res/layout/item_nested_recyclerviews.xml index ea370272..516c84e2 100644 --- a/ViewPager2/app/src/main/res/layout/item_nested_recyclerviews.xml +++ b/ViewPager2/app/src/main/res/layout/item_nested_recyclerviews.xml @@ -40,16 +40,12 @@ android:text="@string/first_rv" android:textStyle="bold" /> - - - + android:layout_marginTop="8dp" + android:background="#FFFFFF" /> - - - + android:layout_marginRight="20dp" + android:background="#FFFFFF" />