想要了解Android的事件分发机制,首先需要知道事件是从哪里开始的。从之前的一篇文章View是如何被添加到屏幕上的的最后一幅图,可以知道,我们打开一个界面后,界面的层级从顶层开始是
Activity->PhoneWindow->DecorView->…
所以当我们手指点击到屏幕上之后,事件的分发也是在Activity中开始。执行Activity中的dispatchTouchEvent方法那就从这里开始看
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果是DOWN事件
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//此方法里面是空的,可以重写此方法
onUserInteraction();
}
//如果getWindow().superDispatchTouchEvent(ev)返回true
//那么dispatchTouchEvent方法就返回true
//反之就执行activity的onTouchEvent方法
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
首先判断是不是DOWN事件,如果是执行onUserInteraction(),这个方法里面啥也没有,我们可以重写这个方法来实现我们的关于DOWN事件的逻辑。
然后调用getWindow().superDispatchTouchEvent(ev),如果它返回true,那么dispatchTouchEvent就返回true。如果返回false,就执行activity的onTouchEvent方法。
Activity的onTouchEvent方法很简单,如果一个事件没有被Activity下的任何一个veiw接受
就结束返回true,只有点击到Window外面才会返回true,一般情况下都返回false。
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
下面来看看getWindow().superDispatchTouchEvent(ev)这个方法,getWindow()方法返回一个Window对象,它是一个抽象类,只有一个子类那就是PhoneWindow,所以无PhoneWindow中查看superDispatchTouchEvent(ev)方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
mDecor是一个DecorView对象,public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks
,DecorView继承自FrameLayout,所以它也是一个ViewGroup它是我们一个页面的最顶级的View。点进DecorView中可以看到
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
它直接调用了其父类的dispatchTouchEvent方法,继续跟进就进入到了ViewGroup中的dispatchTouchEvent方法了。
现在我们知道,当我们点击屏幕的时候,事件通过Activity传递到PhoneWindow在传递到ViewGroup中开始真正的分发。下面开始查看ViewGroup中的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
//安全验证
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//开始处理一个DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//当开始处理一个DOWN事件的时候,清除掉之前的所有的事件和标志位
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//定义一个boolean类型的变量记录是否拦截事件
final boolean intercepted;
//如果是DOWN事件并且当前触摸的对象mFirstTouchTarget不为空
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//disallowIntercept可以拦截除了Down事件以外的事件,因为前面的DOWN中清空了所有标志位
//FLAG_DISALLOW_INTERCEPT可以通过requestDisallowInterceptTouchEvent方法来更改。一般是子view调用这个方法。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//onInterceptTouchEvent默认返回false,不拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//没有触摸目标或者不是DOWN事件,说明拦截
intercepted = true;
}
// 如果拦截了,就进行正常的事件分发
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 检查是否取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
//定义一个新的触摸对象
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//如果没取消,并且不拦截事件
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;
//清理指针id防止不同步
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//如果新的触摸对象为空并且子view个数大于0
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//找到一个可以接收事件的子节点
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒叙遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//如果view是可获取焦点的
if (childWithAccessibilityFocus != null) {
//当前view不可获取焦点
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
//如果view是不可见状态或者在执行动画,或者触摸范围不是在view的范围之内,就跳出循环继续循环下面的
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//找到当前触摸的view
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//子view在触摸范围内,在给它一个标志位
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent分发事件,如果child不为null,就调用child的dispatchTouchEvent方法,
//child为null就调用父类的dispatchTouchEvent方法。
//返回值就是子veiw是否处理事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//addTouchTarget方法给mFirstTouchTarget赋值,addTouchTarget指向当前的子view。
//所以如果所有子view都不消耗事件,mFirstTouchTarget就为null
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// mFirstTouchTarget == null表示没有子veiw消耗事件
if (mFirstTouchTarget == null) {
// 就把它当成一个普通的veiw来执行dispatchTransformedTouchEvent方法
//第三个参数传null,进去后会调用父类的dispatchTouchEvent方法,
//最终调用onTouchEvent方法来处理事件
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
//如果DOWN事件处理完毕
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
除了DOWN事件的其余事件分发给子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;
}
}
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
上面就是ViewGroup的事件分发的流程,重要部分都做了注释,总结一下就是,dispatchTouchEvent方法中,onInterceptTouchEvent方法是否拦截事件,默认不拦截,想要拦截我们需要重写此方法。如果拦截就通过dispatchTransformedTouchEvent方法调用自身的onTouchvent()方法,否则就还是通过dispatchTransformedTouchEvent方法调用子view的dispatchTouchEvent方法。如果子view还是ViewGroup,那么重复上面的调用流程,如果子view是View,那么执行View的dispatchTouchEvent方法。下面去看一下View的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent event) {
//焦点的判断
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// We have focus and got the event, then use normal event dispatch.
event.setTargetAccessibilityFocus(false);
}
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
//5.0以后的嵌套滑动
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//安全判断
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//我们是否设置了mOnTouchListener,如果设置了执行它的onTouch方法
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//执行onTouchEvent方法。到这里我们知道没如果我们设置了一个view的
//mOnTouchListener方法,那么先执行它的onTouch方法,返回false才执行onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
View的dispatchTouchEvent方法就简单多了,首先判断我们有没有设置OnTouchListener方法,如果设置了就执行它的Touch方法,只有Touch方法返回false的时候,才会继续去执行View的onTouchEvent方法。所以我们知道OnTouchListener的优先级高于onTouchEvent。
OK事件分发看完了,下面去View的onTouchEvent看一下事件的处理
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//是否是可点击的
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
//如果一个vew设置了点击事件或者长按事件,即使它是DISABLED的,
//也会消费这个事件,只是不响应。也就是这里直接返回true,但是下面的代码都不执行了。
return clickable;
}
//如果view设置了代理执行下面的方法
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//如果是可点击的,就开始处理ACTION_UP,ACTION_DOWN,ACTION_CANCEL,ACTION_MOVE事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// 按钮在我们之前就被释放了
// 按下显示。让它显示被压的
// 现在状态(在调度单击之前)以确保用户可以看到它。
setPressed(true, x, y);
}
//如果没有处理长按事件或者长按事件返回了false
//mHasPerformedLongPress在DOWN的时候置为false,在DOWN中
//监测是否有长按事件,如果有长按事件mHasPerformedLongPress会置为true
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 如果没有长按事件,移除掉长按的回调
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
//使用Runnable来执行一个点击事件,而不是直接执行
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
//是否处理长按事件
mHasPerformedLongPress = false;
if (!clickable) {
//监测长按事件
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// 是否在一个滚动容器中
boolean isInScrollingContainer = isInScrollingContainer();
// 如果是在容器中,发送一个100毫秒的延时post
//在其run方法中监测长按事件
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// 如果不在容器中,直接检查长按事件
//长按事件的监测,发送一个500毫秒的延时post,
//在run方法中如果检测到是长按就给上面的mHasPerformedLongPress标志位赋值为true
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
onTouchEvent方法中,只要CLICKABLE和LONG_CLICKABLE有一个为true就消费这个事件,在DOWN方法中处理长按事件,在UP方法中处理点击事件。LONG_CLICKABLE默认为false,CLICKABLE的值跟具体的view有关,比如Button默认是true,TextView默认是false。通过setClickable和setLongClickable可以改变这两个值。setOnClickListener和setOnLongClickListener会把这两个值设置为true。
OK,View的事件分发的源码查看完毕,看一下流程图
总结一下:
- 一个事件序列是从手指按下屏幕(ACTION_DOWN)开始,到手指离开屏幕(ACTION_UP)结束,中间有一系列的(ACTION_MOVE)事件,非人为的结束事件会走到ACTION_CANCEL中
- 正常情况下一个事件序列只能由一个View拦截消耗,因为某一个View一旦决定拦截事件,那么这个事件就只能由它来处理。并且它的onInterceptTouchEvent方法不会在被调用。
- 一旦一个View开始处理事件,如果它不消耗ACTION_DOWN事件,也就是onTouchEvent返回了false,那么同一个事件序列中的其他事件都不会再交给他处理,并且把事件重新交给它的父容器来处理。父容器的onTouchEvent方法会被调用。
- 事件的分发过程是由外到内的,事件总是先传递到父容器,在由父容器分发给子View,子View可以通过requestDisallowInterceptTouchEvent方法来干预父容器的执行,ACTION_DOWN事件除外,因为它会在ACTION_DOWN事件中清空标志位。
- ViewGroup中的onInterceptTouchEvent方法默认返回false,所有ViewGroup默认不拦截任何事件。而View中没有onInterceptTouchEvent方法,一旦有事件传递给它,它的onTouchEvent方法就会被调用。
- View的onTouchEvent方法默认返回true,也就是默认处理事件,除非它是不可点击的(clickable和longClickable都为false)。LONG_CLICKABLE默认为false,CLICKABLE的值跟具体的view有关,比如Button默认是true,TextView默认是false。通过setClickable和setLongClickable可以改变这两个值。setOnClickListener和setOnLongClickListener会把这两个值设置为true。
- View的enable属性不会影响onTouchEvent的默认返回值,即使它是enable状态,clickable和longClickable只要有一个为true,那么它的onTouchEvent就返回true。
- onClick会响应的前提是,View是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,当长按事件返回true的时候,onClick是不会响应的。
- onLongClick是在ACTION_DOWN中判断的,想要执行长按事件,longClickable需要为true。