ViewPage2嵌套ViewPage2的滑动冲突解决
2021-11-26
5 min read
Google官方的一个视图Demo
在做测试的时候写了个ViewPage2嵌套ViewPage2的页面,突然发现,内部的ViewPage2响应不到滑动事件,只有外部的ViewPage2能够响应。
第一反应事件冲突了,好说,继承ViewPage2处理下事件分发就好了,一写?啥?final的类不让继承,好吧再看看其他的解决方案:
好吧,也就是ViewPage2是不支持有同向滑动子布局的,如果想要支持滑动需要在onInterceptTouchEvent中处理,并且官方给出了一个解决方案的demo,就是在内层的ViewPage2上嵌套一层布局,这个布局自己实现,那么就可以在这个布局中实现onInterceptTouchEvent来处理
import android.widget.FrameLayout
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL
import kotlin.math.absoluteValue
import kotlin.math.sign
class NestedScrollableHost : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
//这里是获取父的ViewPage2一层一层的往上找
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
}
/**
* 在childCount大于0的情况下获取第一位的View
*/
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
init {
//获取一个阀值,在大于这个阀值的情况下才会触发
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
/**
* canScrollVertically(1) true能够向上滑动 false 不能向上滑动
* canScrollVertically(-1) true能够向下滑动 false 不能向下滑动
* canScrollHorizontally(1) true能够向左滑动 false 不能向左滑动
* canScrollHorizontally(-1) true能够向右滑动 false 不能向左滑动
*/
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
/**
* sign的解释 返回参数的符号函数; 如果参数为零,则为零,如果参数大于零,则为 1.0f,如果参数小于零,则为 -1.0f。
*/
val direction = -delta.sign.toInt()
//判断在同向时,子view能否继续滑动
return when (orientation) {
ORIENTATION_HORIZONTAL -> child?.canScrollHorizontally(direction) ?: false
ORIENTATION_VERTICAL -> child?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
//获取父ViewPage2的方向,获取不到那说明没有嵌套ViewPage2直接返回
val orientation = parentViewPager?.orientation ?: return
// Early return if child can't scroll in same direction as parent
//如果子view在相同的方向上已经不能滚动了,那么也不需要处理了
//1和-1分别测试同轴的两个方向,上和下,左和右对应
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
//ACTION_DOWN时先把事件拦截掉
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
//ViewPage2是否是横向滑动的
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
// assuming ViewPager2 touch-slop is 2x touch-slop of child
//假设父ViewPage2的滑动阀值是childView的阀值的2倍
//如果是横向的画x的值取一半
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
//在竖向的话Y的值取一半
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
//只有这个值大于阀值的的时候才触发
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
/**
* 也就是横向滑动的话,但是y轴的滑动距离大于x轴,那么也是让父布局拦截
* 如果是竖直方向滑动的话,但是x轴滑动的距离大于y轴的,父布局拦截
* 也就是只要isVpHorizontal的值等于scaledDy > scaledDx那么就让父布局处理,说明不是需要处理的
* 滑动方向
*/
// Gesture is perpendicular, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
} else {
//如果子View能滑动,父布局不处理滑动事件并进行继续的分发,分发的话事件就传递下来了
// Gesture is parallel, query child if movement in that direction is possible
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
// Child can scroll, disallow all parents to intercept
parent.requestDisallowInterceptTouchEvent(true)
} else {
//不能滑动,那么拦截
// Child cannot scroll, allow all parents to intercept
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}
使用的话直接在内层的滑动布局外加一层NestedScrollableHost。