由於 Android 的各個 View 是層層重疊的,那麼當在以下圖的位置點擊時,這個點擊事件究竟要給誰處理呢?css
這個時候就須要事件分發機制來處理了。android
說白了,事件分發其實就是決定將點擊事件分發給誰處理的一套規則。bash
這裏先拋出幾個問題,不知道你們有沒有遇到過滑動衝突,下面舉出三個滑動衝突場景:app
圖中有兩個 View,外部的 View 是橫向滑動的,而內部的 View 是豎向滑動的。這個時候在內部的 View 進行滑動,怎麼對這個事件進行分發呢?ide
如今外部的 View 和內部的 View 的滑動方向是同樣的,這個時候在內部的 View 滑動,這時候怎麼解決滑動衝突呢?oop
若是是前面兩個場景同時混合,這時候又怎麼分發呢?學習
好的,帶着這幾個問題如下來說解事件分發。測試
在學習事件分發必需要理解幾個關鍵的概念。ui
當手指點擊屏幕的時候,就會產生事件,這些事件的信息都在 MotionEvent 這個類中,事件的種類以下表:spa
事件種類 | 意思 |
---|---|
MotionEvent.ACTION_DOWN | 手指按下 View |
MotionEvent.ACTION_MOVE | 手指在 View 滑動 |
MotionEvent.ACTION_UP | 手指擡起 |
一個事件序列就是從手指按下 View 開始直到手指離開 View 產生的一系列事件。
其實這裏的意思就是以 DOWN 事件開始,中間產生無數個 MOVE 事件,最後以 UP 事件結束。
Activity -> ViewGroup -> ... -> View
方法 | 做用 |
---|---|
dispathchTouchEvent() | 分發點擊事件 |
onInterceptTouchEvent() | 判斷是否攔截點擊事件 |
onTouchEvent() | 處理點擊事件 |
要注意的是 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>
複製代碼
這裏返回值會有三種可能性:
打印結果:
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 事件,那麼同一個事件序列剩下的事件將不會再交給它處理,而會交給它的父元素處理。
打印結果:
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 處理事件。
打印結果:
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() 返回默認值。
前面已經探究過 ViewGroup 返回默認值的狀況了,如今只看看返回 true 和 false 的狀況。
打印結果:
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
複製代碼
根據打印結果畫出的流程圖:
打印結果:
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 事件,事件序列接下來的事件都不會再交給它處理。
這裏要說明一下的是 onInterceptTouchEvent() 默認返回值是 false,因此這裏只探究返回 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 事件,那麼事件序列的其餘事件就不會再給到它了。
若是是這種狀況,就會直接將事件分發給下一個 View,上面已經說過了,這裏就再也不贅述了。
到這裏其實 View 的分發事件與 ViewGroup 是基本類似的,View 的 dispathchTouchEvent() 返回 true 就表明消費該事件,返回 false 就表明不處理事件,返回默認值就將事件傳遞給 onTouchEvent()。
從以上結果就能夠獲得總的流程圖:
其實從上面的探究能夠得出一些結論:
這裏還要驗證一個結論,就是若是 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()。
參考文章: