前言
本文需要读者对View事件分发的流程有基本的了解,如果还未完全理解事件分发流程的,需要先学习相关部分内容,这部分可以参考我的上一个博客:
Android View点击事件分发原理,源码解读
https://blog.csdn.net/qq_41872247/article/details/139880308
前提:
现在来说我们如果是使用Google官方的View嵌套,比如ViewPager2,ScrollView,RecyclerView等滑动视图的话,哪怕你出现了两个滑动视图嵌套的情况,一般来说也不会出现滑动冲突的场景,因为Google官方经过多年的迭代之后,对于自带的这些视图常用场景的处理都已经很完善了。
已经实测过不会出现滑动冲突的场景(不分先后顺序):
- RecyclerView套RecyclerView
- ScrollView套RecyclerView
- ViewPager2套RecyclerView
- ViewPager2套ScrollView,ScrollView套ViewPager2
所以,想要讲述滑动冲突这个问题怎么解决,最需要的是先有一个冲突的场景案例。
1. 滑动冲突
一般而言,滑动冲突只有以下三种场景。
- 内部控件的滑动方向和外部控件的滑动方向不同(比如内部视图是左右滑动,而外部视图是上下滑动)
- 内部控件的滑动方向和外部控件的滑动方向相同(比如内部视图和外部视图都是上下滑动)
- 前两者结合的嵌套滑动问题。
2. 解决方案
我们在处理滑动冲突的时候,无非都是遵循一个原则:
当用户想要操作里面那个视图的滑动功能时,让里面的视图处理掉滑动点击事件。
当用户想要操作外面那个视图的滑动功能时,让外面的视图处理掉滑动点击事件。
基于这两种场景,我们在处理滑动冲突的时候就有了外部解决法和内部解决法。
对于滑动方向不同的场景,用外部解决法比较容易写代码
对于滑动方向相同的场景,用内部解决法比较容易写代码
2.1 外部解决法
在外部视图的onInterceptTouchEvent进行逻辑判断,如果是父布局需要滑动,就拦截该事件,否则就放过该事件,代码如下:
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
return false
}
MotionEvent.ACTION_MOVE -> {
if (这是父布局的滑动事件) {
return true
} else {
return false
}
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL -> {
return false
}
}
return super.onInterceptTouchEvent(ev)
}
2.2 内部拦截法
想要让子布局达成这个条件,就需要两点:
- 让子布局处理滑动逻辑的视图,确实的消化掉滑动事件,也就是让内部View处理dispatchTouchEvent这个方法最终return true。
- 让父布局跳过处理滑动逻辑的视图,不拦截该事件,好让事件确实的能流到内部的视图中而不是被外部视图直接处理,也就是让父布局的拦截方法onInterceptTouchEvent这个方法return false。
对于内部拦截法而言,由于他不直接修改父布局的onInterceptTouchEvent方法,所以他需要另外一个API:parent.requestDisallowInterceptTouchEvent(),用这个API来变相控制父布局的onInterceptTouchEvent返回true或false
同时要注意,由于点击事件的延续性,无论这个滑动事件最终是父布局处理还是子布局处理,最开始的DOWN事件父布局不要拦截,子布局在DOWN事件固定return true。
他的大体代码结构如下:
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
when(event?.action) {
MotionEvent.ACTION_DOWN -> {
parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_MOVE -> {
if (这是子布局的滑动事件)
parent.requestDisallowInterceptTouchEvent(true)
else
parent.requestDisallowInterceptTouchEvent(false)
}
MotionEvent.ACTION_CANCEL,
MotionEvent.ACTION_UP -> {
parent.requestDisallowInterceptTouchEvent(false)
}
}
// 如果是子布局的滑动事件,一定要保证该方法return true,这证明了子视图确实的消费了该点击事件
// 这样父布局才不会重复处理该事件,引起滑动冲突
// 由于点击事件的延续性,DOWN固定return true
if (这是子布局的滑动事件 || event?.action == MotionEvent.ACTION_DOWN)
super.dispatchTouchEvent(event)
return true
else
return super.dispatchTouchEvent(event)
}