1、技术概述
在Android开发中,不可避免地会遇到View嵌套的情况,而其中就会出现滑动冲突的现象,所以了解解决滑动冲突的方法是十分有必要的
2、技术详述
滑动冲突的场景
常见的滑动冲突的场景主要有以下两种:
- 父View滑动方向和子View滑动方向不一致
- 父View滑动方向和子View滑动方向一致
滑动冲突产生的原因
Android的事件分发机制是从父View传递到子View的,首先事件会传递到父View的dispatchTouchEvent
方法中,而该方法会调用onInterceptTouchEvent
方法判断父View在此时是否需要拦截该事件,如果用户在某个方向的滑动距离超过某一个阈值,则父View的onInterceptTouchEvent
方法会返回true
,此时父View会直接接管该事件流程直到出现ACTION_UP
事件也就是用户抬手时,事件会直接交由父View的onTouchEvent
方法处理,造成了子View无法接收到事件;亦或是父View在ACTION_DOWN
事件时放弃了该事件,父View会将该事件分发给所有子View处理,此时某个子View先行检测到滑动,此时子View的onInterceptTouchEvent
将返回true
,事件就会由子View的onTouchEvent
方法处理,若该方法返回了true
,那么该事件将不会被父View处理。此时便产生了滑动冲突。
滑动冲突的解决方案
滑动冲突的解决方法主要有两种,分为外部拦截法与内部拦截法
外部拦截法
外部拦截法的原理是重写父View的onInterceptTouchEvent
方法,通过判断父View是否需要该事件来选择是否拦截该事件,从而解决了滑动冲突
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//父View的ACTION_DOWN事件必须返回false,否则后续ACTION_MOVE以及ACTION_UP事件会直接交由父View处理不会经过子View
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
//如果父View拦截了该事件,则子View会接收不到ACTION_UP事件导致点击事件失效
intercepted = false;
break;
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
内部拦截法
内部拦截法的原理是重写子View的dispatchTouchEvent
方法,利用调用父View的requestDisallowInterceptTouchEvent(true)
方法使父View不处理该事件让事件分发给子View处理,如果子View不需要处理该事件则调用父View的requestDisallowInterceptTouchEvent(false)
使父View重新具备处理事件的能力
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if (父容器需要此类点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
同时还需要重写父View的onInterceptTouchEvent
方法,因为父View在处理ACTION_DOWN
事件时是不受requestDisallowInterceptTouchEvent
设置的标志位约束的,具体而言是在父View的dispatchTouchEvent
方法中判断事件是ACTION_DOWN
类型时会清除requestDisallowInterceptTouchEvent
设置的标志位,如果父View处理了ACTION_DOWN
事件,之后的ACTION_MOVE
以及ACTION_UP
事件就不会分发给子View,这时重写的·dispatchTouchEvent·方法里写的判断就都失效了。所以需要重写方法使父View不处理ACTION_DOWN
事件从而让事件分发给子View
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
3、总结
滑动冲突可以利用外部拦截法以及内部拦截法处理,主要利用了onInterceptTouchEvent
,requestDisallowInterceptTouchEvent
等方法进行处理
4、参考文献
《Android开发艺术探索》 任玉刚著. ————电子工业出版社