[toc]bash
在进行正文以前,咱们带着如下几个问题有目的的进行,而后最后再作问题的解决。app
首先咱们要清楚,事件分发的对象是什么?其实就 MotionEvent,这个 MotionEvent 能够有 ACTION_DOWN,ACTION_UP,ACTION_MOVE,ACTION_CANCEL 等事件类型。ide
Activity -> Window -> ViewGroup -> View函数
关于事件的走向,我觉的如下这张图能够很清晰的看出事件的最终走向,该图来自Kelin 源码分析
如下是使用文字对事件走向的描述,帮助对流程图的理解。布局
方法 | Activity | ViewGroup | View |
---|---|---|---|
dispatchTouchEvent | (1) return false 或者是 return true,表示 Activity 已经消费 (2) return 父类的 super.dispatchTouchEvent() 表示向下传递,最后结果由下一级决定 |
(1) return false,表示 ViewGroup 不消费事件,返回给上一级的 ViewGroup 或者 Activity 的 onTouchEvent 进行处理; (2) return true,表示当前 ViewGroup 已经消费了事件,事件在此终止; (3) return super.dispatchTouchEvent(),表示事件继续,根据 onInterceptTouchEvent 判断是否本身处理事件(是否调用 onTouchEvent) |
(1) return false,表示 View 不消费事件,返回给上一级的 ViewGroup onTouchEvent 进行处理; (2) return true,表示当前 View 已经消费了事件,事件在此终止; (3) return super.dispatchTouchEvent(),表示事件继续,并调用本身的方法 onTouchEvent |
onInterceptTouchEvent | 没有此方法 | (1) return true,表示 ViewGroup 拦截该事件;以后 onInterceptTouchEvent 不会再被调用 (2) return false 或者 return super.onInterceptTouchEvent 表示不拦截事件,将事件交给子 view 处理。 |
没有此方法 |
onTouchEvent | (1) return false,表示全部的 ViewGroup 和 View 都不消费事件,Activity 也不消费; (2) return true,表示消费该事件,事件在此终止。 |
(1) return false 或者是 return super.onTouchEvent 表示不消费事件,返回给上一级的 onTouchEvent 处理 (2) return true 表示当前 ViewGroup 本身消费了,事件在此终止,不会往上传也不会往下传了。 |
(1) return false 或者是 return super.onTouchEvent 表示不消费事件,返回给上一级的 ViewGroup 进行护理; (2) return true 表示当前 view 进行处理事件,事件在此终止 |
一旦事件产生,那么首先被调用的是 Activity 中的 dispatchTouchEvent 方法。咱们来看看这个方法的实现。post
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
复制代码
首先,若是是 ACTION_DOWN,那么 onUserInteraction 方法就会被调用,onUserInteraction 是个空的方法,当事件产生时,那么这个方法就会被调用,若是在 activity 运行的时候,咱们想要知道用户和设备的交互,那么咱们就能够实现这个方法。ui
接着 window 中的 superDispatchTouchEvent 方法被调用,事件传递到 window 中。Window 是个抽象类,他的惟一实现是 PhoneWindow。在 Window.superDispatchTouchEvent 中调用了 DecorView 的 superDispatchTouchEvent,而这个 DecorView 就是咱们在 activity 中经过调用 setContentView 设置的布局的顶层 View。this
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
复制代码
最后,若是 Activity 中的全部下级事件承载对象没有处理事件,最后 Activity 中的 onTouchEvent 就会被调用,当事件超出边界或者事件为 ACTION_DOWN 时,mWindow.shouldCloseOnTouch(this, event) 为 true,onTouchEvent 默认是返回 false 的。spa
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
复制代码
onTouchEvent 方法 当全部的 view 都没有消费事件的时候,activity 的 onTouchEvent() 就会被调用
咱们这边看一下 getWindow() 里面的实现,其实也就是返回一个 Window 实例
public Window getWindow() {
return mWindow;
}
复制代码
Window 在事件分发的过程当中就相似于一个中间的桥接同样,是没有作什么操做的,只是将事件传递给 DecorView 中。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
复制代码
void dispathTouchEvent(Event event){
boolean consume = false;
if(OnInterceptTouchEvent(event)){
consume = onTouchEvent(event);
}else{
consume = childView.dispatchTouchEvent(event);
}
return consume;
}
复制代码
对于一个 ViewGroup 来讲,点击事件产生以后,dispatchTouchEvent 就会被调用,若是这个 ViewGroup 的 onInterceptTouchEvent 返回 true 就表示它要拦截当前的事件,接着事件就会给这个 ViewGroup 处理,即它的 onTouchEvent 会被调用;若是 ViewGroup 的 onInterceptTouchEvent 返回 false,就表示它不拦截事件,这时当前事件就会被传递给它的子 view,接着调用子元素的 dispatchTouchEvent 方法就会被调用,如此反复,直到事件被最终处理。
// 省略部分代码
// 若是是 ACTION_DOWN 事件,重置标志位 mGroupFlags 为 非 FLAG_DISALLOW_INTERCEPT,这个标志位关系到 ViewGroup 的 onInterceptTouch 是否有效。
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 处理条件为:
// 1. 事件为 ACTION_DOWN
// 2. 有下级的 View 处理事件
// 判断拦截是否失效?mGroupFlags = FLAG_DISALLOW_INTERCEPT 时,onInterceptTouchEvent 是不会被调用的
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 非 ACTION_DOWN 事件且没有其余的下级处理该事件的时候,不会再调用 onInterceptTouchEvent
intercepted = true;
}
// 正常事件分发
// 若是 ViewGroup 决定拦截或者已经有 子 view 处理事件,那么就开始正常的事件分发流程
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(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 childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
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);
// 找到接收该事件的子 View 上,若是找到,则直接跳出循环
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// 判断事件是否落在子 view 上,若是是,则跳出循环
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;
}
// The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } } } 复制代码
从以上的代码中能够看出,当事件传递到 ViewGroup 中的 dispatchTouchEvent 的时候,此次经历了如下的几个重要步骤:
View 对于事件的处理要稍微简单一点,注意这里的 View 并不包含 ViewGroup。咱们先看看 dispatchTouchEvent 方法。
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//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;
}
复制代码
View 对于点击事件的处理就比较简单了,由于 View 是个单独的元素,它没有子元素,所以没法向下传递事件,因此它只能本身处理。
从上面的源码中,咱们能够看出 View 对点击事件的处理过程,首先会判断有没有设置 onTouchListener,若是 onTouchListener 中的 onTouch 返回了 true,那么 onTouchEvent 就不会再被调用,可见 onTouchListener 的优先级要高于 onTouchEvent,这样的处理是方便点击事件在外界进行处理。
public boolean onTouchEvent(MotionEvent event) {
// 不可用状态下,View 依然会消耗点击事件
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:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
....
}
break;
}
return true;
}
return false;
}
复制代码
从上面的代码中能够看出,只要 View 的 CLICKABLE 和 LONG_CLICK 有一个为 true,那么它就会消耗这个事件,即 onTouchEvent 方法返回 true,无论它是否是 DISABLE 状态。
而后当 ACTION_UP 事件发生的时候,会触发 performClick 方法,若是 View 设置了 onClickListener,那么 performClick 方法内部就会调用它的 onClick 方法。以下所示:
public boolean performClick() {
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;
}
复制代码
到这里,点击事件的源码分析就结束了。
-> Activity.dispatchTouchEvent()
-> ViewGroup1.dispatchTouchEvent()
-> ViewGroup1.onInterceptTouchEvent()
-> view1.dispatchTouchEvent()
-> view1.onTouchEvent()
-> ViewGroup1.onTouchEvent()
-> Activity.onTouchEvent();
复制代码
-> Activity.dispatchTouchEvent()
-> Activity.onTouchEvent();
-> 消费
复制代码
-> Activity.dispatchTouchEvent()
-> ViewGroup1.dispatchTouchEvent()
-> ViewGroup1.onInterceptTouchEvent()
-> view1.dispatchTouchEvent()
-> view1.onTouchEvent()
-> ViewGroup1.onTouchEvent()
复制代码
-> Activity.dispatchTouchEvent()
-> ViewGroup1.dispatchTouchEvent()
-> ViewGroup1.onTouchEvent()
复制代码
答案是优先响应子 view,缘由很简单,若是先响应父 view,那么子 view 将永远没法响应,父 view 要优先响应事件,必须先调用 onInterceptTouchEvent 对事件进行拦截,那么事件不会再往下传递,直接交给父 view 的 onTouchEvent 处理。
答案是:mFirstTouchTarget。当子 view 对事件进行处理的时,那么 mFirstTouchTarget 就会被赋值,如果子 view 不对事件进行处理,那么 mFirstTouchTarget 就为 null,以后 VIewGroup 就会默认拦截全部的事件。咱们能够从 dispatchTouchEvent 中找到以下代码,能够看出来,如果子 View 不处理 ACTION_DOWN,那么以后的事件也不会给到它了。
// 检查是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 省略和问题无关代码
} else {
// 默认拦截
intercepted = true;
}
复制代码
- 陈坚润:广州芦苇科技 APP 团队 Android 开发工程师
- 咱们正在招募小伙伴,有兴趣的小伙伴能够把简历发到 app@talkmoney.cn,备注:来自掘金社区
- 详情能够戳这里--> 广州芦苇信息科技