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

導(dǎo)讀

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

  • 事件分發(fā)的定義
  • 事件分發(fā)的源碼分析
  • 通過自定義View分析事件分發(fā)的攔截邏輯
  • 疑問:Activity是怎么接收到事件的域那?
  • 疑問:ViewGroup是如何判斷該把事件傳遞給那個child的活逆?
  • 總結(jié)

事件分發(fā)的定義

在Android中的事件分發(fā),就是將點擊事件傳遞到某個具體的View的進(jìn)行處理的整個過程剪决。

舉個不恰當(dāng)?shù)牧凶恿橥簦愦蜷_手機(jī)通訊錄檀训,找到你的女神,點擊了她的電話享言,然后手機(jī)就會進(jìn)入撥打界面的這一整個過程峻凫。你最終會通過手機(jī)接收到女神是否接了電話都有一個直觀的反饋。
那么我們重點關(guān)注事件分發(fā)的順序览露,和核心的方法:

  • 事件分發(fā)的順序
  • 事件分發(fā)的核心方法
事件分發(fā)的順序

在Android中UI通常是Activity+ViewGroup+View組合而成

類型 簡介 說明
Activity 控制生命周期蔚晨、處理事件 統(tǒng)籌視圖的添加、顯示肛循;與Window、View交互
View 所有UI組件的父類 Button银择、TextView等控件都是View的子類
ViewGroup 含多個View的容器 也是View的子類多糠;是所有布局的父類

事件分發(fā)則先到Activity,再到容器(ViewGroup)浩考,再精準(zhǔn)到具體View夹孔。具體的View往往是實際處理者。

事件分發(fā)的核心方法

dispatchTouchEvent() 析孽、onTouchEvent()搭伤、onInterceptTouchEvent()。

方法 功能 說明
dispatchTouchEvent() 分發(fā)點擊事件 當(dāng)點擊事件能夠傳遞給當(dāng)前View時袜瞬,調(diào)用該方法
onTouchEvent() 處理點擊事件 在dispatchTouchEvent()內(nèi)部調(diào)用怜俐,false表示不處理事件
onInterceptTouchEvent() 事件攔截 在ViewGroup的dispatchTouchEvent()內(nèi)部調(diào)用,ViewGroup獨有的方法

結(jié)合事件分發(fā)的順序和核心方法邓尤,這里引用一張經(jīng)典的流程圖:

事件分發(fā)之張經(jīng)典的流程圖

總結(jié)一下拍鲤,整體流程就是到了每個層面,攔不攔截汞扎,處不處理季稳,如果不攔截不處理,最終回流到Activity行成閉環(huán)澈魄, 事件分發(fā)機(jī)制就是逐級逐層景鼠,去尋找一個有消費能力的。

舉個不恰當(dāng)?shù)牧凶樱?br> 老板Activity某天醉酒時接了一個“xxxx”相關(guān)的項目痹扇,然后交給了項目總監(jiān)ViewGroup985铛漓,
項目總監(jiān)ViewGroup985一聽這玩意兒沒經(jīng)驗,就問了問下面最得力的項目經(jīng)理ViewGroup996會不會搞
項目經(jīng)理ViewGroup996聽的一臉尷尬鲫构,就叫來了主管ViewGroup035
主管ViewGroup035說我還是問問小組長吧
小組長View251一臉懵逼票渠,就去問了程序員View404
程序員View404給小組長View251說這事搞不了
小組長View251等逐級上報最后項目總監(jiān)ViewGroup985跟老板說這貨公司干不了
妥妥的的一個閉環(huán),其中任何一個環(huán)境說能干的這事兒就執(zhí)行下去了

現(xiàn)在我們對事件分發(fā)機(jī)制有了一個清晰的認(rèn)識芬迄,那么源碼具體是如何實現(xiàn)的呢问顷?

事件分發(fā)的源碼分析

老司機(jī)都知道在Android中的頂層View其實是抽象類Window,具體實現(xiàn)類是PhoneWindow,而PhoneWindow的根本是DecorView杜窄,DecorView則是ViewGroup的子類肠骆,點擊查看關(guān)于Android的組成。故此塞耕,我們通過觀察這些類的關(guān)鍵代碼入手蚀腿。
Activity類dispatchTouchEvent(MotionEvent ev)方法:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
  • 第一個if邏輯判斷是ACTION_DOWN新的事件,也就是說扫外,當(dāng)用戶進(jìn)行按下動作的時候莉钙,事件就開始了,這里觸發(fā)了onUserInteraction()方法筛谚,該方法返回void的空方法磁玉,并且有一些不常見的用處,這里就不展開了驾讲。
  • 第二個邏輯是去判斷Window類下的 superDispatchTouchEvent(ev)方法蚊伞,前面說到了Window類是抽象類,具體實現(xiàn)類是PhoneWindow類,那么我們在Android Studio中雙擊SHUFT鍵搜索該類吮铭,并找到superDispatchTouchEvent(ev)方法:
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
  • PhoneWindow類下的superDispatchTouchEvent(ev)方法中直接使用返回了變量mDecor的superDispatchTouchEvent(event)方法时迫,變量mDecor即DecorView類,這里也不展開了谓晌,繼續(xù)看核心方法superDispatchTouchEvent(event)
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
  • DecorView類下的superDispatchTouchEvent(MotionEvent event)方法中則直接調(diào)用了父類方法掠拳,前面說到了DecorView類其實是ViewGroup類的子類,所以我們需要繼續(xù)追蹤ViewGroup類dispatchTouchEvent(event)方法纸肉,由于該方法非常的復(fù)雜和龐大碳想,這里先貼上一段偽代碼
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent();
    }else {
        consume = child.dispatchTouchEvent(ev);
    }
    
    return consume;
}
  • 這段偽代碼簡單明了的講清楚了ViewGroup類接收到事件后的邏輯,即先調(diào)用onInterceptTouchEvent()詢問是否攔截毁靶,攔截則去走自己的onTouchEvent()方法胧奔,反之去走子類的方法。

現(xiàn)在貼上精簡后的ViewGroup類onInterceptTouchEvent()源碼:

       @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            ...
            intercepted = onInterceptTouchEvent(ev);
            ...
        }
        ...
        if (!canceled && !intercepted) {
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            ...
            }
            ...
        }
        ...
        return handled;
    }
  • 會進(jìn)入第一個if邏輯去執(zhí)行onInterceptTouchEvent(ev)方法獲取是否攔截
  • 第二個if邏輯則是不攔截的情況下會進(jìn)入去執(zhí)行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法(后面會說道攔截的情況下)
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                  View child, int desiredPointerIdBits) {
        final boolean handled;
        ...
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        ...
        return handled;
    }
  • super.dispatchTouchEvent(event)表示執(zhí)行自己父類的方法
  • child.dispatchTouchEvent(event)表示事件分發(fā)給了子類预吆。比如一個我們這里點擊的是一個AppCompatButton按鈕龙填,最終執(zhí)行的是AppCompatButton的父類TextView類下的**onTouchEvent(MotionEvent event) **方法(其他父類并沒有重寫onTouchEvent()方法):
    @Override
    public boolean onTouchEvent(MotionEvent event) {}

以上便是對源碼的事件分發(fā)機(jī)制的分析,使用的都是系統(tǒng)默認(rèn)提供的ViewGroup(ConstraintLayout)拐叉、View(AppCompatButton),正常情況(無攔截事件)下的分發(fā)流程岩遗,確實符合前面我們分析的情況。下面我們通過簡單的自定義View來繼續(xù)分析凤瘦。

通過自定義View分析事件分發(fā)的攔截邏輯

這里我們進(jìn)行簡單的自定義ViewGroup+自定義View來分析攔截和不處理(消費)事件宿礁。
簡單的自定義ViewGroup:

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean isTouch = true;
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            System.out.println("ViewGroup===onTouchEvent--"+isTouch);
        }
        return isTouch;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean isIntercept = false;
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            System.out.println("ViewGroup===onInterceptTouchEvent--" + isIntercept);
        }
        return isIntercept;
    }
}
  • 重構(gòu)onTouchEvent(MotionEvent event)方法,使用isTouch變量控制是否處理事件
  • 重構(gòu)了onInterceptTouchEvent(MotionEvent event)方法蔬芥,使用isIntercept 變量控制是否攔截事件
  • 這里沒有重構(gòu)dispatchTouchEvent(MotionEvent event)方法梆靖,因為我們不需要去變更ViewGroup的分發(fā)機(jī)制

簡單的自定義View:

public class MyAppCompatButton extends AppCompatButton {
    public MyAppCompatButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean isTouch=true;
        if(event.getAction()==MotionEvent.ACTION_DOWN){
            System.out.println("View===onTouchEvent--"+isTouch);
        }
        return isTouch;
    }
}

-重構(gòu)了onTouchEvent(MotionEvent event)方法控汉,使用isTouch變量控制是否處理事件
具體在xml中的使用:

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.futurenavi.demo1.views.MyLinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            tools:ignore="MissingConstraints">

            <com.futurenavi.demo1.views.MyLinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                tools:ignore="MissingConstraints">

                <com.futurenavi.demo1.views.MyAppCompatButton
                    android:id="@+id/my_bt1"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="button1~"
                    tools:ignore="MissingConstraints" />
            </com.futurenavi.demo1.views.MyLinearLayout>
        </com.futurenavi.demo1.views.MyLinearLayout>

        <com.futurenavi.demo1.views.MyLinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            tools:ignore="MissingConstraints">

            <com.futurenavi.demo1.views.MyAppCompatButton
                android:id="@+id/my_btn2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="button2~"
                tools:ignore="MissingConstraints" />
        </com.futurenavi.demo1.views.MyLinearLayout>

        <com.futurenavi.demo1.views.MyAppCompatButton
            android:id="@+id/my_btn3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="button3~"
            tools:ignore="MissingConstraints" />
    </LinearLayout>

在布局文件中應(yīng)用了3組布局

  • 自定義ViewGroup+自定義ViewGroup+自定義View(模擬多重ViewGroup嵌套布局頁面)
  • 自定義ViewGroup+自定義View(模擬普通常見布局頁面)
  • 自定義自定義View(模擬極簡布局頁面)

最終的Acitvity頁面

public class ActivityG extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_g);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            System.out.println("Activity===onTouchEvent");
            System.out.println("========================");
        }
        return super.onTouchEvent(event);
    }
}
  • ActivityG頁面僅重寫了onTouchEvent(MotionEvent event)方法,主要用于檢驗返吻,都沒有處理事件的時候是否閉環(huán)回到Acitvity姑子。

下面是不同情況下的運行情況:
ViewGroup默認(rèn)不攔截,View處理事件時测僵,依次點擊三個按鈕的打印記錄:

ViewGroup默認(rèn)不攔截街佑,View處理事件時
  • 自定義ViewGroup+自定義ViewGroup+自定義View時:2層ViewGroup都沒有攔截,View處理事件處理
  • 自定義ViewGroup+自定義View:1層ViewGroup沒有攔截捍靠,View處理事件處理
  • 自定義自定義View:View處理事件處理

ViewGroup進(jìn)行攔截沐旨,ViewGroup自身響應(yīng)事件,View不處理事件時榨婆,依次點擊三個按鈕的打印記錄:

此時需要把MyLinearLayout類下的boolean isIntercept = false;變更為true,MyAppCompatButton類下的boolean isTouch=true;變更為false磁携。

ViewGroup進(jìn)行攔截,ViewGroup自身響應(yīng)事件纲辽,View不處理事件時

  • 自定義ViewGroup+自定義ViewGroup+自定義View時:第一層ViewGroup進(jìn)行攔截,并執(zhí)行自身事件處理璃搜。
  • 自定義ViewGroup+自定義View:同上
  • 自定義自定義View:View不處理事件處理拖吼,Activity響應(yīng)事件。

ViewGroup進(jìn)行攔截这吻,ViewGroup自身不處理事件吊档,依次點擊三個按鈕的打印記錄:

此時需要把MyLinearLayout類下的boolean isTouch=true;變更為false

ViewGroup進(jìn)行攔截,ViewGroup自身不處理事件

  • 由于一層兩層ViewGroup都不會進(jìn)行事件分發(fā)唾糯,這里就只點了按鈕1
  • 自定義ViewGroup:事件攔截怠硼,自身不進(jìn)行處理,Activity響應(yīng)事件

到這里移怯,印證了前面的介紹以及經(jīng)典圖上所繪制的流程香璃,即事件會按照順序一層一層進(jìn)行分發(fā),每一層都會都會判斷是否攔截(ViewGroup)舟误,如果不攔截則繼續(xù)往下葡秒,反之則查看當(dāng)前ViewGroup是否消費事件,若最終找不到有能力消費的嵌溢,則返回到Activity形成閉環(huán)眯牧。

當(dāng)然,截止到目前赖草,還有一點沒有說到学少,即在ViewGroup攔截事件之后,后續(xù)是如何處理的呢秧骑?下面來看看關(guān)鍵源碼:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        ...
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            ...
            intercepted = onInterceptTouchEvent(ev);
            ...
        }
        ...
         if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
        }
        ...
        return handled;
    }
  • 還是ViewGroup類下的dispatchTouchEvent(MotionEvent ev)方法
  • 第一個if邏輯中會執(zhí)行onInterceptTouchEvent(ev)查詢是否攔截
  • 第二個if邏輯中執(zhí)行的是不攔截的邏輯版确,同樣會進(jìn)入dispatchTransformedTouchEvent()方法:
  private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                                  View child, int desiredPointerIdBits) {
        final boolean handled;
        ...
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        ...
        return handled;
    }
  • 前面已知事件不攔截時扣囊,最終會執(zhí)行child.dispatchTouchEvent(event)方法。
  • 此時阀坏,事件進(jìn)行了攔截如暖,則執(zhí)行super.dispatchTouchEvent(event)方法,由于我們自定義的MyLinearLayout類重寫了onTouchEvent()方法:
public class MyLinearLayout extends LinearLayout {
    ...
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean isTouch = true;
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            System.out.println("ViewGroup===onTouchEvent--"+isTouch);
        }
        return isTouch;
    }
    ...
}
  • 此時的isTouch = true忌堂,則MyLinearLayout類onTouchEvent()方法消費事件,事件分發(fā)到此結(jié)束盒至。
  • isTouch = false 呢,自然是繼續(xù)往上最終執(zhí)行到我們的ActivityG類的**onTouchEvent(MotionEvent event) **方法士修,這里就不貼代碼了

這里再提一提枷遂,如果想要更好的理解源碼是如何進(jìn)行事件分發(fā)的,最好通過DEBUG跟蹤事件分的邏輯棋嘲,

  • 使用DUBUG模式啟動項目
  • 在父類Activity類下的dispatchTouchEvent()方法內(nèi)打上斷點
  • 在DecorView類下dispatchTouchEvent()方法內(nèi)打上斷點
  • 在ViewGroup類下的dispatchTouchEvent()酒唉、dispatchTransformedTouchEvent()方法內(nèi)打上斷點

這里發(fā)現(xiàn)android-29和android-28、android-26略有差異沸移,但不影響本篇內(nèi)容事件分發(fā)機(jī)制的痪伦,看到文章的朋友可以試試雹锣。
另外蕊爵,在ViewGroup類的執(zhí)行dispatchTouchEvent()事件分發(fā),對child進(jìn)行判斷的時候饲齐,可以看到系統(tǒng)源碼中并不是簡單的Activity-PhoneWindow(Window)-DecorView-我們的布局Layout-具體的View,而是:

  DecorView@ebcfe75[ActivityG]
  android.widget.LinearLayout{93999f3 V.E...... ........ 0,0-1440,2712}
  android.widget.FrameLayout{17cada3 V.E...... ........ 0,84-1440,2712}
  androidx.appcompat.widget.ActionBarOverlayLayout{ea7fb39 V.E...... ........ 0,0-1440,2628 #7f070056 app:id/decor_content_parent}
  androidx.appcompat.widget.ContentFrameLayout{32e548 V.E...... ........ 0,196-1440,2628 #1020002 android:id/content}
  androidx.constraintlayout.widget.ConstraintLayout{d004903 V.E...... ........ 0,0-1440,2432}
  android.widget.LinearLayout{11a8c3f V.E...... ........ 0,0-1440,2432}
  com.futurenavi.demo1.views.MyLinearLayout{9d95de9 V.E...... ........ 0,168-1440,336}
...
  • 可以確定DecorView是頂層View(Root View)
  • ConstraintLayout為我給ActivityG類的xml中設(shè)定的頂層布局
  • LinearLayout為我給ActivityG類的xml中設(shè)定的第二次布局
  • MyLinearLayout是前面自定義的ViewGroup
  • 另外如果沒有View消費事件咨察,則會倒序再執(zhí)行一次摄狱,完成閉環(huán)
    有興趣的朋友不妨試試

待續(xù)

  • 疑問:Activity是怎么接收到事件的宪迟?
  • 疑問:ViewGroup是如何判斷該把事件傳遞給那個child的?

總結(jié)

  • 我們知道了

  • 第一個if邏輯判斷是ACTION_DOWN新的事件,也就是說玖像,當(dāng)用戶進(jìn)行按下動作的時候祖驱,事件就開始了睡互,這里觸發(fā)了onUserInteraction()方法寇壳,可以看到該方法返回void的空方法泞歉,也就是說,如果想在Activity接收到按下事件做些什么的話挺庞,可以直接重寫onUserInteraction()方法處理掖鱼,這里就不展開了戏挡。

  • 第二個邏輯觸發(fā)getWindow().superDispatchTouchEvent(ev)方法,那么先看看getWindow()方法:

       @UnsupportedAppUsage
        private Window mWindow;
        public Window getWindow() {
            return mWindow;
        }
    

    getWindow()返回了一個Window類寻歧,繼續(xù)查看Window類:

    /**
       * 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 {
      ...
      public void setCallback(Callback callback) {
            mCallback = callback;
        }
      ...
    }
    

    發(fā)現(xiàn)Window是抽象類,注釋里大意說用于頂層窗口外觀和行為策略的抽象基類,此類的實例應(yīng)用作添加到窗口管理器磷箕。 它提供標(biāo)準(zhǔn)的UI策略呜叫,例如背景盛泡,標(biāo)題區(qū)域,默認(rèn)密鑰處理等,此抽象類的唯一現(xiàn)有實現(xiàn)是android.view.PhoneWindow菱属,需要使用Window時應(yīng)實例化
    這里先關(guān)注三個點:

    • 用于頂層窗口外觀和行為策略赏陵。
    • android.view.PhoneWindow饼齿。
    • 需要使用Window時應(yīng)實例化。

    繼續(xù)去看看PhoneWindow蝙搔,在Android Studio中雙擊Shift搜索PhoneWindow類缕溉,并查看superDispatchTouchEvent()方法:

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

    這里就牽扯出了DecorView對象:

    public class DecorView extends FrameLayout implements       RootViewSurfaceTaker, WindowCallbacks {
    ...
      public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
      }
        @Override
      public boolean dispatchTouchEvent(MotionEvent ev) {
            final Window.Callback cb = mWindow.getCallback();
            return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                    ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
      }
    ...
    }
    

    可以看到DecorView是FrameLayout的子類,是一個ViewGroup吃型。
    在dispatchTouchEvent() 方法中用到了Window.Callback():

      public abstract class Window {
      ...
      public void setCallback(Callback callback) {
            mCallback = callback;
        }
      public final Callback getCallback() {
            return mCallback;
        }
      public interface Callback {...}
      ...
    }
    

    由于這個callback是在Activity中實現(xiàn)的:

    public class Activity extends ContextThemeWrapper
          implements LayoutInflater.Factory2,
          Window.Callback, ...{
       @UnsupportedAppUsage
        final void attach(Context context, ActivityThread aThread,...,Window window, ...) {
              ...
              mWindow.setCallback(this);
              ...
        }
    }
    

    所以前面DecorView的dispatchTouchEvent(MotionEvent ev) 中的判斷是傳遞給給Activity還是直接傳遞給父類(VIewGroup)的結(jié)果是cb.dispatchTouchEvent()证鸥,即將事件傳遞給了Activity,反之則會走super.dispatchTouchEvent(ev)勤晚,即父類VIewGroup的dispatchTouchEvent()的方法枉层,這里后面在來說。


  • 最后執(zhí)行onTouchEvent(ev)赐写,即前面說到的onTouchEvent()在dispatchTouchEvent()中執(zhí)行

結(jié)合前面的三個關(guān)注點:每個Activity包含一個Window鸟蜡,而Window其實是一個PhoneWindow,每個PhoneWindow則包含一個DecorView挺邀,而每個DecorVIew都是一個Framelayout(也就是一個ViewGroup)揉忘。
也就是說,在開發(fā)者層面端铛,頂層窗口是Window泣矛,頂層布局是DecorView(FrameLayout )

在Activity中的onCreate()方法第一時間會去執(zhí)行了setContentView方法沦补,即設(shè)置頁面UI(布局)乳蓄,前面說到ViewGroup是Android中的布局的父類:

Activity的組成結(jié)構(gòu) View類及其子類
Activity的組成結(jié)構(gòu)
View類及其子類(ViewGroup及其子類(布局類))

場景/疑問/好奇

講講 Android 的事件分發(fā)機(jī)制
查看過源碼中的事件攔截方法嗎咪橙?或者說在進(jìn)行事件分發(fā)的時候如何讓正常的分發(fā)方式進(jìn)行攔截夕膀?
在一個列表中,同時對父 View 和子 View 設(shè)置點擊方法美侦,優(yōu)先響應(yīng)哪個产舞?為什么會這樣?

上面的問題無論是面試還是平時開發(fā)都是很常見/常用的菠剩,各位看官心里是否或清晰或模糊或好奇呢易猫?

在Android中,通常以一個Activity作為一個頁面具壮,在Activity的組成情況中我們知道每個Activity包含一個Window准颓,而Window其實是一個PhoneWindow哈蝇,每個PhoneWindow則包含一個DecorView,而每個DecorVIew都是一個Framelayout攘已。

而事件炮赦,總是有一個觸發(fā)點的,對于手機(jī)的事件通常從用戶按下開始的样勃,事件分發(fā)對應(yīng)著三個重要方法:

  • dispatchTouchEvent()
  • onTouchEvent()
  • onInterceptTouchEvent()
    (一下讓我想起了當(dāng)年在宿舍一遍遍背onInterceptTouchEvent的樣子吠勘,捂臉)

上面三個方法,從字面就非常好理解峡眶,分別是事件分發(fā)剧防、事件處理、事件攔截辫樱,在Android中無論AppCompatActivity峭拘、FragmentActivity都是Activity的子類,

舉個不恰當(dāng)?shù)牧凶邮ㄊ睿珹ctivity接到了一個“水幕電影”的項目棚唆,然后扭頭就轉(zhuǎn)包給了ViewGroup,這個ViewGroup是沒有能力搞這東西的心例,但是又想賺錢就接了宵凌,然后又轉(zhuǎn)包給了另外一個ViewGroup2,正所謂人以類聚止后,這ViewGroup2也是不會搞想賺錢的主瞎惫,最后這個ViewGroup2把這活給了一個專業(yè)對口的View,這個View漂亮的完成了項目译株,引起了一片好評瓜喇。

總結(jié)一下,Activity是第一時間接收到點擊事件的歉糜,然后會分發(fā)給頂層ViewGroup乘寒,頂層ViewGroup會去檢查是否攔截,不攔截則繼續(xù)往下傳遞匪补,若UI復(fù)雜則會重復(fù)該步驟伞辛,直到事件分發(fā)到具體View進(jìn)行事件處理。看起來似乎也是挺簡單明的夯缺,那么蚤氏,通過源碼來看看,Android具體在這如何進(jìn)行三大環(huán)節(jié)中進(jìn)行事件分發(fā)的踊兜。

DecorView@ebcfe75[ActivityG]
android.widget.LinearLayout{93999f3 V.E...... ........ 0,0-1440,2712}
android.widget.FrameLayout{17cada3 V.E...... ........ 0,84-1440,2712}
androidx.appcompat.widget.ActionBarOverlayLayout{ea7fb39 V.E...... ........ 0,0-1440,2628 #7f070056 app:id/decor_content_parent}
androidx.appcompat.widget.ContentFrameLayout{32e548 V.E...... ........ 0,196-1440,2628 #1020002 android:id/content}
androidx.constraintlayout.widget.ConstraintLayout{d004903 V.E...... ........ 0,0-1440,2432}
android.widget.LinearLayout{11a8c3f V.E...... ........ 0,0-1440,2432}
com.futurenavi.demo1.views.MyLinearLayout{9d95de9 V.E...... ........ 0,168-1440,336}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末竿滨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌于游,老刑警劉巖毁葱,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贰剥,居然都是意外死亡头谜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門鸠澈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柱告,“玉大人,你說我怎么就攤上這事笑陈〖识龋” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵涵妥,是天一觀的道長乖菱。 經(jīng)常有香客問我,道長蓬网,這世上最難降的妖魔是什么窒所? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮帆锋,結(jié)果婚禮上吵取,老公的妹妹穿的比我還像新娘。我一直安慰自己锯厢,他們只是感情好皮官,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著实辑,像睡著了一般捺氢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剪撬,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天摄乒,我揣著相機(jī)與錄音,去河邊找鬼残黑。 笑死馍佑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的萍摊。 我是一名探鬼主播挤茄,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼如叼,長吁一口氣:“原來是場噩夢啊……” “哼冰木!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤踊沸,失蹤者是張志新(化名)和其女友劉穎歇终,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逼龟,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡评凝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了腺律。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奕短。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匀钧,靈堂內(nèi)的尸體忽然破棺而出翎碑,到底是詐尸還是另有隱情,我是刑警寧澤之斯,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布日杈,位于F島的核電站,受9級特大地震影響佑刷,放射性物質(zhì)發(fā)生泄漏莉擒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一瘫絮、第九天 我趴在偏房一處隱蔽的房頂上張望涨冀。 院中可真熱鬧,春花似錦麦萤、人聲如沸蝇裤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栓辜。三九已至,卻和暖如春垛孔,著一層夾襖步出監(jiān)牢的瞬間藕甩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工周荐, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留狭莱,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓概作,卻偏偏與公主長得像腋妙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子讯榕,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345