前言
Android事件分发机制是Android开发者必须了解的基础,本文将对Android事件分发机制的原理进行解析。
文章较长,阅读需要较长时间,建议收藏等充足时间再进行阅读。
事件分发涉及的事件类型
事件类型 | 具体操作 |
---|---|
MotionEvent.ACTION_DOWN | 手指刚接触屏幕(所有事件的开始) |
MotionEvent.ACTION_UP | 手指从屏幕松开 |
MotionEvent.ACTION_MOVE | 手指在屏幕上滑动 |
MotionEvent.ACTION_CANCEL | 非人为因素取消 |
Andorid事件分发实际上就是当手触摸屏幕后,产生的一系列的ACTION_DOWN、ACTION_UP、ACTION_MOVE、ACTION_CANCEL事件,因为ACTION_UP、ACTION_MOVE流程基本相同,并且是建立在ACTION_DOWN事件的基础上,所以本文我们将着重分析ACTION_DOWN事件。
事件分发的流程是如下图的U型结构。事件分发实际上就是dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三个方法的参与,dispatchTouchEvent负责事件的分发工作,onInterceptTouchEvent表示是否拦截Event事件,注意到是onInterceptTouchEvent方法只存在于ViewGroup中,onTouchEvent方法就是负责具体消费事件。
事件分发模型.png1.Activity事件分发
当我们点击屏幕是就会产生Event事件,此时会调用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()方法,该方法的作用是每当Key,Touch,Trackball事件分发到当前Activity就会被调用。如果你想当你的Activity在运行的时候,能够得知用户正在与你的设备交互,你可以override该方法。
紧接着调用了getWindow().superDispatchTouchEvent(ev)方法,可以知道Window的唯一子类是PhoneWindow。
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
进而调用了mDecor的superDispatchTouchEvent方法。DecorView的父类FrameLayout并没有该方法,再往里面找,最终调用的是ViewGroup的dispatchTouchEvent方法。
至此,Activity的事件分发已经走完。
2.ViewGroup事件分发
Activity如果没有重写dispatchTouchEvent,会进入ViewGroup的分发流程。
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
调用cancelAndClearTouchTargets(ev)和resetTouchState()对TouchTarget进行置空操作。
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
}
disallowIntercept的赋值是通过mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0。对源码分析发现mGroupFlags的赋值是通过requestDisallowInterceptTouchEvent方法,而requestDisallowInterceptTouchEvent调用是在子控件中设置的
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
这里需要注意,onInterceptTouchEvent方法只能拦截DOWN以外的事件,因为DOWN事件会进行置空操作。intercepted = true; 表示拦截或者没有子控件能够消费事件。
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;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
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();
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;
}
}
}
如果没有取消并且没有拦截,倒序遍历控件,判断点击事件是否在ViewGroup子控件的集合里面,调用children,canViewReceivePointerEvents(child)&&!isTransformedTouchPointInView(x, y, child, null)是否能够接收或者在控件内部,如果满足条件,调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign),这里是先分发给子控件,没有子控件或子控件没有消费事件的情况下,才调用dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS)交由自己处理。
3.View事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
...
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;
}
}
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;
}
对代码分析可以看到,mOnTouchListener.onTouch(this, event)优先级高于onTouchEvent(event),onTouch方法实际上是一个抽象方法,当你在代码中设置setOnTouchListener时重写,如果返回true,则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;
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
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);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
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();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
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);
}
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;
}
如果OnTouchListener为空或者返回false,则执行onTouchEvent方法。
首先检测View是否是clickable,以及是否禁用等逻辑,如果控件可用可点击的状态下,ACTION_UP中执行了performClick()方法,如果设置了mOnClickListener监听,则会触发onClick事件。至此事件分发结束。
问:onLongClick和onClick在什么时候执行和是否能够一起执行?
onLongClick在ACTION_DOWN检测并触发触发,onClick在ACTION_UP中触发,所以长按事件是先于点击事件执行的,长按事件的执行流程是先调用checkForLongClick(0, x, y),DEFAULT_LONG_PRESS_TIMEOUT = 500,一般经过500ms后会触发performLongClick(mX, mY)方法执行长按事件。至于是否能够同时执行,只需要onLongClick返回false,则长按和点击事件就可以同时执行。
网友评论