事件分發流程對於咱們開發者來說,有什麼做用?php
引用官方介紹: Object used to report movement (mouse, pen, finger, trackball) events. Motion events may hold either absolute or relative movements and other data, depending on the type of device.java
大體意思就是用於報告移動(鼠標,筆,手指,軌跡球)事件的對象。運動事件能夠保持絕對或相對運動以及其餘數據,具體取決於設備的類型。android
在事件分發中,典型經常使用的事件總共有三個:ide
通常一個事件的完整流程是 ACTION_DOWN ---> ACTION_MOVE ---> ACTION_UP源碼分析
經常使用方法 | 含義 |
---|---|
getX/Y() | 點擊事件相對於 當前 View 左上角 的 x/y 軸距離 |
getRawX/Y() | 點擊事件相對於 手機屏幕左上角 的 x/y 軸距離 |
public boolean dispatchTouchEvent(MotionEvent ev)
佈局
用於事件分發,將觸摸事件向下傳遞給目標視圖,若是它自己就是目標視圖,則傳遞給本身來處理事件。返回結果受本身的 onTouchEvent 和下級 View 的 dispatchTouchEvent 方法影響。post
public boolean onInterceptTouchEvent(MotionEvent ev)
學習
在 dispatchTouchEvent 中調用。是否進行事件攔截,若是返回 false ,則表明不攔截事件;若是返回事件爲 true,則攔截事件,而且此事件的後續事件都交給本身來處理,不會再調用此方法詢問是否攔截。this
只有 ViewGroup 有這個方法,默認返回 false,不攔截。spa
public boolean onTouchEvent(MotionEvent ev)
在 dispatchTouchEvent 中調用。用來處理事件,返回 true 表明消費事件;返回 false 表明不消費事件。
表明三者關係的僞代碼:(摘抄於《Android藝術開發探索》第三章)
//僞代碼,解釋 dispatchTouchEvent 和 onInterceptTouchEvent 以及 onTouchEvent 的調用關係
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = childView.dispatchTouchEvent(ev);
}
return consume ;
}
複製代碼
經過上面的僞代碼,咱們能夠大體的瞭解事件的傳遞規則:對於一個根 ViewGroup 來講,點擊事件產生後,首先會傳遞給它,這時它的 dispatchTouchEvent 就會被調用,若是它的 onInterceptTouchEvent 方法返回 true,就表示要攔截事件,接着事件就交給這個 ViewGroup 的 onTouchEvent 來處理,若是 onInterceptTouchEvent 返回 false,表示不攔截事件,當前事件就會傳遞給它的子元素,接着子元素的 dispatchTouchEvent 方法就會被調用,如此反覆直到事件被最終處理。
三個方法在具體項目中的具體的調用流程是什麼呢?咱們用個 Demo 來演示。
都只是簡單重寫這三個方法,並打印日誌。
public class Group1 extends FrameLayout {
public static final String TAG = "----------";
//......代碼省略......
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent-----------------"+ Util.getMotionEvent(ev) + this.getClass().getSimpleName());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG, "onInterceptTouchEvent-----------------" + Util.getMotionEvent(ev)+ this.getClass().getSimpleName());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent-----------------" + Util.getMotionEvent(event)+ this.getClass().getSimpleName());
return super.onTouchEvent(event);
}
}
複製代碼
View 的代碼:
public class View1 extends View {
public static final String TAG = "----------";
//......代碼省略......
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(TAG,"dispatchTouchEvent-----------------" + Util.getMotionEvent(event)+ this.getClass().getSimpleName());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent-----------------"+ Util.getMotionEvent(event) + this.getClass().getSimpleName());
return super.onTouchEvent(event);
}
}
複製代碼
Util 類代碼(打印出事件的名稱):
class Util {
static String getMotionEvent(MotionEvent motionEvent) {
int action = motionEvent.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return "ACTION_DOWN----";
} else if (action == MotionEvent.ACTION_UP) {
return "ACTION_UP------";
} else if (action == MotionEvent.ACTION_MOVE) {
return "ACTION_MOVE----";
} else if (action == MotionEvent.ACTION_CANCEL) {
return "ACTION_CANCEL--";
} else {
return String.valueOf(motionEvent);
}
}
}
複製代碼
MainActivity的代碼:
public class MainActivity extends AppCompatActivity {
public static final String TAG = "----------";
//......代碼省略......
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG, "dispatchTouchEvent-----------------" + com.sjc.eventdispatch.Util.getMotionEvent(ev) + this.getClass().getSimpleName());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG, "onTouchEvent-----------------" + com.sjc.eventdispatch.Util.getMotionEvent(event) + this.getClass().getSimpleName());
return super.onTouchEvent(event);
}
}
複製代碼
MainActivity中的 xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<com.sjc.eventdispatch.Group1 xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
<com.sjc.eventdispatch.View1 android:id="@+id/view1" android:layout_width="200dp" android:layout_height="300dp" android:layout_gravity="center" android:background="@color/colorPrimaryDark" />
</com.sjc.eventdispatch.Group1>
複製代碼
具體頁面呈現:
模擬一://Log日誌
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: onTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: onTouchEvent--------ACTION_UP------MainActivity
複製代碼
從事件的傳遞過程,咱們能夠看出,當用戶觸摸屏幕進行操做時,事件先傳遞給 Activity,而後經過 dispatchTouchEvent
分發給跟佈局 Group1 ,Group1 進行事件分發,先進行調用本身的 onInterceptTouchEvent
進行詢問是否攔截(默認不攔截),而後就傳遞給當前點擊的子控件 View1,View1 經過本身的 dispatchTouchEvent
方法分發給本身,調用本身的 onTouchEvent
方法。View1 默認不消費事件,事件就傳遞給了Group1 本身的 onTouchEvent 方法,因爲 Group1 也不消費事件,事件就回傳給了 Activity,最終 Activity 的 onTouchEvent 接收了事件,而且後續事件也是由它直接接收。
結論:
- 若是一個控件不消費傳遞過來的 DOWN 事件,那麼後續事件不會傳遞給它。
- 若是一個點擊區域的全部控件都不消費事件,那麼這個事件最終會傳遞個 Activity 。
模擬二:
//log日誌
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onInterceptTouchEvent--------ACTION_MOVE----Group1
----------: dispatchTouchEvent--------ACTION_MOVE----View1
----------: onTouchEvent--------ACTION_MOVE----View1
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onInterceptTouchEvent--------ACTION_UP------Group1
----------: dispatchTouchEvent--------ACTION_UP------View1
----------: onTouchEvent--------ACTION_UP------View1
複製代碼
典型的點擊事件傳遞過程,用戶點擊一個按鈕,按鈕來響應操做,事件被這個按鈕消費。而且每次事件傳遞過來時,Group1 都會調用 onInterceptTouchEvent 方法來進行檢查是否攔截。
模擬三:
//Log日誌
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: onTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: onTouchEvent--------ACTION_UP------MainActivity
複製代碼
//Log日誌
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: onTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onTouchEvent--------ACTION_MOVE----Group1
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onTouchEvent--------ACTION_UP------Group1
複製代碼
Group1 的 onInterceptTouchEvent 返回 true 攔截事件,它的 onTouchEvent 被調用。onTouchEvent 返回 true 表明事件被消費,後續事件都傳遞給它來處理;onTouchEvent 返回 false 表明事件沒有消費,事件向上傳遞給 Activity ,後續事件都不會傳遞給 Group1 和 View1 來處理。
結論:
- ViewGroup 一旦攔截事件後,後續事件就會交給它來處理,而且不會再調用 onInterceptTouchEvent 方法詢問是否攔截。
- 再次驗證了
若是一個控件不消費傳遞過來的 DOWN 事件,那麼後續事件不會傳遞給它
。
事件序列的非人爲的提早結束。
模擬條件:
模擬動做: 點擊 View1 ;
public class Group1 extends FrameLayout {
public static final String TAG = "----------";
//......代碼省略......
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG, "onInterceptTouchEvent--------" + Util.getMotionEvent(ev) + this.getClass().getSimpleName());
boolean intercept;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
intercept = true;
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
intercept = false;
break;
}
return intercept;
}
}
複製代碼
//log日誌
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onInterceptTouchEvent--------ACTION_MOVE----Group1
----------: dispatchTouchEvent--------ACTION_CANCEL--View1
----------: onTouchEvent--------ACTION_CANCEL--View1
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: onTouchEvent--------ACTION_MOVE----Group1
----------: onTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onTouchEvent--------ACTION_UP------Group1
----------: onTouchEvent--------ACTION_UP------MainActivity
複製代碼
在這個事件流中,View1 首先消費了 DOWN 事件。當用戶移動,產生了 MOVE 事件,因爲 Group1 的 onInterceptTouchEvent 方法在 MOVE 事件中返回 true,表示開始攔截事件,MOVE 事件及後面的 UP 事件都交給 Group 1處理,再也不傳遞給 View1。而此時 View1 只消費了 DOWN 事件,處於一個事件流的中途階段,爲了讓 View1 有一個完整的事件流,就傳遞給 View1 一個 CANCEL 事件,從而告訴 View1 這個事件流對於它來講已經結束了。
請求父控件不要攔截事件。屬於 ViewParent 的方法,ViewParent是一個接口,ViewGroup 實現了 ViewParent 接口。
模擬條件:
模擬動做: 點擊 View1 ;
public class View1 extends View {
public static final String TAG = "----------";
//......代碼省略......
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(TAG, "dispatchTouchEvent--------" + Util.getMotionEvent(event) + this.getClass().getSimpleName());
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(event);
}
}
複製代碼
----------: dispatchTouchEvent--------ACTION_DOWN----MainActivity
----------: dispatchTouchEvent--------ACTION_DOWN----Group1
----------: onInterceptTouchEvent--------ACTION_DOWN----Group1
----------: dispatchTouchEvent--------ACTION_DOWN----View1
----------: onTouchEvent--------ACTION_DOWN----View1
----------: dispatchTouchEvent--------ACTION_MOVE----MainActivity
----------: dispatchTouchEvent--------ACTION_MOVE----Group1
----------: dispatchTouchEvent--------ACTION_MOVE----View1
----------: onTouchEvent--------ACTION_MOVE----View1
----------: dispatchTouchEvent--------ACTION_UP------MainActivity
----------: dispatchTouchEvent--------ACTION_UP------Group1
----------: onInterceptTouchEvent--------ACTION_UP------Group1
----------: dispatchTouchEvent--------ACTION_UP------View1
----------: onTouchEvent--------ACTION_UP------View1
複製代碼
咱們能夠看到 View1 調用 requestDisallowInterceptTouchEvent 後,可以接收到 MOVE 事件以及後續其餘事件,Group1 的攔截並無起做用。從而咱們可使用 requestDisallowInterceptTouchEvent 來解決一些開發商的滑動衝突之類的問題。
注意: View 調用 requestDisallowInterceptTouchEvent 請求父控件不攔截生效有意義的前提是: View接收到了 DOWN 事件,而且消費了 DOWN 事件。若是一個控件不消費 DOWN 事件,那麼後續事件也不會傳遞給它。
本篇文章用於記錄學習過程當中的理解和筆記,若有錯誤,請批評指正,萬分感謝!
《Android開發藝術探索》第三章