触摸事件传递机制

本文最后更新于:1 年前

触摸事件传递的三个阶段

分发

分发对应dispatchTouchEvent()方法。
如果当前视图是ViewGroup及其子类,则会调用onInterceptTouchEvent()方法判断是否拦截该事件。
该方法返回truefalse表示该事件被当前视图分发过,不继续分发该事件给子视图,其中true表示该事件被当前视图消费掉,false表示该事件未被当前视图消费掉;返回super.dispatchTouchEvent()表示继续分发该事件给子视图。
该事件将会按照嵌套层次从外向内传递,到达最内层的视图时,由该视图的onTouchEvent()方法处理,如果该方法能消费该事件,则返回true,如果消费不了,返回false,这时事件开始按照嵌套层次从内向外传递,并由外层的视图的 onTouchEvent()方法进行尝试消费,其中,返回true为消费完毕,返回false为未消费,可继续向外层传递。

拦截

拦截对应onInterceptTouchEvent()方法。
该方法只在ViewGroup及其子类中存在,在ActivityView中不存在。
该方法返回true表示拦截该事件,不继续分发给子视图,同时交给自身的onTouchEvent()方法进行尝试消费,其中,返回true为消费完毕,返回false为未消费,可继续向外层传递;返回falsesuper.onInterceptTouchEvent表示不拦截该事件,继续分发给子视图。

消费

消费对应onTouchEvent()方法。
该方法返回true表示处理该事件,不传递该事件给父视图;返回false表示当前视图不处理该事件,传递该事件给父视图的onTouchEvent()方法尝试消费。

其他说明

  • 如果同时有ActivityViewGroupView时,嵌套层次由外向内是ActivityViewGroupView
  • View控件的事件触发顺序是先执行onTouch()方法,再执行onClick()方法。如果onTouch()方法中返回true,最后不会调用onClick()方法。
  • 事件的类型有三种:
    • ACTION_DOWN:手指按下操作,表示触摸事件的开始。
    • ACTION_MOVE:手指移动距离超过阈值会被计为一次ACTION_MOVE,所以一次完整的触摸事件可能包含多个ACTION_MOVE,也可能一个都没有。
    • ACTION_UP:手指离开屏幕,表示触摸事件的结束。
  • 如果一层级的最外层控件的dispatchTouchEvent()方法返回false,该层级会不响应触摸事件并且事件也不会被消费,会继续让该层级后面的下一层级处理,相当于点击“穿透”了。

验证触摸事件传递过程

默认的传递过程

代码

1.Test_Touch\app\src\main\java\io\weichao\test_touch\MainActivity.java

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
package io.weichao.test_touch;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;

public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("test_touch", "MainActivity dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("test_touch", "MainActivity onTouchEvent");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("test_touch", "MainActivity onTouchEvent MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d("test_touch", "MainActivity onTouchEvent MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d("test_touch", "MainActivity onTouchEvent MotionEvent.ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}

2.Test_Touch\app\src\main\res\layout\activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<io.weichao.test_touch.CustomRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/CustomRelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/holo_red_dark"
tools:context="io.weichao.test_touch.MainActivity">

<io.weichao.test_touch.CustomButton
android:id="@+id/CustomButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</io.weichao.test_touch.CustomRelativeLayout>

3.Test_Touch\app\src\main\java\io\weichao\test_touch\CustomRelativeLayout.java

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
54
55
56
57
package io.weichao.test_touch;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.RelativeLayout;

/**
* Created by pi on 2017/7/27.
*/
public class CustomRelativeLayout extends RelativeLayout {
public CustomRelativeLayout(Context context) {
super(context);
}

public CustomRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CustomRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

public CustomRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("test_touch", "CustomRelativeLayout dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("test_touch", "CustomRelativeLayout onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("test_touch", "CustomRelativeLayout onTouchEvent");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("test_touch", "CustomRelativeLayout onTouchEvent MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d("test_touch", "CustomRelativeLayout onTouchEvent MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d("test_touch", "CustomRelativeLayout onTouchEvent MotionEvent.ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}

4.Test_Touch\app\src\main\java\io\weichao\test_touch\CustomButton.java

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
package io.weichao.test_touch;

import android.content.Context;
import android.support.v7.widget.AppCompatButton;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

/**
* Created by pi on 2017/7/27.
*/
public class CustomButton extends AppCompatButton {
public CustomButton(Context context) {
super(context);
}

public CustomButton(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CustomButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("test_touch", "CustomButton dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("test_touch", "CustomButton onTouchEvent");
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d("test_touch", "CustomButton onTouchEvent MotionEvent.ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.d("test_touch", "CustomButton onTouchEvent MotionEvent.ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d("test_touch", "CustomButton onTouchEvent MotionEvent.ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}

显示的界面

点击下面的紫色区域,显示 log

按下会触发1次,弹起会触发1次,每判定出一次移动会触发1次。

点击红色区域,显示 log

中间部分每判定出一次移动会触发1次,这里触发了3次。

点击灰色区域,显示 log

中间部分每判定出一次移动会触发1次,这里触发了3次。

只修改 Activity

只修改 dispatchTouchEvent()方法,返回 true 或 false

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

事件被MainActivity分发过,不管是否被消费,都不会再分发,所以不执行任何onTouchEvent()方法。

只修改 onTouchEvent()方法,返回 true 或 false

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

无变化,但是返回true时最后不会调用MainAcitivityonClick()方法。

只修改 CustomRelativeLayout

只修改 dispatchTouchEvent()方法,返回 true

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

事件被CustomRelativeLayout分发过,且被消费了,所以不执行任何onTouchEvent()方法。

只修改 dispatchTouchEvent()方法,返回 false

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

事件被CustomRelativeLayout分发过,但未被消费过,所以该事件被MainActivity消费掉,执行MainActivityonTouchEvent()方法。

只修改 onInterceptTouchEvent()方法,返回 true

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

CustomRelativeLayout拦截向下传递,所以执行CustomRelativeLayoutMainActivityonTouchEvent()方法。

只修改 onInterceptTouchEvent()方法,返回 false

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

CustomRelativeLayout不拦截向下传递,所以当CustomButton有触摸事件时,会消费掉该事件,所以执行CustomButtononTouchEvent()方法;否则,CustomRelativeLayout不会消费掉该事件,所以执行CustomRelativeLayoutMainActivityonTouchEvent()方法。

只修改 onTouchEvent()方法,返回 true

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

CustomRelativeLayout不拦截向下传递,所以当CustomButton有触摸事件时,会消费掉该事件,所以执行CustomButtononTouchEvent()方法;否则,CustomRelativeLayout会消费掉该事件,所以执行CustomRelativeLayoutonTouchEvent()方法。

只修改 onTouchEvent()方法,返回 false

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

和【只修改 onInterceptTouchEvent()方法,返回 false】一样。

只修改 CustomButton

只修改 dispatchTouchEvent()方法,返回 true

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

CustomRelativeLayout不拦截向下传递,所以当CustomButton有触摸事件时,会试图消费掉该事件,但因为CustomButton已分发过,所以不执行CustomButtononTouchEvent()方法,同时该事件也被消费了,所以不会再被CustomRelativeLayoutMainActivity消费;否则,默认CustomRelativeLayout也不会消费掉该事件,所以执行CustomRelativeLayoutMainActivityonTouchEvent()方法。

只修改 dispatchTouchEvent()方法,返回 false

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

CustomRelativeLayout不拦截向下传递,所以当CustomButton有触摸事件时,会试图消费掉该事件,但因为CustomButton已分发过,所以不执行CustomButtononTouchEvent()方法,但同时未消费过,但默认CustomRelativeLayout也不会消费掉该事件,所以执行CustomRelativeLayoutMainActivityonTouchEvent()方法;否则,默认CustomRelativeLayout也不会消费掉该事件,所以执行CustomRelativeLayoutMainActivityonTouchEvent()方法。

只修改 onTouchEvent()方法,返回 true

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

CustomRelativeLayout不拦截向下传递,所以当CustomButton有触摸事件时,会消费掉该事件;否则,默认CustomRelativeLayout也不会消费掉该事件,所以执行CustomRelativeLayoutMainActivityonTouchEvent()方法。

只修改 onTouchEvent()方法,返回 false

点击下面的紫色区域,显示 log

点击红色区域,显示 log

点击灰色区域,显示 log

与默认的传递过程对比

完全一样。



触摸事件传递机制
https://weichao.io/e78466886e33/
作者
魏超
发布于
2018年3月4日
更新于
2022年12月4日
许可协议