说到Android的View事件分发,咱们都应该听过下面三个方法的大名:
- dispatchTouchEvent
- onInterceptTouchEvent
- onTouchEvent
那么这三个方法在源码中是怎样被串联起来的呢?
抛开细节,我们还是先追一下其工作流程。
工作流程
一切要从Activity说起:
//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//重点,Activity中的window表示想接手此事
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果window没有接手,就由Activity自产自销
return onTouchEvent(ev);
}
//当用户需要知道设备正在产生点击触摸事件时,可以覆写此方法
public void onUserInteraction() {
}
public Window getWindow() {
return mWindow;
}
我们来看看window究竟有没有兴趣接手此事:
/**
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
//略过
public abstract boolean superDispatchTouchEvent(MotionEvent event);
}
emmmm,看来它没有…
注释表示也许它的唯一实现类android.view.PhoneWindow会有兴趣:
//PhoneWindow.java
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
然而它又让DecorView接手:
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
这就是一个甩锅的过程…我们继续看FrameLayout:
@RemoteView
public class FrameLayout extends ViewGroup {
//咳,不用看了,它没有dispatchTouchEvent方法
}
锅终于被甩到ViewGroup了:
//ViewGroup.java
public boolean dispatchTouchEvent(MotionEvent ev) {
//略过部分代码
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//略过部分代码
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept一般默认值为false,此处属性可覆写类似requestDisallowInterceptTouchEvent相关的方法改变,如ViewPager等需要处理滑动冲突的控件就覆写了此方法
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果没其他
if (!disallowIntercept) {
//重点,继承ViewGroup的控件都会受此方法影响
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//略过部分代码
//intercepted的值开始起作用了
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 childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//略过部分代码
//重点,此处开始遍历其子控件了,找接锅的人
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
//重点,此处在判断当前子控件有没有兴趣接锅
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//重点,此处为mFirstTouchTarget赋了值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//重点,此处有跳出,这个子控件表示锅我来扛
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//略过部分代码
}
}
//哪个子控件想接手此事件,快出来领锅吧
if (mFirstTouchTarget == null) {
//没有人领锅,就交给super处理了
handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//确定一下领锅的人
while (target != null) {
//让下一个领锅的人上来
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//如果和之前遍历判断中领锅的人是同一个,那么锅就由他领了算了
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
//否则,就要判断下一个人想不想领锅,直到while完毕
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
//略过部分代码
}
//略过部分代码
return handled;
}
//TouchTarget明显是一个链表,有利于锅的传递
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
由此看来,dispatchTransformedTouchEvent方法就是关键了:
//ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
final boolean handled;
//取消动作的也需要处理的,不过这属于意外情况,常规动作事件不走这里
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//重点,子控件为空就交给父类处理了
handled = super.dispatchTouchEvent(event);
} else {
//重点,交给子控件的dispatchTouchEvent处理
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//略过部分代码
//如果有两个动作传递到此,并且相同,当作一个动作处理了,这个PointerIdBits类似于一个点击触摸事件的ID,这也是一种意外情况
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);
//重点,交给子控件的dispatchTouchEvent处理
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// 这里才是正常情况
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());
}
//重点,老样子,交给子控件的dispatchTouchEvent处理
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
情况渐渐明了,在中途没有控件拦截下,真正到达一个非容器(ViewGroup)控件之前,ViewGroup会通过dispatchTouchEvent一直向下传递事件,层层传递。
最终来到了View:
//View.java
public boolean dispatchTouchEvent(MotionEvent event) {
//略过部分代码
boolean result = false;
//略过部分代码
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//重点,看上面这个mOnTouchListener,为什么onTouch的优先级要高于onTouchEvent就是因为这里有判断
result = true;
}
//重点,onTouchEvent出现了
if (!result && onTouchEvent(event)) {
result = true;
}
}
//略过部分代码
return result;
}
最终成功抵达了onTouchEvent。
上面的源码中也可以看出来,onTouch比onTouchEvent的优先级高是因为先于onTouchEvent执行,那么onClick是不是因为后于onTouchEvent执行所以优先级更低呢?
答–那可不咋滴:
public boolean onTouchEvent(MotionEvent event) {
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//略过了很多代码
performClick();
//略过了很多代码
break;
}
return true;
}
return false;
}
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//就是这里了
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
//略过部分代码
return result;
}
总结
从Activity中产生点击行为,依次向下传递点击事件,Activity–>PhoneWindow->DecorView–>各层级ViewGroup–>最底层View,
中途谁表示由它处理(onInterceptTouchEvent==true),就不再向下传递;
如果到最底层都没人接手,又依次向上通过底层View的onTouchEvent向最顶层传递,中途经过各层级ViewGroup的super.dispatchTouchEvent,直到由Activity的onTouchEvent接手。
事情经过大致就是这样,当然其中有许多细节,比如子View通过requestDisallowInterceptTouchEvent可干预父View事件传递之类的,但总体还处于这三个方法的逻辑之中。
以上。