Android 事件分發(fā)機(jī)制源碼攻略(一)

前言

android事件分發(fā)算是自定義View不可缺失的一部分,事件分發(fā)是指那一類跟屏幕交互的操作等事件,例如滑動,點擊,長按這類。這些事件都是由摁下、移動、抬起等基本事件組成的。那事件分發(fā)是指當(dāng)你點擊了屏幕,這個事件是如何從Activity傳遞到真正處理這個事件的View上的過程。例如,我們在做ListView跟ViewPager嵌套的時候,既能左右滑動,又能上下滑動,這些事件是如何避免彼此間的沖突的。下面我會分三節(jié)來介紹這一原理。
首先,這次的源碼分析是基于25.0.3版本進(jìn)行的。

分發(fā)事件

分發(fā)的事件主要是MotionEvent這個類所表示的點擊、移動、抬起、取消等事件
MotionEvent.ACTION_DOWN
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_UP
MotionEvent.ACTION_CANCEL
...

分發(fā)的對象

分發(fā)的對象是指收到上述事件的類

Activity
ViewGroup
View

上述三個類是主要的事件分發(fā)對象,后期的討論也是集中在這三個類里面。這里有個點得提一下就是ViewGroup是View的子類。這些事件會在這三者的dispatchTouchEvent、onTouchEvent這兩個方法里面?zhèn)鬟f,還有一個ViewGroup特有的onInterceptTouchEvent方法。下面給出以上提到的類以及方法之間的關(guān)系。

事件分發(fā)機(jī)制圖

從這張圖,我們可以看出,不同返回值,事件的分發(fā)方向不同,這里不做詳細(xì)分析,這張圖是否畫得出來作為你對這次源碼閱讀的成績。

Demo

Activity

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        Log.i(TAG,"dispatchTouchEvent"+ev.getAction());

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG,"onTouchEvent"+event.getAction());
        return super.onTouchEvent(event);
    }

CusViewGroup

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        Log.i(TAG,"dispatchTouchEvent"+ev.getAction());

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {

        Log.i(TAG,"onInterceptTouchEvent"+ev.getAction());

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        Log.i(TAG,"onTouchEvent"+event.getAction());

        return super.onTouchEvent(event);
    }

View

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        Log.i(TAG,"dispatchTouchEvent"+ev.getAction());

        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        Log.i(TAG,"onTouchEvent"+event.getAction());

        return super.onTouchEvent(event);
    }

布局

<?xml version="1.0" encoding="utf-8"?>
<com.example.coffeetime.cusviewdemo.CusLineaLayout
    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="com.example.coffeetime.cusviewdemo.MainActivity">

    <com.example.coffeetime.cusviewdemo.CusView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@color/colorPrimary"/>

</com.example.coffeetime.cusviewdemo.CusLineaLayout>

結(jié)果

這里寫圖片描述

ACTION_DOWN的值為0
ACTION_MOVE的值為1

從輸出的結(jié)果的第一行可以看出來最先獲取事件的是Activity這一層,從倒數(shù)第三行可以看出,ACTION_DOWN這個事件最終消費是在Activity的onTouchEvent這個方法被消費。這個結(jié)果跟上面的圖片是一致的。
再看看輸出結(jié)果的倒數(shù)兩行,ACTION_MOVE事件從dispatchTouchEvent直接就傳給了同級的onTouchEvent方法去了,說明ACTION_DOWN事件在哪里被消費了,后續(xù)事件也在那里消費,并且跳過中間傳遞。這個原因會在分析源碼的時候給出解釋。

Activity

從上述的結(jié)果,我們可以得出事件最先被攔截的地方是從Activity的dispatchTouchEvent方法開始,那我們的源碼攻略也從這里開始。

 /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

這個方法首先判斷事件類型,如果是ACTION_DOWN事件,則先執(zhí)行onUserInteraction()方法;

    public void onUserInteraction() {
    }

這個方法沒有實現(xiàn),根據(jù)文檔注釋,當(dāng)有任意一個按鍵、觸屏或者軌跡球事件發(fā)生時,棧頂Activity的onUserInteraction會被觸發(fā)。如果我們需要知道用戶是不是正在和設(shè)備交互,可以在子類中重寫這個方法,去獲取通知(比如取消屏保這個場景)。跟這個方法配對的還有onUserLeaveHint方法,這個方法是在用戶離開設(shè)備的時候觸發(fā)的。

我們接著回到剛剛那個地方,判斷完事件ACTION_DOWN事件之后,會執(zhí)行getWindow().superDispatchTouchEvent(ev)這個方法,這個getWindow獲取的是哪個Window呢,我們進(jìn)去看看。

 public Window getWindow() {
        return mWindow;
    }
/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {

通過這塊注釋,我們可以看出,window的實現(xiàn)類是phoneWindow。其實在Android里面很多都是可以通過閱讀注釋或者是官方文檔去找出具體的實現(xiàn)類,不是只能依靠百度;好了,那我們可以直接進(jìn)到phoneWindow類去看下superDispatchTouchEvent這個方法做了些什么

   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

這個方法又是調(diào)用mDecor的同名方法去實現(xiàn),mDecor又是什么

phoneWindow
 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
DecorView
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

DecorView 是window的頂級View,而DecorView是繼承FrameLayout的布局,接著去FragmeLayout里面尋找dispatchTouchEvent方法,而FragmeLayout并沒有實現(xiàn)這個方法,也即這個是直接交給ViewGroup去處理的。

小結(jié)

在Activity層事件的傳遞過程如下圖

這里寫圖片描述

從Activity的dispatchTouchEvent獲取事件經(jīng)過PhoneWindow、DecorView再到最終的ViewGroup,這一層代碼比較簡單,沒有比較難分析的,只是經(jīng)過的類比較多,其實只要把握的主線,分析起來還是比較簡單的。這一篇比較少,下一篇是ViewGroup的分析,由于ViewGroup的源碼比較多,所以才拆開來寫,不然太長了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容