之前『TabLayout & ViewPager2 快速打造选项卡切换效果]』一文介绍了如何实现一个简单的页面滑动切换效果,随着开发的深入,你可能会遇到 ViewPager2
嵌套滑动的问题,也就是当 ViewPager2
内有一个可以同方向滚动的 View
,比如 RecyclerView
,它并不能够按照我们的预期工作。

嵌套?之前我们用 NestedScrollView 解决 Android 嵌套滚动问题,这次能不能依葫芦画瓢?
很显然,并不适合。
既然如此,我们能否通过修改 ViewPager2
的事件分发逻辑,使其根据我们的需要进行分发?
很遗憾,ViewPager2
被标记为 final
,我们无法继承修改。
public final class ViewPager2 extends ViewGroup {
...
}
RecyclerView
总该不会被标记为 final
吧,我们重写 RecyclerView
是否行得通?
我们换个角度思考这个问题,这次我们重写 RecyclerView
,假如说下次我们需要往 ViewPager2
里嵌套一个 ScrollView
或者其他任意可滚动的 View
,岂不是每一个都要重写?有没有一劳永逸的方法?
其实可以单独自定义一个 ViewGroup
用于处理 ViewPager2
内的事件分发,当添加了需要处理嵌套滑动问题的 View
时,只需要在该 View
外面包一层我们自定义的 ViewGroup
,就可以实现对嵌套滑动事件的处理。
class ViewPager2NestedFrameLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
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
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
parentViewPager?.isUserInputEnabled = false
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
parentViewPager?.isUserInputEnabled = true
}
}
return super.dispatchTouchEvent(ev)
}
}
我们继承 FrameLayout
来实现,一般情况下该 ViewGroup
仅会包裹我们需要嵌套滚动的 View
,所以 FrameLayout
对原有代码的入侵性最小,同时性能也最优。
这其中一个关键点是如何获取 ViewPager2
的实例,当然我们可以通过外部传入,但这样每次使用时都需要手动设置一遍,非常麻烦。于是这里采用另一种方式,通过 View
的 getParent()
方法获取其父 View
,然后判断其是否为 ViewPager2
,如果是则返回,否则继续向上查找。
拿到 ViewPager2
的实例就好办了,当接收到 ACTION_DOWN
事件时,我们调用 setUserInputEnabled()
设置为 false
,禁止 ViewPager2
滚动,当接收到 ACTION_UP
或 ACTION_CANCEL
事件时,我们设置为 true
,允许 ViewPager2
滚动。
使用方法正如我们前面所述,包裹在需要嵌套滑动的 View
外即可:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".NestedFragment">
...
<com.example.nested.ViewPager2NestedFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</com.example.nested.ViewPager2NestedFrameLayout>
</FrameLayout>
效果如下:

正如我们所期望的那样工作,由于范围仅限制在该 ViewGroup
范围内,所以触摸该 ViewGroup
以外的区域仍能够使 ViewPager2
正常滑动。