Android的事件分发主要有这几个角色:Activity、Window、ViewGroup和View。当Activity接收到事件时,会将事件传递给Window,而后Window将事件传递给顶层容器DecorView(继承自FrameLayout),事件分发由此开始。java
这边我将对DOWN、MOVE和UP事件结合源码单独分析。ide
首先先明确几个概念:源码分析
当Activity接收到事件时,Activity的dispatchTouchEvent方法会被调用。post
Activity.javathis
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
复制代码
从代码中能够看到,Activity收到事件后将事件交由Windows处理spa
PhoneWindow.java3d
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
复制代码
Window会将事件交由DecorView处理rest
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
...
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
复制代码
由此能够看到事件传递给了ViewGroup,View事件分发由此开始。code
首先分析DOWN事件,当咱们触摸手机屏幕的一瞬间,Activity接收到DOWN事件,事件由Activity传递到Window,再到DecorView。当DecorView接收到事件,会调用ViewGroup的dispatchTouchEvent方法。orm
因为是DOWN事件传递到ViewGroup,在dispatchTouchEvent方法中首先会重置触摸状态,包括清除保存处理事件View的单链表,所以每次DOWN事件表明一个新的事件序列的开始,这点以后会具体分析。
if (actionMasked == MotionEvent.ACTION_DOWN) {
//重置全部的触摸状态
cancelAndClearTouchTargets(ev);
resetTouchState();
}
复制代码
因为是DOWN事件,则先会去判断当前容器是否禁止拦截事件。默认状况下,父容器能够拦截事件,此时会调用onInterceptTouchEvent方法,该方法默认返回false;若父容器被禁止拦截事件,则不会调用onInterceptTouchEvent方法。
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 {
//拦截事件
intercepted = true;
}
复制代码
这里先看默认状况,事件没有被父容器拦截,即intercepted为false。此时会去遍历该ViewGroup的子View,寻找发生DOWN事件的View。若找到发生DOWN事件的View,将事件分发给对应的子View,若View可以处理事件,也就是子View的dispatchTouchEvent方法返回true,则将该处理事件的View加入mFirstTouchTarget这个链表中,并标记当前DOWN事件已被处理。
ViewGroup#dispatchTouchEvent
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//未取消未拦截
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//遍历全部的子View,寻找处理事件的View
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//找处处理事件的子View,保存该子View
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//事件已经被子View处理
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
复制代码
父ViewGroup经过调用dispatchTransformedTouchEvent 将事件分发给对应的子View。子View处理了DOWN事件,也就是子View的dispatchTouchEvent方法返回true,从而会使得dispatchTransformedTouchEvent方法返回true。
ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
...
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);
}
// Done.
transformedEvent.recycle();
return handled;
}
复制代码
若没有找到可以处理事件的子View,此时事件会交给当前ViewGroup来处理。没有找处处理DOWN事件的子View,也就是mFirstTouchTarget这个链表没有被赋值,此时为null。此时经过dispatchTransformedTouchEvent将事件传递给当前ViewGroup的父类,调用View的dispatchTouchEvent方法进行事件处理。
ViewGroup#dispatchTouchEvent
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
复制代码
接下来事件传递到了View,下方是View的dispatchTouchEvent方法。
View.java
public boolean dispatchTouchEvent(MotionEvent ev){
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
复制代码
事件到了View的dispatchTouchEvent方法,先会去判断事件是否由OnTouchListener消费掉而且View是否可用,若OnTouchListener返回true且View处于可用状态,则表示该DOWN事件被消费掉,该DOWN事件处理结束;若事件未被OnTouchListener消费掉或者View处于不可用状态,则将事件交由View的onTouchEvent方法。
View.java
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//当前View不可用
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
//若View可点击或者可长按,则事件被消费
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//若View可点击或者长按,则事件被消费
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
...
break;
}
return true;
}
return false;
}
}
复制代码
上述2.1.1的前提是父容器没有拦截事件,也就是intercepted的值为false。若此时intercepted值未true(当onInterceptToucnEvent方法返回true)。此时不会去寻找处理事件的子View,也就是mFirstTouchTarget为null,一样事件会交由ViewGroup父类的dispatchTouchEvent方法处理。
DOWN事件的分发流程如图所示,总的来讲能够概括一下几点:
上述2.2分析了DOWN事件的分发,接下来先分析UP事件的分发。 当手机抬起屏幕的一瞬间,Activity会接收到UP事件,Activity将UP事件传递给Window,Window将事件传递给DecorView,DecorView父类ViewGroup的dispatchTouchEvent方法被调用。
因为dispatchTouchEvent方法接收到的是UP事件,若mFirstTouchTarget不为空,此时表明存在处理DOWN和MOVE事件的子View。mFirstTouchTarget是一个链表,用于保存处理事件的子View,在DOWN事件被子View处理后在子View的父容器内被赋值,至于要用一个链表的缘由是存在多点触控的状况,这里只考虑单点触控的事件分发。
存在处理DOWN和MOVE事件的子View,也就是mFirstTouchTarget不为空。在mFirstTouchTarget不为空的状况下,会去判断当前容器是否禁止拦截事件。默认状况下为不拦截事件,此时会调用onInterceptTouchEvent方法,该方法默认返回false;
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 {
//拦截事件
intercepted = true;
}
复制代码
当intercepted为false时,此时为默认状况,表明当前容器不拦截UP事件,事件被分发给保存在mFirstTouchTarget的子View。
ViewGroup#dispatchToucnEvent
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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;
//将事件分发给子View
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;
}
}
复制代码
能够看到事件传递到了dispatchTransformedTouchEvent内部,因为child不为null,则会调用child的dispatchTouchEvent方法将事件分发给子View。
ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
...
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);
}
// Done.
transformedEvent.recycle();
return handled;
}
复制代码
当intercepted的值未true时,表明当前UP事件被当前容器拦截。UP事件被当前容器拦截,可是以前的DOWN和MOVE事件都被子View处理了,此时mFirstTouchTarget不为空,因此此时走else分支,取消当前的UP事件,变为CANCEL事件,往下分发或者交由本身处理,而且此时会在遍历时清空保存在mFirstTouchTarget中处理事件的子View,最终mFirstTouchTarget的值为空。
ViewGroup#dispatchTouchEvent
if (mFirstTouchTarget == null) {
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 {
//因为事件被拦截,intercepted为true,cancelChild为true,表明取消事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
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;
}
}
复制代码
因为是UP事件,最终会清除View的触摸状态
ViewGroup#dispatchTouchEvent
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
}
复制代码
当mFirstTouchTarget为空时,不存在处理事件的子View,此时容器父类View的dispatchTouchEvent方法会接收到事件。
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
复制代码
事件传递到View的dispatchTouchEvent方法,一样先会去判断事件是否由OnTouchListener消费掉,若事件被消费且View可用,则该UP事件处理结束。若事件未被OnTouchListener消费掉或者View不可用,则将事件交由View的onTouchEvent方法。
View.java
public boolean dispatchTouchEvent(MotionEvent ev){
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
复制代码
事件传递到View的onTouchEvent中,能够看到在UP的时候在条件知足的状况下会执行单击事件,同时事件被消费。
View.java
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//View不可用,但View可单击或者长按,一样能够消费事件
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
//View 可点击或者长按
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
..
if (!mHasPerformedLongPress) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//执行单击事件
if (!post(mPerformClick)) {
performClick();
}
}
}
}
break;
}
return true;
}
return false;
}
复制代码
UP事件交给ViewGroup本身处理时,除了在UP事件会在条件知足下触发单击事件和事件未被消费时会交给Activity处理,其他的流程和ViewGroup处理DOWN事件相似。