Android View的事件分發機制探索

來源:互聯網
上載者:User

標籤:

概述

        Android事件傳遞機制也是Android系統中比較重要的一塊,事件類型有很多種,這裡主要討論TouchEvent的事件在framework層的傳遞處理機制。因為對於App開發人員來說,理解framework層的事件傳遞機制,就差不多了。

 

 

帶著問題來思考整個事件分發過程。

 

1、為什麼要有事件分發過程?

        當Android裝置的螢幕,接收到觸摸的動作時,螢幕驅動把壓力訊號(包括壓力大小,壓力位置等)傳遞給系統底層,然後作業系統經過一系列的處理,然後把觸摸事件一層一層的向上傳遞,最終事件會被準確的傳遞到產生事件的對象上,系統會遍曆每一個View對象,然後計算觸摸點在哪一個View中。比如A和B兩個View,是兄弟View,AView產生的觸摸事件,是不會被分發到B上面的。

 

2、怎麼看待事件序列?

        在Android系統中,一個單獨的事件基本上是沒什麼作用的,只有一個事件序列,才有意義。一個事件序列正常情況下,定義為 DOWN、MOVE(0或者多個)、UP/CANCEL。事件序列以DOWN事件開始,中間會有0或者多個MOVE事件,最後以UP事件或者CANCEL事件結束。

DOWN事件作為序列的開始,有一個很重要的職責,就是尋找事件序列的接受者,怎麼理解呢?framework 在DOWN事件的傳遞過程中,需要根據View事件處理方法(onTouchEvent)的傳回值來確定事件序列的接受者。如果一個View的onTouchEvent事件,在處理DOWN事件的時候返回true,說明它願意接受並處理該事件序列。

 

3、Android的framework層如何處理事件的分發過程?

        觸摸事件到了framework層之後,首先會被傳遞到Activity,然後Activity會把事件委託給它內部的Window對象進行分發處理,而Window對象又會委託它內部的DecorView進行事件分發處理。我們都知道,DecorView是整棵View樹的根節點,所以整個事件傳遞過程的複雜度就是事件在View樹種分發傳遞的複雜度。 Android View架構提供了3個對事件的主要操作概念文件。

    1、事件的分發機制,dispatchTouchEvent。主要是parent根據觸摸事件的產生位置,以及child是否願意負責處理該系列事件等狀態,向其child分發事件的機制。

    2、事件的攔截機制,onInterceptTouchEvent。主要是parent根據它內部的狀態、或者child的狀態,來把事件攔截下來,阻止其進一步傳遞到child的機制。

    3、事件的處理機制,onTouchEvent。主要是事件序列的接受者(可以是一個View或者ViewGroup),對事件作出處理,並且向其parent傳遞處理結果的機制。

 

4、上述三個機制,是怎麼向其調用者傳遞處理結果的?

        在Java中,傳遞計算結果,有很多種途徑,這裡採用的是一種適用於同步調用的方法,傳回值的方法。每個機制都使用boolean類型作為其傳回值,那麼每個機制的每個傳回值是什麼含義呢。

    1、事件的分發機制,dispatchTouchEvent。

        true-事件被以該節點為根節點的View樹成功處理,此時該事件就算是處理完成了,事件不會再向上返還給View的父節點(把事件分發過來的那個節點)。

        false-以該節點為根節點的View樹種,沒有一個View(包括該View)成功處理了此事件,所以事件會向上返還給View的父節點(把事件分發過來的那個節點)。

    2、事件的攔截機制,onInterceptTouchEvent。主要是parent根據它內部的狀態、或者child的狀態,來把事件攔截下來,阻止其進一步傳遞到child的機制。

        true-當前ViewGroup(因為View中沒有該方法,而沒有child的VIew也不需要有攔截機制)希望該事件不再傳遞給其child,而是希望自己處理。

        false-當前ViewGroup不準備攔截該事件,事件正常向下分發給其child。

    3、事件的處理機制,onTouchEvent。主要是事件序列的接受者(可以是一個View或者ViewGroup),對事件作出處理,並且向其parent傳遞處理結果的機制。

        true-表示該View成功處理了該事件,該處理結果會向上通知給其parent。

        false-表示該View沒有成功處理該事件,那麼它的parent會有機會來處理該事件(parent標記為事件序列接受者,parent 的 onTouchEvent 在 Down 事件時返回true)。

 

 

 

原始碼分析

原始碼基於SDK 23

View:1、dispatchTouchEvent:

/** 把事件分發到目標對象,因為這裡是View對象,預設不含有child,所以這裡他會把事件分發給自己 */

public boolean dispatchTouchEvent(MotionEvent event);

原始碼:

不給出,有興趣的讀者執行查閱SDK

虛擬碼:

public boolean dispatchTouchEvent(MotionEvent event){    boolean result = false;    //如果有事件監聽器,先讓監聽器處理事件。    if (mOnTouchListener.onTouch(event)) {        //如果監聽器成功處理了該事件,處理結果設定為true。        result = true;    }    //如果沒有監聽器,就調用自身的onTouchEvent方法來處理事件。    if (!resutlt && onTouchEvent(event)) {        //如果自身的onTouchEvent成功處理事件,處理結果設定為true。        result = true;    }    return result;}

 


 

 

ViewGroup:1、onInterceptTouchEvent

/** 預設實現是返回false,也就是預設不攔截任何事件 */

public boolean onInterceptTouchEvent(MotionEvent ev);

 

2、dispatchTouchEvent

/** 根據內部攔截狀態,向其child或者自己分發事件 */

public boolean dispatchTouchEvent(MotionEvent ev);

原始碼:

不給出,有興趣的讀者執行查閱SDK

虛擬碼:

public boolean dispatchTouchEvent(MotionEvent ev) {    if (ACTION_DOWN事件 || 沒有事件處理對象) {        if (允許攔截事件,該標誌位由child調用requestDisallowInterceptTouchEvent<span style="font-family:微軟雅黑;font-size:14px;">設定</span>) {            //查詢攔截機制的結果,根據該結果來判斷是否需要攔截            intercepted = onInterceptTouchEvent(ev);        } else {            //不允許攔截,那麼不攔截            intercepted = false;        }    } else {         //不是DOWN,並且有處理對象,允許攔截,中斷事件傳遞        intercepted = true;    }    if (不取消 && 不攔截) {        if (ACTION_DOWN) { //找尋接收事件序列的對象,其他事件不需要再計算事件產生對象,試想一下滑動一個ListView,當手指滑動出ListView的範圍時,依然還是ListView響應後續事件。            for (遍曆所有childView) {                if (觸摸點不在childView內部) {                    continue;                }                if (childView.dispatchTouchEvent(event)) {                    儲存處理該事件的View,後續事件直接傳遞到該View,不要重新計算;                }            }        }        if (還沒有事件處理對象) {            //當前View樹中沒找到合適的child處理對象,把事件給自己處理,View.dispatchTouchEvent()就是把事件分發給自己            super.dispatchTouchEvent(event);        } else {            //傳遞給child            childView.dispatchTouchEvent(event);        }    } else if (攔截) {        //攔截事件,把事件給自己處理,View.dispatchTouchEvent()就是把事件分發給自己        super.dispatchTouchEvent(event);    }    return 處理結果;}

 


 

 

3、requestDisallowInterceptTouchEvent

/** 乾澀parent的事件分發機制,通知parent,是否攔截後續事件,如果設定為true,parent就不會攔截該事件,不管什麼狀態。設定為false,parent走正常的攔截流程 */

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);

原始碼:

不給出,有興趣的讀者執行查閱SDK

虛擬碼:

public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {    if (已經是當前要設定的狀態) {        // 已經處於這個狀態, 假設我們的parent也是這個狀態        return;    }    設定該狀態;    // 傳遞給parent    if (有父容器) {        設定父容器的攔截狀態;    }}

 


 

 

自己動手

我們都知道,如果ScrollView內部嵌套ListView,那麼ListView是不可以滑動的,效果如所示:

 

 

那麼其實這就是典型的事件衝突問題,就是說,原本應該被ListView用來上下滑動的事件,被ScrollView攔截了。就導致ListView不能正常滑動。

我們來看一下ScrollView的原始碼:

onInterceptTouchEvent的虛擬碼:

public boolean onInterceptTouchEvent(MotionEvent ev) {    /*     * 這個方法決定了我們是否要攔截事件.     * 如果返回true, onTouchEvent會被調用並且我們開始做實際的Scroll操作.     */    /*    * 大部分迴圈的狀態: 使用者在再拖拽的狀態並且正在移動手指,    * 我們希望攔截這個事件    */    final int action = ev.getAction();    if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {        return true;    }        //其他動作    ......................}

 


所以正常的上下拖拽,ScrollView都會攔截。

 

 

那麼我們下面改進一下,就是當我們滑動ScrollView中非ListView的地區時,ScrollView滑動,而我們滑動ListView的時候,ListView滑動,效果看起來如所示:

 

這裡解決方案如下:

既然ScrollView會攔截事件,那麼當我們滑動ListView的時候,我們不希望ScrollView攔截事件,這裡我們繼承ListView,在onTouchEvent中,請求ScrollView不要攔截事件。

部分代碼如下:

@Overridepublic boolean onTouchEvent(MotionEvent ev) {    super.onTouchEvent(ev);    switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            getParent().requestDisallowInterceptTouchEvent(true);            break;        case MotionEvent.ACTION_MOVE:            break;        case MotionEvent.ACTION_UP:            getParent().requestDisallowInterceptTouchEvent(false);            break;        default:            break;    }    return  true;}

 


這樣就可以很好的解決事件衝突的問題。

還有一種方法就是覆寫parent的onInterceptTouchEvent方法,來修改事件攔截的狀態。

 

Android View的事件分發機制探索

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.