View的事件分發(一)分發流程

目錄

做用

事件分發流程對於咱們開發者來說,有什麼做用?php

  • 根據咱們本身的需求來自定義滑動觸摸響應的規則。
  • 解決滑動衝突

相關知識

MotionEvent

引用官方介紹: 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 -----------擡起操做

通常一個事件的完整流程是 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>
複製代碼

具體頁面呈現:

模擬一:

  • 條件:全部方法都保持返回 super.xxx(event) 狀態;
  • 動做:點擊 View1 ;
//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 接收了事件,而且後續事件也是由它直接接收。

結論:

  1. 若是一個控件不消費傳遞過來的 DOWN 事件,那麼後續事件不會傳遞給它。
  2. 若是一個點擊區域的全部控件都不消費事件,那麼這個事件最終會傳遞個 Activity 。

模擬二:

  • 條件:View1 的 onTouchEvent 方法返回 true ;
  • 動做:點擊 View1 ;
//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 方法來進行檢查是否攔截。

模擬三:

  • 條件1:View1 的 onTouchEvent 方法返回 true, Group1 的 onInterceptTouchEvent 返回 true 。
  • 動做1:點擊 View1 ;
//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
複製代碼
  • 條件2:View1 的 onTouchEvent 方法返回 true ,Group1 的 onInterceptTouchEvent 返回 true ,Group1 的 onTouchEvent 返回 true 。
  • 動做2:點擊 View1 。
//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 來處理。

結論:

  1. ViewGroup 一旦攔截事件後,後續事件就會交給它來處理,而且不會再調用 onInterceptTouchEvent 方法詢問是否攔截。
  2. 再次驗證了 若是一個控件不消費傳遞過來的 DOWN 事件,那麼後續事件不會傳遞給它

總結

其餘知識點:

CANCEL 事件的理解

事件序列的非人爲的提早結束。

模擬條件:

  • View1 的 onTouchEvent 方法返回 true ;
  • Group1 的 onInterceptTouchEvent 在 MOVE 事件上返回 true,其餘事件返回false。
  • 其餘方法保持返回 super.xxx()。

模擬動做: 點擊 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 這個事件流對於它來講已經結束了。

requestDisallowInterceptTouchEvent

請求父控件不要攔截事件。屬於 ViewParent 的方法,ViewParent是一個接口,ViewGroup 實現了 ViewParent 接口。

模擬條件:

  • View1 的 onTouchEvent 方法返回 true ,在 dispatchTouchEvent 方法中調用 requestDisallowInterceptTouchEvent 方法;
  • Group1 的 onInterceptTouchEvent 在 MOVE 事件上返回 true,其餘事件返回false。
  • 其餘方法保持返回 super.xxx()。

模擬動做: 點擊 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 事件,那麼後續事件也不會傳遞給它。

源碼分析

View的事件分發(二)源碼分析


本篇文章用於記錄學習過程當中的理解和筆記,若有錯誤,請批評指正,萬分感謝!

事件分發系列文章:

View的事件分發(二)源碼分析

View的事件分發(三)源碼分析

參考文檔

《Android開發藝術探索》第三章

Android事件分發機制,大表哥帶你慢慢深刻

相關文章
相關標籤/搜索