Earth Guardian

You are not LATE!You are not EARLY!

0%

View 事件分发机制

基本概念

事件类型

主要有如下三种:

1
2
3
- MotionEvent.ACTION_DOWN   
- MotionEvent.ACTION_MOVE
- MotionEvent.ACTION_UP

事件处理 API

1
2
3
4
5
6
- Activity.dispatchTouchEvent()    
- Activity.onTouchEvent()
- ViewGroup.dispatchTouchEvent()
- ViewGroup.onInterceptTouchEvent()
- View.dispatchTouchEvent()
- View.onTouchEvent()

主要是描述 Touch 事件的分发,拦载,响应三者的关系。 Touch 事件发生时,会由根元素逐级分发直到最内层 View,然后反过来逐级响应该事件。

名词解释

  • dispatchTouchEvent()

分发:该方法封装了事件分发的整个过程,是事件分发的调度者和指挥官。

  • onInterceptTouchEvent()

拦载:该方法表示是否拦截事件,只有 ViewGroup 有该回调。
返回 true 表示拦截,返回 false 或者 super.onInterceptTouchEvent 表示不拦截

  • onTouchEvent()

响应:该方法表示响应并处理事件

消费:表示事件不再继续逐级向下传递分发,或者不再继续逐级向上响应。也就是事件在某个方法中被消费后,该方法就是整个分发、拦载、响应流程的终点。

处理顺序

分发顺序为逐级向下:
dispatchTouchEvent: Activity(Window) -> ViewGroup -> View
响应顺序为逐级向上:
onTouchEvent: View -> ViewGroup --> Activity

事件分发流程

DOWN 事件 U 型流程图

流程图是针对 ACTION_DOWN 事件的分析,而 ACTION_MOVE/ACTION_UP 和事件的消费有关,放到后面分析。
事件的正常流程是:由上向下分发,并由下向上响应的一个 U 型图。箭头上的值表示返回值,其中 super 表示返回父类实现。

0031_view_dispatch_event3.png

流程图总结

  • 所有 API 返回值都为 super ,表示事件能走完整个 U 型流程。
  • 分发响应只要返回值为 true 都表示消费该事件,流程不再继续流转。
  • Activity 中的分发响应

分发:只有返回 super 才能逐级向下分发,true/false 都表示事件最终被消费。
响应:不管返回什么值,都表示事件被最终消费。

  • ViewGroupView分发响应返回 false ,都表示流转到上一级执行 onTouchEvent (响应)。
  • 拦载
    返回 true 表示拦载,事件不会继续分发, 当前 ViewGroup 直接响应事件, U 型流程继续向前走。
    返回 false 或者 super 表示不拦载,事件继续向下分发

MOVEUP 事件流程

ACTION_MOVE/ACTION_UPACTION_DOWN 事件的消费有关。在 U 型图中,ACTION_DOWN 事件在哪一层被消费ACTION_MOVE/ACTION_UP 就只能到达这一层。

  • 分发
    如果 DOWN 事件被分发消费,UP 事件只能逐级向下分发到这一层。
  • 响应
    如果 DOWN 事件被响应后消费,UP 事件逐级向下分发到这一层,并在这一层响应后直接被消费。

示例 1

View 分发时消费 DOWN 事件。
ViewdispatchTouchEvent 中消费 DOWN 事件, MVOE/UP 也只能分发CustomView 这一层。

0031_view_dispatch_event_move-up1.png

示例 2

ViewGroup 响应时消费 DOWN 事件。
ViewGrouponTouchEvent 中消费 DOWN 事件, MOVE/UP 只能分发到这一层,并在这一层响应后直接消费。

0031_view_dispatch_event_move-up2.png

示例 Log 分析

示例结构图

CustomView :为自定义 View
CustomLayout :为自定义 ViewGroup
CustomLayout2 :继承 LinearLayout

0031_layout_hierachy.png

示例完整布局文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.***.view.ViewEvent">

<com.***.view.CustomLayout
android:id="@+id/view_event_custom_layout"
android:layout_width="300dp"
android:layout_height="300dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:custom_orientation="vertical">

<com.***.view.CustomLayout2
android:id="@+id/view_event_custom_layout_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:custom_orientation="horizontal">
<com.***.view.CustomView
android:id="@+id/view_event_custom_view_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:custom_text="@string/view_event_custom_view_button"
app:custom_color="@color/colorAccent"
app:custom_size="@dimen/smallTextSize"
android:layout_marginLeft="@dimen/custom_margin_view"/>

<TextView
android:id="@+id/view_event_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/view_event_textview"
android:layout_marginLeft="@dimen/custom_margin_text"/>
</com.***.view.CustomLayout2>

<TextView
android:id="@+id/view_event_textview_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/view_event_textview"
app:custom_margin="@dimen/custom_margin_text"/>

</com.***.view.CustomLayout>

</android.support.constraint.ConstraintLayout>

正常流程,不做任何处理

所有 API 都返回的是 super.***,走完整个 U 型图的流程。如下示例为点击 CustomView 后,触发了事件逐级向下分发拦载、逐级向上响应的完整流程,包含 ACTION_DOWNACTION_UP 事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 处理 ACTION_DOWN 事件,Activity 开始分发事件
19:26: 26395/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
// 第一层 ViewGroup 接收事件并向下分发
19:26: 26395/com.yD/:CustomLayout:: dispatchTouchEvent:
// 第一层 ViewGroup 判断是否拦载
19:26: 26395/com.yD/:CustomLayout:: onInterceptTouchEvent:
// 第二层 ViewGroup 接收事件并向下分发
19:26: 26395/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
// 第二层 ViewGroup 判断是否拦载
19:26: 26395/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
// 第三层 View 接收事件并分发
19:26: 26395/com.yD/:CustomView:: dispatchTouchEvent:
// 第三层 View 响应事件
19:26: 26395/com.yD/:CustomView:: onTouchEvent:
// 第二层 ViewGroup 向上响应事件
19:26: 26395/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:
// 第一层 ViewGroup 向上响应事件
19:26: 26395/com.yD/:CustomLayout:: onTouchEvent:
// Activity 开始响应事件并消费
19:26: 26395/com.yD/:ViewEventActivity:: onTouchEvent:
// 点击结束抬手,ACTION_UP,Activity 开始分发事件
19:26: 26395/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
// 因为是 Activity 消费了 DOWN 事件,所以 UP 事件只到这一层响应并消费
19:26: 26395/com.yD/:ViewEventActivity:: onTouchEvent:

dispatchTouchEvent 的返回值

  • 当层返回 super.dispatchTouchEvent
    不影响事件分发响应,和正常流程完全一样
  • 当层返回 false

分发:表示事件不再逐级向下传递分发
响应:同时该层也不会响应事件,该层(不含)逐级向上所有层都响应事件。
如下示例为在 CustomLayout2 层直接返回 false

1
2
3
4
5
6
7
8
9
10
11
12
// 处理 ACTION_DOWN 事件
19:40: 6728/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
19:40: 6728/com.yD/:CustomLayout:: dispatchTouchEvent:
19:40: 6728/com.yD/:CustomLayout:: onInterceptTouchEvent:
// CustomLayout2 层直接返回 false ,事件不再向下分发传递,同时本层也不响应事件
19:40: 6728/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
// 该层(不含)逐级向上所有层都响应事件
19:40: 6728/com.yD/:CustomLayout:: onTouchEvent:
19:40: 6728/com.yD/:ViewEventActivity:: onTouchEvent:
// 处理 ACTION_UP 事件,Activity 消费了 DOWN 事件,所以 UP 事件只到这一层响应并消费
19:40: 6728/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
19:40: 6728/com.yD/:ViewEventActivity:: onTouchEvent:
  • 当层返回 true
    表示该层消费了这次事件。

分发:事件不会继续在整个流程中传递分发
响应:所有层都不响应该事件(因为分发流程未走完,所以响应流程无法开始)
如下示例为在最底层的 CustomView 中直接返回 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 处理 ACTION_DOWN 事件
19:46: 11801/com.yD/:XMT:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
19:46: 11801/com.yD/:XMT:CustomLayout:: dispatchTouchEvent:
19:46: 11801/com.yD/:XMT:CustomLayout:: onInterceptTouchEvent:
19:46: 11801/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
19:46: 11801/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
// CustomView 层直接返回 true,事件不再继续分发,同时所有层都不会响应事件
19:46: 11801/com.yD/:XMT:CustomView:: dispatchTouchEvent:
// 处理 ACTION_UP 事件,CustomView 的dispatchTouchEvent 消费了 DOWN 事件
19:46: 11801/com.yD/:XMT:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
19:46: 11801/com.yD/:XMT:CustomLayout:: dispatchTouchEvent:
19:46: 11801/com.yD/:XMT:CustomLayout:: onInterceptTouchEvent:
19:46: 11801/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
19:46: 11801/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
// ACTION_UP 传递分发到这一层的 dispatchTouchEvent 并消费
19:46: 11801/com.yD/:XMT:CustomView:: dispatchTouchEvent:

onInterceptTouchEvent 的返回值

无法消费事件。

  • 当层返回 false 或者 super.onInterceptTouchEvent
    表示该层不做拦载,不影响事件分发响应,和正常流程完全一样。

  • 当层返回 true
    表示该层拦载了事件。

分发:事件不再逐级向下传递。
响应:该层(含)逐级向上所有层都响应事件。
如下示例为在 CustomLayout2 中直接返回 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 处理 ACTION_DOWN 事件
19:57: 22163/com.yD/:XMT:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
19:57: 22163/com.yD/:XMT:CustomLayout:: dispatchTouchEvent:
19:57: 22163/com.yD/:XMT:CustomLayout:: onInterceptTouchEvent:
19:57: 22163/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
// CustomLayout2 层直接返回true,拦载该事件,事件不再继续向下分发
19:57: 22163/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
// 从 CustomLayout2 层(含)开始逐级向上响应事件
19:57: 22163/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:
19:57: 22163/com.yD/:XMT:CustomLayout:: onTouchEvent:
19:57: 22163/com.yD/:XMT:ViewEventActivity:: onTouchEvent:
// 处理 ACTION_UP 事件,Activity 消费了 DOWN 事件,所以 UP 事件只到这一层响应并消费
19:57: 22163/com.yD/:XMT:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
19:57: 22163/com.yD/:XMT:ViewEventActivity:: onTouchEvent:

onTouchEvent 返回值

分发:返回值不影响事件传递分发
响应:决定该层(不含)逐级向上所有层是否响应事件。

  • 当层返回 false 或者 super.onTouchEvent
    表示不影响事件分发响应,和正常流程完全一样。如果直接返回 false 仅仅是比 super.onTouchEvent 少调用一次父类的 onTouchEvent

  • 当层返回 true
    表示该层消费了事件,即该层(不含)逐级向上所有层都不响应事件了。
    该层(含)及之下层已经响应了事件。
    如下示例为 CustomLayout2 中返回 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 处理 ACTION_DOWN 事件
20:01: 25724/com.yD/:XMT:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
20:01: 25724/com.yD/:XMT:CustomLayout:: dispatchTouchEvent:
20:01: 25724/com.yD/:XMT:CustomLayout:: onInterceptTouchEvent:
20:01: 25724/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
20:01: 25724/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
20:01: 25724/com.yD/:XMT:CustomView:: dispatchTouchEvent:
// 并不影响事件的分发,也不影响下层级的响应
20:01: 25724/com.yD/:XMT:CustomView:: onTouchEvent:
// CustomLayout2 中返回 true,该层(不含)逐级向上将不再响应事件
20:01: 25724/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:
// 处理 ACTION_UP 事件,CustomLayout2 的 onTouchEvent 响应并消费了事件
20:01: 25724/com.yD/:XMT:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
20:01: 25724/com.yD/:XMT:CustomLayout:: dispatchTouchEvent:
20:01: 25724/com.yD/:XMT:CustomLayout:: onInterceptTouchEvent:
20:01: 25724/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
// ACTION_UP 传递分发到这一层,onTouchEvent 响应并消费
20:01: 25724/com.yD/:XMT:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:

CustomLayout2 继承 CustomLayout

如果 CustomLayout2 继承 CustomLayout ,则在调用 supder.*** 会多执行一次 CustomLayout 相关方法。

  • 正常流程 Log 分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 事件正常下发
10:01: 12487/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
10:01: 12487/com.yD/:CustomLayout:: dispatchTouchEvent:
10:01: 12487/com.yD/:CustomLayout:: onInterceptTouchEvent:
10:01: 12487/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
// 调用 super.***
10:01: 12487/com.yD/:CustomLayout:: dispatchTouchEvent:
10:01: 12487/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
// 调用 super.***
10:01: 12487/com.yD/:CustomLayout:: onInterceptTouchEvent:
10:01: 12487/com.yD/:CustomView:: dispatchTouchEvent:
10:01: 12487/com.yD/:CustomView:: onTouchEvent:
10:01: 12487/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:
// 调用 super.***
10:01: 12487/com.yD/:CustomLayout:: onTouchEvent:
10:01: 12487/com.yD/:CustomLayout:: onTouchEvent:
10:01: 12487/com.yD/:ViewEventActivity:: onTouchEvent:
10:01: 12487/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
10:01: 12487/com.yD/:ViewEventActivity:: onTouchEvent:
  • CustomLayoutonTouchEvent 返回 true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 事件正常下发
10:10: 19035/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
10:10: 19035/com.yD/:CustomLayout:: dispatchTouchEvent:
10:10: 19035/com.yD/:CustomLayout:: onInterceptTouchEvent:
10:10: 19035/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
// 调用 super.***
10:10: 19035/com.yD/:CustomLayout:: dispatchTouchEvent:
10:10: 19035/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
// 调用 super.***
10:10: 19035/com.yD/:CustomLayout:: onInterceptTouchEvent:
10:10: 19035/com.yD/:CustomView:: dispatchTouchEvent:
10:10: 19035/com.yD/:CustomView:: onTouchEvent:
10:10: 19035/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:
// 调用 super.***,但是 CustomLayout 在 onTouchEvent 响应完后消费了该事件
10:10: 19035/com.yD/:CustomLayout:: onTouchEvent:
// 所以不再再次调用 CustomLayout:: onTouchEvent:
// UP 事件正常下发
10:10: 19035/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
10:10: 19035/com.yD/:CustomLayout:: dispatchTouchEvent:
10:10: 19035/com.yD/:CustomLayout:: onInterceptTouchEvent:
10:10: 19035/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
10:10: 19035/com.yD/:CustomLayout:: dispatchTouchEvent:
10:10: 19035/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:
// 同理调用 super.*** 时 CustomLayout 在 onTouchEvent 响应完后消费了该事件
10:10: 19035/com.yD/:CustomLayout:: onTouchEvent:

事件分发及响应的部分源码分析

  • Activity 事件分发源码
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);
}

大体的分发过程为:首先传递到 Activity,然后传给了 Activity 依附的 Window,接着由 Window 传给视图的顶层 View 也就是 DecorView,最后由 DecorView 向整个 ViewTree 分发。
getWindow().superDispathTouchEvent 就是用来分发事件到 DecorView 中。如果整个 ViewTree 分发没有消费事件,会调用 ActivityonTouchEvent

  • ViewGroupView 事件分发伪代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 点击事件产生后,会直接调用 dispatchTouchEvent() 方法
public boolean dispatchTouchEvent(MotionEvent e) {
// 代表是否消费事件
boolean consumed = false;
if (onInterceptTouchEvent(e)) {
// 只有 ViewGroup 才有该方法
// 如果返回 true 则代表当前 ViewGroup 拦截了点击事件
// 则该事件由当前 ViewGroup 的 onTouchEvent ()方法逐级向上响应事件
consumed = onTouchEvent(e);
} else {
// 如果返回 false 则代表当前 ViewGroup 不拦截点击事件
// 则该点击事件则会继续传递给它的子元素
// 子元素的 dispatchTouchEvent()重复上述过程
for (View view: children) {
consumed = view.dispatchTouchEvent(e);
if (consumed) {
break;
}
}
// 如果分发没有消费该事件,则逐级向上响应
if (!consumed) {
consumed = onTouchEvent(e);
}
}
return consumed;
}

常见监听事件的消费

onTouch 监听

  • 事件监听
1
2
3
4
5
6
view.setOnTouchListener(new View.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
...
}
}
  • onTouch 返回值
    返回 false 表示不影响整个事件分发响应流程。
    返回 true 表示 dispatchTouchEvent 分发消费这次事件。
    对应的 Log 打印:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// CustomView 监听 onTouch 事件后的分发流程
// onTouch 返回 false 走完整个 U 型流程
17:07: 29706/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
17:07: 29706/com.yD/:CustomLayout:: dispatchTouchEvent:
17:07: 29706/com.yD/:CustomLayout:: onInterceptTouchEvent:
17:07: 29706/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
17:07: 29706/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
17:07: 29706/com.yD/:CustomView:: dispatchTouchEvent:
17:07: 29706/com.yD/:ViewEventActivity:: customView, onTouch:
17:07: 29706/com.yD/:CustomView:: onTouchEvent:
17:07: 29706/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onTouchEvent:
17:07: 29706/com.yD/:CustomLayout:: onTouchEvent:
17:07: 29706/com.yD/:ViewEventActivity:: onTouchEvent:
17:07: 29706/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
17:07: 29706/com.yD/:ViewEventActivity:: onTouchEvent:


// CustomView 监听 onTouch 事件后的分发流程
// onTouch 返回 true
// 查看源码 onTouch 是在 View 的 dispatchTouchEvent 中调用的
// 返回 true 表示 消费了该事件
17:09: 31152/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
17:09: 31152/com.yD/:CustomLayout:: dispatchTouchEvent:
17:09: 31152/com.yD/:CustomLayout:: onInterceptTouchEvent:
17:09: 31152/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
17:09: 31152/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
17:09: 31152/com.yD/:CustomView:: dispatchTouchEvent:
17:09: 31152/com.yD/:ViewEventActivity:: customView, onTouch:
17:09: 31152/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
17:09: 31152/com.yD/:CustomLayout:: dispatchTouchEvent:
17:09: 31152/com.yD/:CustomLayout:: onInterceptTouchEvent:
17:09: 31152/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
17:09: 31152/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
17:09: 31152/com.yD/:CustomView:: dispatchTouchEvent:
17:09: 31152/com.yD/:ViewEventActivity:: customView, onTouch:
  • 源码分析
    源码可以看出,onTouch 回调是在 dispatchTouchEvent 中调用的,所以返回 true 时,表示分发消费了事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean dispatchTouchEvent(MotionEvent event) {
...
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
// 回调 onTouch 事件监听
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}

// 执行 onTouchEvent 事件分发响应流程
if (!result && onTouchEvent(event)) {
result = true;
}
...
}

onLongClick 监听

  • 事件监听
1
2
3
4
5
6
view.setOnLongClickListener(new View.OnLongClickListener(){
@Override
public boolean onLongClick(View v) {
...
}
}
  • onLongClick 返回值
    不管返回 true 还是 false,都表示在 onTouchEvent 响应中消费该事件,并且是在 ACTION_DOWN 中执行 onLongClick 回调。分析见下面 onTouchEvent 部分源码。对应的 Log 打印:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17:14: 3237/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
17:14: 3237/com.yD/:CustomLayout:: dispatchTouchEvent:
17:14: 3237/com.yD/:CustomLayout:: onInterceptTouchEvent:
17:14: 3237/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
17:14: 3237/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
17:14: 3237/com.yD/:CustomView:: dispatchTouchEvent:
17:14: 3237/com.yD/:CustomView:: onTouchEvent:
// ACTION_DOWN 中响应 onLongClick 事件
17:14: 3237/com.yD/:ViewEventActivity:: customView, onLongClick:
17:14: 3237/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
17:14: 3237/com.yD/:CustomLayout:: dispatchTouchEvent:
17:14: 3237/com.yD/:CustomLayout:: onInterceptTouchEvent:
17:14: 3237/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
17:14: 3237/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
17:14: 3237/com.yD/:CustomView:: dispatchTouchEvent:
17:14: 3237/com.yD/:CustomView:: onTouchEvent:

onClick 监听

  • 事件监听
1
2
3
4
5
6
view.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
...
}
}
  • 事件消费
    onTouchEvent 响应中消费该事件,并且是在 ACTION_UP 中执行 onClick 回调。对应的 Log 打印:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// CustomView 监听 onClick 事件后的分发流程
// 相当于在 onTouchEvent 中消费了该事件
17:03: 26616/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_DOWN...}
17:03: 26616/com.yD/:CustomLayout:: dispatchTouchEvent:
17:03: 26616/com.yD/:CustomLayout:: onInterceptTouchEvent:
17:03: 26616/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
17:03: 26616/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
17:03: 26616/com.yD/:CustomView:: dispatchTouchEvent:
17:03: 26616/com.yD/:CustomView:: onTouchEvent:
17:03: 26616/com.yD/:ViewEventActivity:: dispatchTouchEvent: ev MotionEvent { action=ACTION_UP...}
17:03: 26616/com.yD/:CustomLayout:: dispatchTouchEvent:
17:03: 26616/com.yD/:CustomLayout:: onInterceptTouchEvent:
17:03: 26616/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, dispatchTouchEvent:
17:03: 26616/com.yD/:CustomLayout2:: :XMT:CustomLayout2:, onInterceptTouchEvent:
17:03: 26616/com.yD/:CustomView:: dispatchTouchEvent:
17:03: 26616/com.yD/:CustomView:: onTouchEvent:
// ACTION_UP 中响应 onClick 事件
17:03: 26616/com.yD/:ViewEventActivity:: customView, onClick:
  • 源码分析
    参考如下源码,onLongClickonClick 事件都是在 onTouchEvent 响应中回调了监听,其中:
    onLongClick 是在 ACTION_DOWN 中处理监听回调
    onClick 是在 ACTION_UP 中处理监听回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public boolean onTouchEvent(MotionEvent event) {
...
final int action = event.getAction();
...
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// 事件点击事件监听
performClick();
}
...
case MotionEvent.ACTION_DOWN:
...
setPressed(true, x, y);
// 处理长按事件监听
checkForLongClick(0, x, y);
...
}
return true;
}
...
}

ViewGroup 中拒绝拦载事件

可以通过设置 FLAG_DISALLOW_INTERCEPT 来要求 ViewGroup 拒绝拦载事件,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ViewGroup.java
@Override
public void requestDisallowInterceptTouchEvent(
boolean disallowIntercept) {

if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT)
!= 0)) {
// We're already in this state, assume our ancestors are too
return;
}

if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}

// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}

通过 requestDisallowInterceptTouchEvent 设置好 FLAG 后,在 ViewGroup 的事件分发机制中可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
...
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 获取 FLAG 判断是否允许拦载
final boolean disallowIntercept =
(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
...
}

参考链接

  1. http://www.jianshu.com/p/e99b5e8bd67b
  2. http://www.jianshu.com/p/7daf0feb6c2d
  3. http://blog.csdn.net/guolin_blog/article/details/9097463/
  4. http://blog.csdn.net/guolin_blog/article/details/9153747/
  5. http://www.cnblogs.com/duoduohuakai/p/3996385.html
  6. http://www.cnblogs.com/sunzn/archive/2013/05/10/3064129.html
  7. http://blog.csdn.net/carson_ho/article/details/54136311