手把手教你跟踪源码!本篇文章内容较长,建议收藏小王同学网站浏览。
事件分发机制在面试中的重要性
在 Android 开发面试中,事件分发机制是 Android 系统的核心部分,掌握这部分内容是衡量一个开发者是否具备扎实基础的重要标准。所以面试官为什么每次都问???为什么你在开发中滑动冲突处理不了??为什么自己写的自定义View的点击事件有很多BUG?说明你还是没掌握,今天我们就再次来讲解一下Android的事件分发机制!
事件分发的本质是什么?
Android 事件分发的本质是系统如何将触摸事件传递给合适的视图(View)进行处理。
即 事件传递的过程 = 触摸事件的分发过程。
事件在哪些对象之间进行传递?
答:Activity、ViewGroup、View。Android
的UI
界面由Activity
、ViewGroup
、View
及其派生类组成
Activity、ViewGroup、View 的区别
事件分发机制中的关系
1个点击事件发生后,事件先传到Activity
、再传到ViewGroup
、最终再传到 View
好了,废话不多说,我们开始研究源码。
Activity的事件分发
public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果触摸事件是ACTION_DOWN
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 开始分发事件的起点!!将触摸事件分发给当前窗口的根视图,判断是否已处理事件
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果根视图没有处理事件,调用Activity的onTouchEvent()方法处理
return onTouchEvent(ev);
}
为什么从Activity 作为事件传递的入口?
我们先要知道一个点,就是系统把点击事件传递到Activity就是通过调用Activity的dispatchTouchEvent方法,与其说是传递,也就是调用该方法。
Activity
是应用界面管理的核心,它持有 Window
对象。Window
包含了整个视图层次结构的根视图。因此,Activity
自然成为了事件传递的入口。
onUserInteraction():
当接收到
ACTION_DOWN
事件时,调用onUserInteraction()
方法,通知Activity
有用户交互发生。
superDispatchTouchEvent(ev):
调用
getWindow().superDispatchTouchEvent(ev)
将事件传递给Window
对象的根视图。Window
会将事件传递给视图层次结构中的各个视图。
onTouchEvent(ev):
如果事件没有被视图层次结构中的任何视图处理,调用
Activity
自身的onTouchEvent(ev)
方法进行处理。这通常是作为最终的兜底处理。
我们点进去superDispatchTouchEvent
方法,发现是一个抽象方法
//Window类,是抽象类
public abstract boolean superDispatchTouchEvent(MotionEvent event);
按住control点击这个方法,卧槽,根本点不动,想知道谁实现这个方法,此时怎么办呢?别急,我们到Window顶部看一下
注释清晰的写着,翻译过来就是:其唯一实现类 :android.view.PhoneWindow类,好了,那我们就去找PhoneWindow
哈哈哈一下就找到了。这时候内心喊一句:哪里跑~我们继续点击mDecor.superDispatchTouchEvent
继续追进去!!
哈哈终于找到了你了吧,到这里豁然开朗,居然调用到了ViewGroup的分发事件方法,其实这就完成了把点击事件传递到了ViewGroup的过程。分发到ViewGroup去了,此时我们点进来看下,ViewGroup的dispatchTouchEvent代码很长很长,别急,我们到这里先缓一缓,我们先回头看看我们走过的路,总结一下再继续。
那回归到Activity的dispatchTouchEvent方法:
//Activity的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//这个判断会耗时,很久才返回
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
也就是说,当我们一点击屏幕,就触发dispatchTouchEvent,然后第一个判断我们不需要看,看第二个if,getWindow().superDispatchTouchEvent(ev)
这一句代码就开始往下分发,如果getWindow().superDispatchTouchEvent(ev)
返回true,那么说明被ViewGroup
或者View
消费掉了,如果返回false,那么就没有被ViewGroup或者View消费,接着还是调用Activity的onTouchEvent
方法。
总结来说就是如果Activity所包含的视图拦截或者消费了该触摸事件的话,就不会再执行Activity的onTouchEvent();
如果Activity所包含的视图没有拦截或者消费该触摸事件的话,则会执行Activity的onTouchEvent()。
Activity中的onTouchEvent是Activity自身对触摸事件的处理。
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
此时,如果底层的View没有消费事件,最终会走到Activity的onTouchEvent方法,我们看一下onTouchEvent
方法,它调用了mWindow.shouldCloseOnTouch(this, event)
,我们再点进去看看。
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
// 判断事件是否为 ACTION_UP 并且在边界之外,或者事件为 ACTION_OUTSIDE
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
// 如果允许点击外部关闭窗口,并且装饰视图不为空,并且事件在外部
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
return true; // 返回 true,表示应该关闭
}
return false; // 否则返回 false,表示不关闭
}
代码比较简单。它会先调用mWindow.shouldCloseOnTouch(),如果shouldCloseOnTouch()返回true,则意味着该触摸事件会触发"结束Activity"的动作。就调用finish()来结束Activity,并返回true,表示Activity消费了这个触摸事件。否则的话,就返回false。
最后我们总结一下:
1. 开始 (用户触碰屏幕)
• 用户在屏幕上触摸,触摸事件被触发。
2. Activity.dispatchTouchEvent()
• 触摸事件首先传递到当前 Activity 的 dispatchTouchEvent() 方法。
3. getWindow().superDispatchTouchEvent(ev)
• 在 Activity.dispatchTouchEvent() 方法内部,调用 Window 的 superDispatchTouchEvent() 方法。
4. PhoneWindow.superDispatchTouchEvent()
• 在 Window 的 superDispatchTouchEvent() 方法内部,调用 PhoneWindow 的 superDispatchTouchEvent() 方法。
5. mDecor.superDispatchTrackballEvent(event)
• 在 PhoneWindow.superDispatchTouchEvent() 方法内部,调用 DecorView 的 superDispatchTrackballEvent() 方法。
6. ViewGroup.dispatchTouchEvent()
• 在 DecorView.superDispatchTrackballEvent() 方法内部,调用 ViewGroup 的 dispatchTouchEvent() 方法。
7. 判断 ViewGroup.dispatchTouchEvent() 返回值
• 如果 ViewGroup.dispatchTouchEvent() 返回 true:
• 表示事件被消费,Activity.dispatchTouchEvent() 返回 true,流程结束。
• 如果 ViewGroup.dispatchTouchEvent() 返回 false:
• 表示事件未被消费,继续向下传递。
8. Activity.onTouchEvent()
• 调用 Activity 的 onTouchEvent() 方法处理未被消费的事件。
9. mWindow.shouldCloseOnTouch(this, event)
• 调用 Window 的 shouldCloseOnTouch() 方法,根据触摸事件决定是否关闭窗口。
10. 结束
• 事件处理完成,整个事件分发过程结束。
那么,ViewGroup
的dispatchTouchEvent()
什么时候返回true
/ false
?请继续往下看ViewGroup事件的分发机制
ViewGroup的事件分发
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
······代码省略······
// 判断是否需要拦截事件
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
intercepted = !disallowIntercept && onInterceptTouchEvent(ev);
ev.setAction(action); // 恢复action,以防它被改变
} else {
intercepted = true;
}
// 更新目标可访问性焦点
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// 判断是否需要取消事件
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0 && ev.getSource() != InputDevice.SOURCE_MOUSE;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// 分配目标子视图
if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)) {
final int actionIndex = ev.getActionIndex();
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);
final View[] children = mChildren;
// 找到可以接收事件的子视图
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
//2. 判断当前遍历的子View能否接收到事件
if (!child.canReceivePointerEvents() || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//就是这里!!!!
//调用 dispatchTransformedTouchEvent 方法,将触摸事件分发给特定的子视图。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = i;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
// 没有找到新的触摸目标,分配给现有触摸目标
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// 事件分发
// 没有子视图被标记为当前触摸事件的目标
if (mFirstTouchTarget == null) {
//调用 dispatchTransformedTouchEvent 方法,将触摸事件分发给当前 ViewGroup 自己(而不是子视图)。
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;
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;
}
这段代码实现了复杂的事件分发逻辑,确保触摸事件能够在 ViewGroup 和其子视图之间正确地分发和处理。核心逻辑包括:
1. 判断是否拦截事件。
2. 分发事件给 ViewGroup 或其子视图。
分发事件给 ViewGroup 或其子视图的是调用的dispatchTransformedTouchEvent方法,我们下面看下这个方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// 获取事件的原始动作类型
final int oldAction = event.getAction();
// 如果当前事件是取消事件或者动作类型是 ACTION_CANCEL
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
// 将事件的动作类型设置为 ACTION_CANCEL
event.setAction(MotionEvent.ACTION_CANCEL);
// 如果 child 为 null
if (child == null) {
// 调用父类的 dispatchTouchEvent 方法分发事件
handled = super.dispatchTouchEvent(event);
} else {
// 这里这里!!!
// 调用子View 视图的 dispatchTouchEvent 方法分发事件
handled = child.dispatchTouchEvent(event);
}
// 将事件的动作类型恢复为原始动作类型
event.setAction(oldAction);
// 返回事件是否被处理
return handled;
}
// 省略的代码
}
调用子View的dispatchTouchEvent后是有返回值的
若该控件可点击,那么点击时dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
即该子View把ViewGroup的点击事件消费掉了
下面我们画图总结一下ViewGroup的流程:
开始 (用户触碰屏幕)
• 用户在屏幕上触摸,触摸事件被触发。
2. ViewGroup.dispatchTouchEvent()
• 触摸事件首先传递到 ViewGroup 的 dispatchTouchEvent() 方法。
3. ViewGroup.onInterceptTouchEvent()
• 在 dispatchTouchEvent() 方法内部,调用 ViewGroup 的 onInterceptTouchEvent() 方法判断是否拦截事件。
4. 判断是否拦截事件
• 根据 onInterceptTouchEvent() 的返回值进行判断:
• false: 不拦截事件,允许事件继续向子 View 传递。
• true: 拦截事件,不允许事件继续向子 View 传递。
5. 事件未被拦截 (false 分支)
• 寻找可处理事件的子 View
• 遍历子 View,寻找可以处理当前触摸事件的子 View。
• 找到处理事件的子 View
• 调用找到的子 View 的 dispatchTouchEvent() 方法,将事件传递给该子 View。
• 事件传递给子 View 后,由子 View 处理。
6. 事件被拦截 (true 分支)
• 不允许事件继续向子 View 传递
• 调用当前 ViewGroup 的 dispatchTouchEvent() 方法来处理事件,而不是向子 View 传递。
• 自己处理该事件
• 当前 ViewGroup 自己处理事件,处理完成后结束。
7. 结束
• 事件处理完成,整个事件分发过程结束。
View的事件分发
从上面ViewGroup
事件分发机制知道,View
事件分发机制从dispatchTouchEvent()
开始
源码分析
public boolean dispatchTouchEvent(MotionEvent event) {
······省略的代码·······
// 安全性过滤触摸事件
if (onFilterTouchEventForSecurity(event)) {
// 如果视图已启用并且处理滚动条拖动事件
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// (mViewFlags & ENABLED_MASK) == ENABLED:这是为了检查视图是否处于启用状态。
// mViewFlags:通常包含视图的各种标志位,包括启用状态等。
// ENABLED_MASK:这是一个掩码,用于提取启用状态位。
// ENABLED:表示视图处于启用状态的常量值。
// (mViewFlags & ENABLED_MASK) == ENABLED:这个条件用于判断视图是否处于启用状态。
// handleScrollBarDragging(event):这是一个方法,检查和处理滚动条拖动事件。
// 如果 handleScrollBarDragging(event) 返回 true,则表示触摸事件被处理为滚动条拖动事件。
// result = true;:如果上述条件都满足,表示事件已经被处理,设置 result 为 true。
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 如果有触摸监听器并且视图已启用,处理触摸事件
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// ListenerInfo li = mListenerInfo;:获取视图的监听器信息。
// mListenerInfo:包含视图的监听器信息,包括触摸监听器等。
// li:局部变量,指向视图的监听器信息。
// li != null && li.mOnTouchListener != null:检查是否有触摸监听器。
// li != null:确认视图有监听器信息。
// li.mOnTouchListener != null:确认监听器信息中有触摸监听器。
// (mViewFlags & ENABLED_MASK) == ENABLED:再次检查视图是否处于启用状态。
// li.mOnTouchListener.onTouch(this, event):调用触摸监听器的 onTouch 方法处理事件。
// 如果 onTouch 返回 true,表示事件已被处理。
// result = true;:如果上述条件都满足,表示事件已经被触摸监听器处理,设置 result 为 true。
// 如果没有被触摸监听器处理,则调用 onTouchEvent 处理
if (!result && onTouchEvent(event)) {
result = true;
}
// !result:检查前面是否已经处理了事件。如果 result 为 false,表示事件还没有被处理。
// onTouchEvent(event):调用视图的 onTouchEvent 方法处理事件。
// 如果 onTouchEvent 返回 true,表示事件已被处理。
// result = true;:如果上述条件都满足,表示事件已经被 onTouchEvent 处理,设置 result 为 true。
}
······省略的代码·······
return result;
}
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX(); // 获取触摸点的X坐标
final float y = event.getY(); // 获取触摸点的Y坐标
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
&& (mPrivateFlags4 & PFLAG4_ALLOW_CLICK_WHEN_DISABLED) == 0) {
// 如果触摸事件是ACTION_UP并且视图当前是按下状态,取消按下状态
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// 清除手指按下标志位
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// 返回是否可点击
return clickable;
}
// 如果有TouchDelegate,优先由TouchDelegate处理触摸事件
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 如果视图是可点击或者有工具提示
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
// 如果视图没有获取到焦点,执行点击操作
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick(); // 创建PerformClick对象
}
// 尝试延迟执行点击操作
if (!post(mPerformClick)) {
******************
* 直接执行点击操作 *
******************
performClickInternal();
}
}
break;
case MotionEvent.ACTION_DOWN:
// 如果在滚动容器中
if (isInScrollingContainer) {
// 设置预按下标志位
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
// 创建CheckForTap对象
mPendingCheckForTap = new CheckForTap();
}
// 记录触摸点的X坐标
mPendingCheckForTap.x = event.getX();
// 记录触摸点的Y坐标
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:
if (clickable) {
setPressed(false); // 取消按下状态
}
removeTapCallback(); // 移除点击回调
removeLongPressCallback(); // 移除长按回调
mInContextButtonPress = false; // 重置上下文按钮按下状态
mHasPerformedLongPress = false; // 重置长按已执行标志位
mIgnoreNextUpEvent = false; // 重置忽略下次抬起事件标志位
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; // 清除手指按下标志位
break;
case MotionEvent.ACTION_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, // 立即发送
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true; // 触摸事件已处理
}
return false; // 触摸事件未处理
}
这个大家再熟悉不过了吧,我们在实现按钮的点击的时候就会实现这个回调方法,终于找到是哪里出发的了。只要通过setOnClickListener()为控件View注册1个点击事件,那么就会给mOnClickListener变量赋值(即不为空),则会往下回调onClick()返回true
1. 开始:当用户触摸屏幕时,这个过程开始。
2. View.dispatchTouchEvent():首先调用View类的dispatchTouchEvent()方法来分发触摸事件。
3.
如果开发者注册了onTouch方法:
• 如果开发者实现的onTouch方法返回false(表示没有消费该事件),则继续往下传递。
• 如果开发者实现的onTouch方法返回true,则停止事件的传播,并且不会触发onClick事件。
如果开发者没有注册onTouch方法:
• onTouchEvent():如果开发者没有注册onTouch()方法,那么这个方法会被调用。在这个方法中,开发者可以根据触摸事件的具体情况来决定是否消耗掉这个事件。
4.
• performClick():如果onTouchEvent()方法返回true,表示事件被消费了,那么系统会自动调用performClick()方法来触发点击事件。
• onClick():如果事件被消费,不再往下传递,那么就会触发onClick()方法。
• 结束:整个触摸事件处理流程结束。
onTouch,onTouchEvent,onClick优先级,关系(字节面试)
onTouch:指的是View设置的OnTouchListener接口的onTouch()方法
onTouchEvent:指的是事件分发中的重要方法(dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent)
onClick:指的是View设置的OnClickListener接口的onClick()方法
这里需要特别注意的是,onTouch()
的执行 先于 onClick()
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
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;
}
其中mOnTouchListener指的就是我们通过 setOnTouchListener()设置的接口,可以知道如果onTouch()返回true的话,事件就被消化,onTouchEvent()就不会收到事件了,所以onTouch比onTouchEvent优先级高,从中可以看出在onTouchEvent()中,调用了我们设置的OnClickListener()接口中的onClick(),所以onTouchEvent优先级高于onClick
优先级从高到低:onTouch>onTouchEvent>onClick
dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent三者关系
下面,我用一段伪代码来阐述上述3个方法的关系 & 事件传递规则
// 点击事件产生后
// 步骤1:调用dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false; //代表 是否会消费事件
// 步骤2:判断是否拦截事件
if (onInterceptTouchEvent(ev)) {
// a. 若拦截,则将该事件交给当前View进行处理
// 即调用onTouchEvent()去处理点击事件
consume = onTouchEvent (ev) ;
} else {
// b. 若不拦截,则将该事件传递到下层
// 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程
// 直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
// 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)
return consume;
}
总结
到现在为止,Activity中关于触摸事件的代码就全部分析完了。总结来说:
事件分发的顺序为:
事件产生:
当你在屏幕上进行触摸操作,产生一个
MotionEvent
对象。
Activity 接收:
事件从
WindowManager
传递给当前的Activity
,Activity
调用dispatchTouchEvent()
方法开始分发事件。
根视图分发:
Activity
将事件传递给根视图(通常是一个ViewGroup
),根视图的dispatchTouchEvent()
方法接收事件。
ViewGroup 分发:
ViewGroup
调用dispatchTouchEvent()
方法,将事件传递给子视图或调用onInterceptTouchEvent()
方法决定是否拦截事件。
拦截事件:
如果
ViewGroup
的onInterceptTouchEvent()
返回true
,表示拦截事件,ViewGroup
自己处理事件;如果返回
false
,事件继续传递给子视图。
子视图处理:
子视图(可能是另一个
ViewGroup
或View
)接收事件并调用自己的dispatchTouchEvent()
方法。
具体视图处理:
最终由具体的
View
调用onTouchEvent()
方法处理事件。
事件消费:
视图通过返回
true
或false
表示是否消费了该事件。如果返回true
,表示事件已被处理,不会继续向上传递;如果返回
false
,事件将返回到上一级视图的onTouchEvent()
方法进行处理,直到最终被处理或丢弃。
评论区