ViewGroup事件传递
上篇笔记中介绍道,ViewGroup中参与事件传递的方法有以下三个:
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent ev)
ViewGroup事件传递方法调用
dispatchTouchEvent
当我们点击控件时,首先调用的是布局组件(ViewGroup)的dispatchTouchEvent方法。这个方法和View中的不太一样,它需要去确定用户到底点击的是哪个子View,并将事件分发给他。然后调用这个子View的dispatchTouchEvent,最后按照上篇笔记中记录的流程,执行事件的分发。ViewGroup.dispatchTouchEvent()部分源代码如下:
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
上述代码中,有个比较重要的局部变量:intercepted。这个变量决定了ViewGroup是否将事件分发给子View。而在代码中我们可以看到,这个变量的值就是onInterceptTouchEvent()方法的返回值。接下来我们看onInterceptTouchEvent()方法.
onInterceptTouchEvent
onInterceptTouchEvent源代码:
Public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
这个方法的代码及其简单。当该方法返回true时,即事件交给ViewGroup自身来消费,后续调用ViewGroup的onTouch和onTouchEvent方法;当返回false时,事件将分发给子View来触发。
当事件交由子View来触发时,那么ViewGroup.diapatchTouchEvent方法的返回值完全有子View的diapatchTouchEvent返回值决定。如果子View是可点击的,那么ViewGroup的diapatchTouchEvent返回true,也就是代表事件已经被子View消费掉了,即不会执行ViewGroup的onTouch和onTouchEvent方法。如果子View没有消费事件,那么会调用ViewGroup的父类的diapatchTouchEvent方法,进行事件分发,交由onTouch和onTouchEvent触发。当我们点击ViewGroup的空白区域,同样会调用父类的diapatchTouchEvent进行事件分发。
实践
- 当事件正常从ViewGroup分发到对应的子View,log打印如下:
2.当事件在ViewGroup中被拦截,onInterceptTouchEvent方法返回true,Log打印如下:
3.当点击了ViewGroup中不可点击的子View,Log打印如下:
Button要设置成不可点击状态,只能通过将Button控件的enable属性设置为false。
事件拦截
事件传递方法返回值都是boolean型,true代表事件已经被消费,false代表事件没有被消费,可以分发。
onTouch方法返回true
onTouch方法默认返回值都是false,当我们使其返回true,将事件拦截在了touch事件。也就是说在dispatchTouchEvent方法中不会调用onTouchEvent方法(触发点击事件在该方法中)。
onInterceptTouchEvent方法返回true
onInterceptTouchEvent方法默认返回是false,当我们将其返回值设定为true时,也就是让事件不能分发给ViewGroup的子View。让ViewGroup自身来消费事件。
Activity触摸事件传递
其实,当我们触摸屏幕时,事件传递的顺序应该是activity->viewgroup->view。和VIewGroup,View相同,Activity也有dispatchTouchEvent和onTouchEvent方法。触摸事件最先调用的是Activity的dispatchTouchEvent方法,代码如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
首先判断当前事件是否是ACTION_DOWN,如果是调用onUserInteraction()方法。该方法可以在Activity中被重写,在事件被分发前会调用该方法。该方法的返回值是void型,不会对事件传递结果造成影响。接着会判断getWindow().superDispatchTouchEvent(ev)的执行结果。这一步首先通过getWindow()方法得到Activity被加载时,创建的PhoneWindow对象,然后调用PhoneWindow对象中的DecorView成员变量的dispatchTouchEvent方法对事件进行分发。如果事件没有被DecorView及其子View消费,那么调用Activity的onTouchEvent()方法消费事件。