Android里面的事件传递机制是一个非常重要内容,是开发者必须要掌握的知识点
什么是事件传递?
当我们手指触摸屏幕时,会产生一系列的触摸事件,必须要将每一个事件传递给一个具体的View
处理,这个传递过程就是事件传递。
当触摸事件产生后,会首先传递到Activity
,然后通过Window
传递给DecorView
,再由DecoreView
传递给我们的View
。
事件传递机制涉及到View的三个方法:
dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent
dispatchTouchEvent
表示事件分发给了View
,如果触摸事件能够到达该View
,那么此方法一定会被调用,返回值表示是否消耗此事件,返回值受子View的dispatchTouchEvent
和onTouchEvent
影响。
onInterceptTouchEvent
用于拦截事件,返回值表示是否拦截此事件,在dispatchTouchEvent
方法内部调用。如果拦截此事件,那么后续同一事件序列的事件到来时,此方法将不会再被调用,默认拦截。
普通View
(非ViewGroup
)没有此方法。
ViewGroup
默认不拦截,返回false
。
onTouchEvent
用于处理事件,返回值表示是否消耗此事件,在dispatchTouchEvent
中被调用,普通View
默认消耗事件,返回true
。
(1)如果开始处理事件时,不消耗ACTION_DOWN
事件,那么同一事件序列中的后续事件都不会交给此View处理。然后会回调父View
的onTouchEvent
方法,如果父View
消耗ACTION_DOWN
事件,那么同一事件序列的后续事件都会交给父View
处理;如果父View
不消耗ACTION_DOWN
事件,会在Activity
的onTouchEvent
中处理ACTION_DOWN
事件,并且同一事件序列的后续事件都会在Activity
的onTouchEvent
中处理。
(2)如果开始处理事件时,消耗了ACTION_DOWN
事件,那么同一事件序列的后续事件仍会交给此View
处理,未消耗的触摸事件会消失,会在Activity
的onTouchEvent
中处理,
下面一段伪代码能够很清楚的描述这三个方法的调用逻辑
public boolean dispatchTouchEvent(MotionEvent event) {
boolean isConsume = false;
if (onInterceptTouchEvent(event)) {
isConsume = onTouchEvent(event);
} else {
isConsume = false;
childView.dispatchTouchEvent(MotionEvent event);
}
return isConsume;
解决滑动冲突
理解了这三个方法的具体调用逻辑,我们可以用来处理滑动冲突,处理滑动冲突主要有两种方法:外部拦截法和内部拦截法。
(1)外部拦截法
只需要重写外部ViewGroup的onInterceptTouchEvent既可
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.i("zb123", "CustomViewGroup onInterceptTouchEvent " + event.getAction());
boolean isIntercept = false;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
isIntercept = false;
break;
case MotionEvent.ACTION_MOVE:
if (父View需要拦截) {
isIntercept = true;
} else {
isIntercept = false;
}
break;
case MotionEvent.ACTION_UP:
isIntercept = false;
break;
}
return isIntercept;
}
(2)内部拦截法
需要重写外部ViewGroup的onInterceptTouchEvent和内部View的dispatchTouchEvent
外部ViewGroup
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean isIntercept = false;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
isIntercept = false;
} else {
isIntercept = true;
}
return isIntercept;
}
内部View
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (内部View不需要拦截) {
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
}
return super.dispatchTouchEvent(event);
}