事件分发机制
概述
- 用户通过屏幕与手机交互的时候,每一次点击、长按、移动都是一个事件。
- 事件分发机制:某一个事件从屏幕传递到各个View,由View来使用这个事件(消费事件)或忽略这一事件(不消费事件),这整个流程的控制。
- 系统把事件封装到MotionEvent对象中,事件分发到过程就是MotionEvent分发到过程。
View继承关系
事件序列
每个事件都是从 ACTION_DOWN开始到ACTION_UP结束。
事件类型 | 具体动作 |
---|---|
MotionEvent.ACTION_DOWN | 按下 |
MotionEvent.ACTION_UP | 抬起 |
MotionEvent.ACTION_MOVE | 移动 |
MotionEvent.ACTION_CANCEL | 取消事件(事件被上层拦截时触发) |
当某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它处理,并且事件将重新由它的父View处理。
事件分发流程
Activity#dispatchTouchEvent() --> PhoneWindow#superDispatchToucheEvent() --> DecorView#superDispatchTouchEvent() --> ViewGroup#dispatchTouchEvent() --> View#dispatchTouchEvent() --> View#onTouchEvent()
事件分发机制伪代码
public boolean dispatchTouchEvent(MotionEvent event) {
//是否消费事件
boolean isConsume = false;
if (isViewGroup) {
//如果是ViewGroup
if (onInterceptTouchEvent(event)) {
//如果onInterceptTouchEvent()返回true,表示当前View拦截事件,交给当前View的onTouchEvent()处理
isConsume = onTouchEvent(event);
} else {
//如果onInterceptTouchEvent()返回false,该事件会传递给子View处理,子View的dispatchTouchEvent()会被调用
isConsume = child.dispatchTouchEvent(event);
}
} else {
//如果是View,交给View的onTouchEvent()处理
isConsume = onTouchEvent(event);
}
return isConsume;
}
//如果onTouchEvent()不消费返回false,则交给父View的onTouchEvent()
public void handleTouchEvent(MotionEvent event) {
if (!onTouchEvent(event)) {
getParent.onTouchEvent(event);
}
}
事件分发核心方法
dispatchTouchEvent(MotionEvent ev)
分发事件,当事件传递到当前View时,该方法会被调用
- 默认返回super,表示会继续分发该事件
- 当返回true时,表示当前View消费事件,不传递给其他View
- 当返回false时,表示停止分发事件,交由上层控件的
onTouchEvent()
处理
onInterceptTouchEvent(MotionEvent ev)
拦截事件,ViewGroup专有方法,在dispatchTouchEvent()
中调用
- 默认返回super,这时有两种情况:
- 第一种:ViewGroup存在子View,并且点击到该子View,这时不会拦截并分发给子View处理,相当于返回false
- 第二种:ViewGroup没有子View,或有子View但是没有点击子View,这时事件交由ViewGroup的
onTouchEvent()
处理,相当于返回true - LinearLayout/RelativeLayout/FrameLayout等ViewGroup默认不拦截,而ScrollView/ListView等ViewGroup则可能拦截
- 当返回true时,表示拦截事件,交给当前View的
onTouchEvent()
处理,事件一旦被拦击,后续的move、up事件都直接交给onTouchEvent()
,不会再重新询问是否拦截,即不再调用onInterceptTouchEvent()
- 当返回false时,表示事件不拦截,分发到子View
onTouchEvent(MotionEvent event)
消费事件,在dispatchTouchEvent()
中调用,可以通过不同场景定制触摸反馈算法
-
默认返回super,这时分两种情况:
- 如果当前View的clickable或longClickable为true,表示消费该事件,相当于返回true
- 如果当前Viewclickable和longClickable同时为false,表示不消费该事件,事件将向上级控价传递,相当于返回false
-
当返回true时,表示当前View消费该事件
-
当返回false时,表示当前View不处理事件,事件会被传递给上层控件的
onTouchEvent()
处理 -
requestDisallowInterceptTouchEvent(boolean disallowIntercept)
:是ViewGroup专有方法,用于做事件拦截的,一般在子View中调用
onTouch/onTouchEvent/onClick执行顺序
onTouch()
↓
onTouchEvent()
↓
onClick()
当onInterceptTouchEvent()
被调用,表示当前View要消费事件。
如果设置setOnTouchListener()
,则onTouch(
)会被调用,如果onTouch()
返回true时,onTouchEvent()
不会被调用;如果返回false或没有设置setOnTouchListener()
时,会调用onTouchEvent()
。
onClick()
在设置setOnClickListener()
会被调用。
源码分析
Activity事件分发流程
Activity & Window & DecorView关系
- 一个Activity包含一个Window,Window由PhoneWindow类来实现的
- 每个PhoneWindow对应一个DecorView,将DecorView做为整个应用窗口的根视图
- DecorView是FrameLayout的子类,而这个DecorView又将屏幕划分为两个区域一个是TitleView一个是ContentView,而我们平常做应用所写的布局正是展示在ContentView中的。
Activitty#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
//一般事件序列从DOWN事件开始,所以一直为true
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//事件传递到PhoneWindow#superDispatchTouchEvent()处理
/*
如果PhoneWindow#superDispatchTouchEvent()返回true,表示分发该事件;
如果返回false,则交给Activity#onTouchEvent()处理
*/
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
PhoneWindow#superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
//PhoneWindow将事件传递给DecorView,mDecor是DecorView的实例对象
return mDecor.superDispatchTouchEvent(event);
}
DecorView#superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
//最终交给ViewGroup#dispatchTouchEvent()处理
return super.dispatchTouchEvent(event);
}
Activity#onTouchEvent()
//当一个事件未被Activity下的任何View处理时,将调用Activity#onTouchEvent()处理
public boolean onTouchEvent(MotionEvent event) {
// Activity为Dialog时
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
View事件分发流程
View#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
/*
当mOnTouchListener!=null,会先执行onTouch(),如果返回true,则不执行onTouchEvent;
如果返回false,则执行onTouchEvent()
*/
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)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
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();
/*
CLICKABLE/LONG_CLICKABLE/CONTEXT_CLICKABLE:可单击、可长按、上下文点击,至少满足一个,clickable为true
clickable用于判断是否可以点击
如果View是被禁用状态DISABLED,clickable为true时会消耗事件
*/
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;
}
/*
mTouchDelegate不为null,则消费该事件
*/
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
/*
核心代码:处理事件逻辑代码
*/
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//处理UP事件
//如果不可点击时,状态全部重置
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)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
//处理DOWN事件
//判断是否为手指触摸
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
//如果不可点击时,检查是否为长按事件
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
/*
判断是否在滑动控件里
如果在滑动控件里,则延迟响应down事件
如果不在滑动控件里,则标记按下状态
*/
//是否为滑动控件
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 {
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
//处理CANCEL事件
//重置状态
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
//处理MOVE事件
/*
如果手指移出界时,状态全部重置
*/
if (!pointInView(x, y, touchSlop)) {
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
//如果用力按下时,会触发长按
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false;
}
//处理点击事件,在up事件时才会触发
private boolean performClickInternal() {
notifyAutofillManagerOnClick();
return performClick();
}
//执行点击事件
public boolean performClick() {
notifyAutofillManagerOnClick();
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;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
ViewGroup事件分发流程
ViewGroup#dispatchTouchEvent()
-
判断是否拦截,如果down事件发生时或mFirstTouchTarget!=null,会执行
onInterceptTouchEvent()
,如果ViewGroup消费该事件,mFirstTouchTarget会设置为null -
mFirstTouchTarget是提供给move和up事件使用的
-
当View一旦开始拦截,那么后续事件就全部交给它处理,不再执行
onInterceptTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;
//过滤安全事件
if (onFilterTouchEventForSecurity(ev)) {
/*
如果接收down事件,说明是一次新的事件序列
清空mFirstTouchTarget引用、重置状态
*/
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
/*
判断是否拦截
当发生down事件,且mFirstTouchTarget!=null,down事件被child消费
*/
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//disallowIntercept由requestDisallowInterceptTouchEvent()设置
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//默认情况会进入该方法
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
//消费了down事件,后续事件也被拦截
intercepted = true;
}
//检查是否为cancel事件
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
}
}
- 当ViewGroup不拦截时,会执最终行
child.dispatchTouchEvent(event)
将事件分发给子View
//不取消和不拦截时调用
if (!canceled && !intercepted) {
//遍历所有子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 (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//如果ViewGroup没有子View,则调用super.dispatchTouchEvent()
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 {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
}
if (mFirstTouchTarget == null) {
//分发给自己的onTouchEvent()
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
} else {
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
continue;
}
}
}
}
ViewGroup#onInterceptTouchEvent()
//onInterceptTouchEvent()默认返回false,不进行拦截
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
总结
事件由上而下的传递规则
事件产生后先由Activiyt处理,传递给Window再传递给顶层的ViewGroup,一般事件传递只考虑ViewGroup的onInterceptTouchEvent()
,因为我们不会重写dispatchTouchEvent()
。
对于根ViewGroup,事件首先传递给它的dispatchTouchEvent()
,如果该ViewGroup的onInterceptTouchEvent()
返回true,则表示它要拦截这个事件,事件会由它的onTouchEvent()
处理,如果onInterceptTouchEvent()
返回false,则表示不拦截事件,事件交给它的子View的dispatchTouchEvent()
处理,如此反复,最终传递到最底层的View,一般情况最终会调View的onTouchEvent()
。
事件由下而上的传递规则
事件传到最底层的View,如果它的onTouchEvent()
返回true,则表示消费事件,如果返回false则表示该View不处理,事件则会传递给父View的onTouchEvent()
处理,如果父View的onTouchEvent()
仍然返回false,则继续往上传递,如此反复。
常见问题
onTouch & onTouchEvent & onClick关系
- 如果mOnTouchListener不为null,会执行
mOnTouchListener.onTouch()
,返回值为true时,表示消费该事件 - 如果mOnTouchListener为null,或者
mOnTouchListener.onTouch()
返回值为false,会调用View#onTouchEvent()
,返回值为true时,表示消费该事件 View#onTouchEvent()
处理up事件时,会调用onClick()