前言
事件分发机制主要是为了处理用户触摸屏幕产生的事件,因为页面中所呈现的是多个view组合而成,那么具体由谁来处理这个事件就是个问题
知识点铺垫
触摸屏幕产生的事件(也就是事件分发的具体对象)
即MotionEvent是一个用于报告运动的事件,它包含触摸的动作、相对/绝对位置等
触摸状态 | 释义 |
---|---|
ACTION_DOWN | 按下 手势开始 |
ACTION_UP | 抬起 手势结束 |
ACTION_CANCLE | 取消 当前手势已终止 |
ACTION_MOVE | 移动 在down和up之间发生 |
消费事件的对象
Activity,ViewGroup,View 事件基本上就是从外向里再向外
消费事件的对象组成
怎么消费
这三个消费event的对象由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()这三个方法处理整个流程
- 流程图结合源码更容易理解
结合源码
一切的源头:Activity#dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//开始向下一级分发
// 会调用到Window的实现类PhoneWindow中
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//上面轮回一圈没人处理 就自己消费了
return onTouchEvent(ev);
}
PhoneWindow#superDispatchTouchEvent()
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor 就是DecorView
//DecorView本身继承自FrameLayout 这里调用的就是ViewGroup的 dispatchTouchEvent()
return mDecor.superDispatchTouchEvent(event);
}
ViewGroup#dispatchTouchEvent()
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/* 略过一些 */
// Check for interception.(检查拦截)
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);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//循环遍历子view 并通过dispatchTransformedTouchEvent()方法决定事件走向
for (int i = childrenCount - 1; i >= 0; i--) {
...
//获取到子view dispatch
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
//此处mFirstTouchTarget给赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
//上面分发后子View没有处理的话 此处就为null
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
...
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
...
}
//子视图空调用父布局
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
}
return handled;
}
真正的分发:ViewGroup#dispatchTransformedTouchEvent()
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);
//有子View就继续向下调用 没有就调用自身的
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
}
View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
...
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//上面判断了是否设置了onTouch事件 如果有就不会回调onTouchEvent事件
result = true;
}
//调用了自身的onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
View.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
...
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// 当处于按下状态时才有点击事件
if (!focusTaken) {
// 使用一个Runnable发送点击事件
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
...
}
break;
case MotionEvent.ACTION_DOWN:
...
if (!clickable) {
//检测是否长按事件 通过Runnable
checkForLongClick(0, x, y);
break;
}
...
break;
case MotionEvent.ACTION_CANCEL:
//重置/删除大部分状态
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
总结
- 事件起始由ACTION_DOWN 结束为ACTION_UP
- 整个流程是责任链模式 从上到下再到上(okhttp中拦截器也是这样)
- 事件传递优先级:onTouchListener.onTouch -> onTouchEvent -> onClickListener.onClick
- ViewGroup默认不拦截任何事件(返回false)
最后
- 之前看了很多次关于这方面的知识,但总是感觉断断续续,模模糊糊,所以这次做一个小小的总结,留给以后回顾,如果文中如果有什么纰漏欢迎讨论与指出。
- 参考
Android触摸事件传递机制
Android事件分发机制详解:史上最全面、最易懂
深入理解Android事件分发机制
图解 Android 事件分发机制