1. 什麼是事件分發?
因為 Android 的各個 View 是層層重疊的,那麼當在如下圖的位置點選時,這個點選事件究竟要給誰處理呢?
這個時候就需要事件分發機制來處理了。
說白了,事件分發其實就是決定將點選事件分發給誰處理的一套規則。
2. 事件分發使用場景
這裡先丟擲幾個問題,不知道大家有沒有遇到過滑動衝突,下面舉出三個滑動衝突場景:
2.1 場景一
圖中有兩個 View,外部的 View 是橫向滑動的,而內部的 View 是豎向滑動的。這個時候在內部的 View 進行滑動,怎麼對這個事件進行分發呢?
2.2 場景二
現在外部的 View 和內部的 View 的滑動方向是一樣的,這個時候在內部的 View 滑動,這時候怎麼解決滑動衝突呢?
2.3 場景三
如果是前面兩個場景同時混合,這時候又怎麼分發呢?
好的,帶著這幾個問題以下來講解事件分發。
3. 事件分發的基本概念
在學習事件分發必須要理解幾個關鍵的概念。
3.1 什麼是事件?
當手指點選螢幕的時候,就會產生事件,這些事件的資訊都在 MotionEvent 這個類中,事件的種類如下表:
事件種類 | 意思 |
---|---|
MotionEvent.ACTION_DOWN | 手指按下 View |
MotionEvent.ACTION_MOVE | 手指在 View 滑動 |
MotionEvent.ACTION_UP | 手指抬起 |
3.2 什麼是事件序列?
一個事件序列就是從手指按下 View 開始直到手指離開 View 產生的一系列事件。
其實這裡的意思就是以 DOWN 事件開始,中間產生無數個 MOVE 事件,最後以 UP 事件結束。
3.3 事件序列的傳遞順序
Activity -> ViewGroup -> ... -> View
3.4 事件序列關鍵方法
方法 | 作用 |
---|---|
dispathchTouchEvent() | 分發點選事件 |
onInterceptTouchEvent() | 判斷是否攔截點選事件 |
onTouchEvent() | 處理點選事件 |
4. 事件分發的關鍵概念
4.1 事件分發流程
要注意的是 Activity 和 View 是沒有 onInterceptTouchEvent() 方法的。
現在探究一下事件分發的流程是怎麼樣的。
探究的過程就使用一步步列印,並且改變相關的返回值,然後畫出區域性的流程圖,最後得出全域性的流程圖。
這裡先看看測試程式碼:
Util:
public class Util {
public static String getAction(MotionEvent event) {
String action = "";
if (event.getAction() == MotionEvent.ACTION_DOWN) {
action = "ACTION_DOWN";
} else if (event.getAction() == MotionEvent.ACTION_MOVE) {
action = "ACTION_MOVE";
} else if (event.getAction() == MotionEvent.ACTION_UP) {
action = "ACTION_UP";
}
return action;
}
}
複製程式碼
這個類的作用只是讓列印可以列印出來具體是哪個點選事件。
MainActivity:
public class MainActivity extends AppCompatActivity {
public String TAG = "chan";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================Activity dispatchTouchEvent Action: "
+ Util.getAction(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================Activity onTouchEvent Action: "
+ Util.getAction(ev));
return super.onTouchEvent(ev);
}
}
複製程式碼
ViewGroup1:
public class ViewGroup1 extends LinearLayout {
public String TAG = "chan";
public ViewGroup1(Context context) {
super(context);
}
public ViewGroup1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================ViewGroup dispatchTouchEvent Action: "
+ Util.getAction(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(TAG, "=================ViewGroup onInterceptTouchEvent Action: "
+ Util.getAction(ev));
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(TAG, "=================ViewGroup onTouchEvent Action: "
+ Util.getAction(event));
return super.onTouchEvent(event);
}
}
複製程式碼
View1:
public class View1 extends View {
public String TAG = "chan";
public View1(Context context) {
super(context);
}
public View1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(TAG, "=================View dispatchTouchEvent Action: "
+ Util.getAction(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.d(TAG, "=================View onTouchEvent Action: "
+ Util.getAction(event));
return super.onTouchEvent(event);
}
}
複製程式碼
activity_main.xml:
<?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=".MainActivity">
<com.example.administrator.toucheventdemo.ViewGroup1
android:layout_width="300dp"
android:layout_height="300dp"
android:background="#00ff00">
<com.example.administrator.toucheventdemo.View1
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#ff0000"/>
</com.example.administrator.toucheventdemo.ViewGroup1>
</android.support.constraint.ConstraintLayout>
複製程式碼
4.1.1 改變 Activity 的 dispathchTouchEvent() 方法的返回值
這裡返回值會有三種可能性:
- super.dispatchTouchEvent(ev)
- true
- false
4.1.1.1 返回 super.dispatchTouchEvent(ev)
列印結果:
08-09 09:49:03.167 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 09:49:03.168 26886-26886/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
=================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
=================View dispatchTouchEvent Action: ACTION_DOWN
08-09 09:49:03.169 26886-26886/com.example.administrator.toucheventdemo D/chan: =================View onTouchEvent Action: ACTION_DOWN
=================ViewGroup onTouchEvent Action: ACTION_DOWN
=================Activity onTouchEvent Action: ACTION_DOWN
08-09 09:49:03.190 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 09:49:03.196 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 09:49:03.197 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
08-09 09:49:03.197 26886-26886/com.example.administrator.toucheventdemo D/chan: =================Activity onTouchEvent Action: ACTION_UP
複製程式碼
根據列印結果畫出的流程圖:
上面的流程圖只是畫了 DOWN 事件的分發過程,從列印結果可以看出後面的 MOVE 和 UP 事件,Activity 並沒有分發下去,而是自己處理了。這裡可以得出一個結論:
如果一個 View 不消費 DOWN 事件,那麼同一個事件序列剩下的事件將不會再交給它處理,而會交給它的父元素處理。
4.1.1.2 返回 true
列印結果:
08-09 10:11:19.533 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:11:19.546 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:11:19.569 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:11:19.572 28137-28137/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
複製程式碼
如果直接返回 true,不會分發任何事件,可以在 Activity 處理事件。
4.1.1.3 返回 false
列印結果:
08-09 10:13:54.394 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:13:54.442 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:13:54.444 28415-28415/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
複製程式碼
如果直接返回 false,不會分發任何事件,可以在 Activity 處理事件。
從這裡就可以看出,Acitity 如果要將事件分發出去,必須在 dispatchTouchEvent() 返回預設值。
4.1.2 改變 ViewGroup 的 dispathchTouchEvent() 方法的返回值
前面已經探究過 ViewGroup 返回預設值的情況了,現在只看看返回 true 和 false 的情況。
4.1.2.1 返回 true
列印結果:
08-09 10:20:51.658 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:20:51.659 29295-29295/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 10:20:51.690 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.691 29295-29295/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.699 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
08-09 10:20:51.702 29295-29295/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================ViewGroup dispatchTouchEvent Action: ACTION_UP
複製程式碼
根據列印結果畫出的流程圖:
4.1.2.1 返回 false
列印結果:
08-09 10:25:27.046 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:25:27.047 29672-29672/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 10:25:27.048 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity onTouchEvent Action: ACTION_DOWN
08-09 10:25:27.062 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.078 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.088 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 10:25:27.091 29672-29672/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================Activity onTouchEvent Action: ACTION_UP
複製程式碼
從結果可以看出,如果 ViewGroup 不消費 DOWN 事件,事件序列接下來的事件都不會再交給它處理。
4.1.3 改變 ViewGroup 的 onInterceptTouchEvent() 方法的返回值
這裡要說明一下的是 onInterceptTouchEvent() 預設返回值是 false,所以這裡只探究返回 true 和 預設值就可以了。
4.1.3.1 返回 true
列印結果:
08-09 10:31:52.499 30168-30168/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 10:31:52.501 30168-30168/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
=================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
=================ViewGroup onTouchEvent Action: ACTION_DOWN
=================Activity onTouchEvent Action: ACTION_DOWN
08-09 10:31:52.515 30168-30168/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================Activity onTouchEvent Action: ACTION_UP
複製程式碼
流程圖:
上面的列印結果是 ViewGroup 的 onTouchEvent 返回預設值的情況,但是上面的流程圖可以知道,如果 onTouchEvent 返回預設值或者 false,代表不消耗 DOWN 事件,那麼事件序列的其他事件就不會再給到它了。
4.1.3.2 返回 false 或者 預設值
如果是這種情況,就會直接將事件分發給下一個 View,上面已經說過了,這裡就不再贅述了。
到這裡其實 View 的分發事件與 ViewGroup 是基本相似的,View 的 dispathchTouchEvent() 返回 true 就代表消費該事件,返回 false 就代表不處理事件,返回預設值就將事件傳遞給 onTouchEvent()。
從以上結果就可以得到總的流程圖:
4.2 事件分發的一些結論
其實從上面的探究可以得出一些結論:
- 如果一個 View 不消費 DOWN 事件,那麼同一個事件序列剩下的事件將不會再交給它處理,而會交給它的父元素處理
- 如果一個 ViewGroup 決定攔截事件(onInterceptTouchEvent 返回 true),那麼 onInterceptTouchEvent() 就不會再被呼叫
這裡還要驗證一個結論,就是如果 View 不消費除了 DOWN 事件的其他事件,那麼這些事件就會直接交給 Activity 的 onTouchEvent() 處理,而不會再交給它的父容器的 onTouchEvent()。
這裡修改下 View1 的程式碼:
public class View1 extends View {
public String TAG = "chan";
public View1(Context context) {
super(context);
}
public View1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(TAG, "=================View dispatchTouchEvent Action: "
+ Util.getAction(event));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = super.onTouchEvent(event);
Log.d(TAG, "=================View onTouchEvent Action: "
+ Util.getAction(event));
if(event.getAction() != MotionEvent.ACTION_DOWN) {
return false;
}
return true;
}
}
複製程式碼
可以看到程式碼我只是修改了 onTouchEvent() 的程式碼,這裡只消費 DOWN 事件,其餘事件都不消費。
列印結果:
08-09 11:18:52.846 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_DOWN
08-09 11:18:52.847 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_DOWN
08-09 11:18:52.848 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup onInterceptTouchEvent Action: ACTION_DOWN
=================View dispatchTouchEvent Action: ACTION_DOWN
=================View onTouchEvent Action: ACTION_DOWN
08-09 11:18:52.863 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_MOVE
08-09 11:18:52.864 3098-3098/com.example.administrator.toucheventdemo D/chan: =================ViewGroup dispatchTouchEvent Action: ACTION_MOVE
=================ViewGroup onInterceptTouchEvent Action: ACTION_MOVE
=================View dispatchTouchEvent Action: ACTION_MOVE
=================View onTouchEvent Action: ACTION_MOVE
=================Activity onTouchEvent Action: ACTION_MOVE
08-09 11:18:52.877 3098-3098/com.example.administrator.toucheventdemo D/chan: =================Activity dispatchTouchEvent Action: ACTION_UP
=================ViewGroup dispatchTouchEvent Action: ACTION_UP
=================ViewGroup onInterceptTouchEvent Action: ACTION_UP
=================View dispatchTouchEvent Action: ACTION_UP
08-09 11:18:52.878 3098-3098/com.example.administrator.toucheventdemo D/chan: =================View onTouchEvent Action: ACTION_UP
=================Activity onTouchEvent Action: ACTION_UP
複製程式碼
從列印結果可以看出,除了 DOWN 事件外,其餘事件就直接交給 Activity 處理,並沒有再回撥 ViewGroup 的 onTouchEvent()。
參考文章: