1. 滑动冲突的常见场景

- 场景1:内外View的滑动方向不同,例如viewPager嵌套ListView(当然系统的ViewPager本身已处理)
- 场景2:内外View的滑动方向相同,例如ViewPager嵌套横向的RecyclerView
- 场景3:场景1和场景2的叠加复合场景
2. 滑动冲突的两种解决方式
- 滑动冲突各式各样,需要结合具体的场景选择合适的解决方案,常见的解决滑动冲突的方式有
外部拦截法和内部拦截法
- 外部拦截法:所谓外部就是指外层父容器ViewGroup,正如前面所讲的事件分发机制那样,直接重写父容器的
onInterceptTouchEvent方法,并在方法内部根据具体的场景进行拦截即可
- 内部拦截法:所谓内部是指内层元素(View/ViewGroup),内部拦截法的思路是让父元素不拦截任何事件,全都分发给子元素,如果子元素需要就直接消耗掉,不需要再交由父元素处理,通常需要配合
requestDisallowInterceptTouchEvent方法进行处理,一般做法是重写子元素的dispatchTouchEvent方法并在合适的位置调用parent.requestDisallowInterceptTouchEventt(boolean disallowIntercept)即可
3. 具体场景代码实例
3.1 不同方向滑动冲突
- 内外滑动方向不一致时,这个时候就需要判断滑动方向与水平线的角度来判断到底是左右滑动还是垂直滑动,当然实际比较可以比较垂直和水平两个方向的滑动距离绝对值
- 例如水平方向的HorizontalScrollView嵌套垂直方向的ListView,不做处理时会出现最开始在ListView上斜着滑动时会出现一丝滑动冲突,解决的代码:
class MyHorizontalScrollView(context: Context?, attrs: AttributeSet?) :
HorizontalScrollView(context, attrs) {
private var lastX = 0f
private var lastY = 0f
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> {
lastX = ev.x
lastY = ev.y
}
MotionEvent.ACTION_MOVE -> {
if (abs(ev.x - lastX) < abs(ev.y - lastY)) {
return false
}
}
}
return super.onInterceptTouchEvent(ev)
}
}
<com.btpj.eventdispatch.eventconflict.MyHorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="match_parent"
tools:context=".eventconflict.EventConflictActivity">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent">
<View
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="@color/_4cd2f5" />
<ListView
android:layout_width="300dp"
android:layout_height="wrap_content"
android:entries="@array/list_item" />
<View
android:layout_width="300dp"
android:layout_height="match_parent"
android:background="@color/_6200EE" />
</LinearLayout>
</com.btpj.eventdispatch.eventconflict.MyHorizontalScrollView>
3.2 相同方向的滑动冲突
- 当内层与外层View的滑动方向都一致时,就会存在同方向的滑动冲突,至于具体滑动逻辑是怎样的就要根据具体的场景了
- 以外层ScrollView嵌套内层ListView为例,他们就会出现滑动冲突(当然内层的ListView还会出现获取不到高度的问题,这个不在主题的范围内,直接设置为固定高度即可简单解决)他们的滑动逻辑一般为
- 当ScrollView没有滑动到底部时,一直由ScrollView本身处理
- 当ScrollView滑动到底部时:
- 如果ListView没有滑动到顶部,则交给ListView处理
- 如果ListView滑动到顶部,分两种情况
- 向上滑动,则交给listView处理
- 向下滑动,则交给ScrollView处理
- 根据具体的滑动逻辑来编写代码
class MyScrollView(context: Context?, attrs: AttributeSet?) : ScrollView(context, attrs) {
private var mIsScrollViewToBottom = false
private var mLastY = 0f
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setOnScrollChangeListener { v, _, scrollY, _, _ ->
mIsScrollViewToBottom = v.height + scrollY >= getChildAt(0).height
}
}
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
var intercepted = false
val listView: ListView = (getChildAt(0) as LinearLayout).getChildAt(1) as ListView
when (ev?.action) {
MotionEvent.ACTION_MOVE -> {
intercepted = if (!mIsScrollViewToBottom) {
true
} else {
if (!isListViewToTop(listView)) {
false
} else {
ev.y > mLastY
}
}
}
}
mLastY = ev?.y ?: 0f
super.onInterceptTouchEvent(ev)
return intercepted
}
private fun isListViewToTop(listView: ListView): Boolean {
if (listView.firstVisiblePosition == 0) {
val firstChildView = listView.getChildAt(0)
return firstChildView != null && firstChildView.top >= 0
}
return false
}
}
<com.btpj.views.eventconflict.same.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.btpj.views.eventconflict.same.SameEventConflictActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="@color/_4cd2f5" />
<ListView
android:layout_width="match_parent"
android:layout_height="800dp"
android:entries="@array/list_item" />
</LinearLayout>
</com.btpj.views.eventconflict.same.MyScrollView>
4. 总结
- 了解事件冲突能更好的了解事件冲突解决的原理,常见解决时间冲突的方法包括
外部拦截法以及内部拦截法,通常来说外部拦截法更加简洁容易
- 一般常见的官方提供的控件内部也有帮我们做事件冲突的处理,但完全自定义ViewGroup则经常涉及到事件冲突的处理,这时就得自己动手去解决了,后面会结合自定义View来讲解一些自定义View涉及到的事件冲突的处理