Android事件分发

[TOC]

参考:

Kelin的图解Android事件分发机制

源码解读

概览(以ACTION_DOWN为例)

分发流程图

从上到下依次为Activity、ViewGroup、View

  1. dispatchTouchEvent和onTouchEvent一旦返回true,事件就停止传递了
  2. dispatchTouchEvent和onTouchEvent返回false,都会回传给父控件的onTouchEvent处理(其中Activity会终止)

但其实调用顺序完整的说法应该是

Activity>PhoneWindow>DecorView>ViewGroup>View

onTouch

dispatchTouchEvent相当于onTouch

这几个方法的优先级是onTouch>onTouchEvent>onClick

onTouch返回true,相当于dispatchTouchEvent返回true

而onClick是在onTouchEvent中调用的

关于ACTION_MOVE和ACTION_UP

  • 当dispatchTouchEvent进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到MOVE和UP的事件。且一个View一旦决定拦截事件,那么这一个事件序列都由它来处理,而不会再调用onInterceptTouchEvent了
  • 如果在某个控件的dispatchTouchEvent返回true消费事件,那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP

事件消费

  • 如果我们在onTouchEvent中消费了事件,那么MOVE和UP事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent并结束本次事件传递过程

事件分发

结合代码分析

dispatchTouchEvent

Activity#dispatchTouchEvent

  1. Activity调用了window的dispatchTouchEvent
  2. window调用了mDecor的dispatchTouchEvent
  3. mDecor调用了super即FrameLayout的dispatchTouchEvent
1
2
3
4
5
6
7
8
9
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

ViewGroup#dispatchTouchEvent

伪代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean result = false; // 默认状态为没有消费过

if (!onInterceptTouchEvent(ev)) { // 如果没有拦截交给子View
result = child.dispatchTouchEvent(ev);
}

if (!result) { // 如果事件没有被消费,询问自身onTouchEvent
result = onTouchEvent(ev);
}

return result;
}
真实代码
  1. 首先判断是否需要调用onInterceptTouchEvent,mFirstTouchTarget在事件不被拦截并且交给子元素处理时不为空,即有子View处理事件时,此值不为空
  2. disallowIntercept由子View的requestDisallowInterceptTouchEvent决定,但是这个对ACTION_DOWN无效,因为在dispatchTouchEvent开头会重置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 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); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
  1. 接下来遍历子View,满足:

    • 没有进行动画
    • 点击的位置在View的坐标范围内的View会被调用dispatchTouchEvent

View#dispatchTouchEvent

调用步骤:

  1. onTouch(注意View是disabled的话,只要是clickable或者longclickable仍然可以消费点击事件,只是不会生效)
  2. onTouchEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;
}
0%