前言
学习Android,事件传递机制是必须要了解的,之前在项目中遇到一些诸如ScrollView嵌套ListView,RecycleView等,就需要用到这一块的知识,市面上有很多书籍,资料等都讲了这一块的知识,可是只按照别人那里得来的通用性结论死记硬背,感觉也不能深刻的理解,所以我觉得这一块最好的理解方式还是看源码,所以今天决定按照源码的角度来学习和分析事件传递机制。
事件传递机制
首先我们得了解,什么是事件传递机制,举个简单的例子,一个简单的activity,外层是LinearLayout布局,里面有个按钮,点击可以触发相应的事件,那么事件是直接通过按钮的监听来执行的么,显然当然不是,这里的事件是从activity到LinearLayout再到Button一层一层传递进来的,而且上层有权利决定是否分发、是否拦截和消费此事件。我们也都知道,事件传递有三个阶段:分发,拦截和消费,下面分别介绍这三个阶段:
事件分发
事件分发对应着dispatchTouchEvent方法,在Android系统中,所有的触摸事件都是通过这个方法来分发的,Activity,View和ViewGroup都有这个方法,首先可以去看Activity对应的这个方法,这个代码很短,我就直接拷进来了:
activity内的dispatchTouchEvent源码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
可以看到,Activity的分发方法中,先判断事件是否为按下事件,如果是,则会执行onUserInteraction方法,可是点进去发现这个方法是个空方法,也就是子类可以重写这个方法决定当按键为按下时activity的操作内容,这个就先不看了,注意下面这行,看名字可以确定大概就是判断子层viewgroup的dispatchOntouchEvent是否返回的是super方法,如果是,这边返回true,也就是事件继续向下分发。可是这里有一点会有点绕,那也就是如果activity的子类覆盖这个方法,不super而是直接返回true会怎么样呢?大家可以试一下,结果应该是事件不会继续向下分发的,其实这里你只要不super,不论返回true或者false都是不会继续向下分发的。因为参见这里的代码就可以联想到,这里判断子类是否super,其它地方也会判断Activity的这个方法是否super,我们虽然只看Activity、View和ViewGroup,但是Android系统这么大不可能说这些内容不跟其它内容耦合,我们只要把握一个关键点就好了。
ViewGroup的事件分发
先看dispatchOnTouchEvent这个方法,这个源码太长,就不粘上来了,我们从上往下阅读,抓住几个重点(关键字),可以看到,一开始声明了一个boolean类型的handler值
boolean handled = false;
这个就是方法最终的返回值,后面要抓住这个点,继续往下阅读,又遇到一个关键字:
// Check for interception.
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 关键字,很明显就和另一个方法onInterceptTouchEvent有关,果然,往下几行就发现了这个方法,注意上面的判断: if (!disallowIntercept),其实英文直译就可以知道,不允许拦截,加上!就是允许拦截时候会进入这个方法,
所以我们可以假设,当手机当前无其他操作处于允许拦截时,会调用这个onInterceptTouchEvent方法,给intercepted 赋值onInterceptTouchEvent方法的返回值。
猜测
那么我们就有了一个猜测,既然有了这个字段,他的返回值肯定会在某种情况下影响dispatchTouchEvent的返回值,继续往下看,找到这样一行
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
......
这里是一个大的判断,也就是当onInterceptTouchEvent的返回值是false且不取消该次点击的时候,也就是没有拦截子view事件的时候,才会进入这个判断
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
可以看到,这里有个cancelChild的字段是由intercepted决定的,然后下面一行把这个布尔值传进dispatchTransformedTouchEvent方法内部,判定这个方法的返回值为true时,handler=true,看见了么,onInterceptTouchEvent就是通过这种方式织入dispatchTouchEvent方法中,决定返回值的。那么这个dispatchTouchEvent方法中究竟做了什么呢,我们进去看一下:
dispatchTouchEvent内部代码
这里同样是在开始声明了一个布尔类型的handler值
final boolean handled;
再看这个handler是怎么赋值的
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
可以看到,在某种情况下当子view的child为空时,方法的返回值为ViewGroup的dispatchTouchEvent方法的返回值,而child不为空时,返回值就取决于child View的dispatchTouchEvent方法的返回值。当然这里很容易想的通,如果没有内层的view,dispatchTouchEvent还是交给自己处理,而如果有子view,就传递给子view处理。那么在返回到刚刚哪里,方法返回true时,也就是当事件被消费时,这里也返回true,表示被事件被消费掉。
结论
那么对ViewGroup,我们可以的出一个结论,上层的Activity的dispatchTouchEvent传入到ViewGroup,ViewGroup在事件可以被拦截的情况下会调用onInterceptTouchEvent方法,其返回值作为一个参数间接的影响到了dispatchTouchEvent方法,从而根据这个返回值决定dispatchTransformedTouchEvent方法内部的事件流,也就是当onInterceptTouchEvent返回true时,会进入这种逻辑,有子层的view则调用子层view的dispatch,无则返回自己的dispatch。、
OnTouchEvent
相信看到这里你一定很奇怪,为什么没有OnTouchEvent方法,其实我也很奇怪,我这里看到是7.0的源码,在souceinsight里搜索也搜索不到这个方法,之前看书上说是有这个方法的。那么它到哪去了?
其实也没看过别的版本的源码,所以不知道以前这里会不会实现OnTouchEvent方法,因为这里ViewGroup是View的子类,所以子类没有重写这个方法也正常,所以这里对ViewGroup的事件分发的探索也就到此为止了。那么关于onTouchEvent方法实际上我看的这个版本并没有重写该方法,那么,这个就放在下次的View的传递机制去探索啦