Android觸摸事件分發(fā)機制

一 概述

觸摸事件的分發(fā)機制是安卓開發(fā)中的基礎(chǔ)知識,但這塊知識又有點繞,總是讓人覺得似懂非懂。其實安卓事件傳遞就是把用戶觸摸屏幕時的touch事件封裝成MotionEvent對象在Activity、ViewGroup和View中傳遞并處理該touch事件的過程。

二 觸摸事件分發(fā)的方法

現(xiàn)在我們知道觸摸事件是在Activity、ViewGroup和View中進行傳遞的,對應(yīng)的方法如下:

  1. Activity
    Activity不對觸摸事件進行攔截,收到觸摸事件后直接分發(fā)給ViewGroup,如果所有的view最后都沒有處理該觸摸事件,會調(diào)用Activity的onTouchEvent方法進行處理,因此Activity處理觸摸事件的方法為:
    dispatchTouchEvent
    onTouchEvent
  2. ViewGroup
    當(dāng)ViewGroup收到觸摸事件后,它可以分發(fā)給自己的子View但在分發(fā)之前可以判斷是否需要攔截該觸摸事件,也可以調(diào)用自己的onTouchEvent方法處理觸摸事件,因此ViewGroup處理觸摸事件的方法有三個:
    dispatchTouchEvent
    onInterceptTouchEvent
    onTouchEvent
  3. View
    View和Activity一樣可以接收和處理觸摸事件但不能攔截觸摸事件,畢竟View下面也沒有子View存在了,攔截沒有意義。故View中的方法為:
    dispatchTouchEvent
    onTouchEvent

三 觸摸事件的傳遞機制

  1. 在Activity中
    當(dāng)用戶點擊屏幕時Activity最先收到觸摸事件此時會調(diào)用Activity的dispatchTouchEvent方法,源碼如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

可以看到Activity調(diào)用getWindow().superDispatchTouchEvent(ev)方法繼續(xù)把觸摸事件傳遞給包含的View,如果有View處理了該事件則返回true,事件傳遞結(jié)束;如果沒有View處理該事件則調(diào)用Activity的onTouchEvent(ev)方法處理該事件,無論在Activity的onTouchEvent(ev)方法中是否消費該事件,該事件的傳遞都結(jié)束了。

  1. 在ViewGroup中
    當(dāng)在Activity中調(diào)用getWindow().superDispatchTouchEvent(ev)方法時,Touch事件會被傳遞給Activity包含的最外層ViewGroup,然后層層向下傳遞。我們分析ViewGroup是如何處理Touch事件的。
    當(dāng)Touch事件傳遞到ViewGroup會先調(diào)用ViewGroup的dispatchTouchEvent方法:
 /**
  * 源碼分析:ViewGroup.dispatchTouchEvent()
  */ 
    public boolean dispatchTouchEvent(MotionEvent ev) { 

    ... // 僅貼出關(guān)鍵代碼

    // ViewGroup每次事件分發(fā)時,都需調(diào)用onInterceptTouchEvent()詢問是否攔截事件
    if (disallowIntercept || !onInterceptTouchEvent(ev)) {  

    // 判斷值1:disallowIntercept = 是否禁用事件攔截的功能(默認是false),可通過調(diào)用requestDisallowInterceptTouchEvent()修改
    // 判斷值2: !onInterceptTouchEvent(ev) = 對onInterceptTouchEvent()返回值取反
            // a. 若在onInterceptTouchEvent()中返回false(即不攔截事件),就會讓第二個值為true,從而進入到條件判斷的內(nèi)部
            // b. 若在onInterceptTouchEvent()中返回true(即攔截事件),就會讓第二個值為false,從而跳出了這個條件判斷

        ev.setAction(MotionEvent.ACTION_DOWN);  
        final int scrolledXInt = (int) scrolledXFloat;  
        final int scrolledYInt = (int) scrolledYFloat;  
        final View[] children = mChildren;  
        final int count = mChildrenCount;  

    // 通過for循環(huán),遍歷了當(dāng)前ViewGroup下的所有子View
    for (int i = count - 1; i >= 0; i--) {  
        final View child = children[i];  
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                || child.getAnimation() != null) {  
            child.getHitRect(frame);  

            // 判斷當(dāng)前遍歷的View是不是正在點擊的View,從而找到當(dāng)前被點擊的View
            // 若是,則進入條件判斷內(nèi)部
            if (frame.contains(scrolledXInt, scrolledYInt)) {  
                final float xc = scrolledXFloat - child.mLeft;  
                final float yc = scrolledYFloat - child.mTop;  
                ev.setLocation(xc, yc);  
                child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

                // 條件判斷的內(nèi)部調(diào)用了該View的dispatchTouchEvent()
                // 即 實現(xiàn)了點擊事件從ViewGroup到子View的傳遞(具體請看下面的View事件分發(fā)機制)
                if (child.dispatchTouchEvent(ev))  { 

                mMotionTarget = child;  
                return true; 
                // 調(diào)用子View的dispatchTouchEvent后是有返回值的
                // 若該控件可點擊,那么點擊時,dispatchTouchEvent的返回值必定是true,因此會導(dǎo)致條件判斷成立
                // 于是給ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                // 即把ViewGroup的點擊事件攔截掉

                        }  
                    }  
                }  
            }  
        }  
    }  
}

/**
* 作用:是否攔截事件
* 說明:
*     a. 返回true = 攔截,即事件停止往下傳遞(需手動設(shè)置,即復(fù)寫onInterceptTouchEvent(),從而讓其返回true)
*     b. 返回false = 不攔截(默認)
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {  
    return false;
} 

從代碼中可以看出在ViewGroup的dispatchTouchEvent方法中先判斷該ViewGroup是否攔截該Touch事件,如果攔截了,Touch事件就不會往下傳遞而是直接調(diào)用ViewGroup的onTouchEvent方法處理事件;如果沒有攔截則遍歷所有子View找到正在點擊的那個View并把Touch事件傳遞給它。
3.在View中
View收到Touch事件后會先調(diào)用View的dispatchTouchEvent方法,在dispatchTouchEvent方法中調(diào)用該View的onTouchEvent方法去處理該Touch事件,如果該View的onTouchEvent方法返回true則表示該View消費了該事件,否則會繼續(xù)調(diào)用其父View的onTouchEvent方法去處理該事件,直到某個View的onTouchEvent方法消費了該事件,或者傳遞到Activity的onTouchEvent方法,則事件傳遞結(jié)束。
在View的onTouchEvent方法中如果接收并消費了ACTION_DOWN事件,則該View會接收到后續(xù)的ACTION_MOVE、ACTION_UP等事件;反之,如果該View沒有消費ACTION_DOWN事件則后續(xù)的事件不會再傳遞給該View。

四 注意事項

  1. ViewGroup的onInterceptTouchEvent方法默認返回false,ViewGroup進行事件分發(fā)都會調(diào)用該方法,但是一旦onInterceptTouchEvent方法返回true則表示該ViewGroup攔截了觸摸事件,后續(xù)進行事件分發(fā)不再調(diào)用onInterceptTouchEvent方法。
    舉個栗子:我們在onInterceptTouchEvent方法中判斷是ACTION_MOVE事件就返回true,在該ViewGroup的子View可以收到ACTION_DOWN事件,如該子View消費了ACTION_DOWN事件,則在第一個ACTION_MOVE事件到來時,ViewGroup會攔截該事件,但是并不會調(diào)用ViewGroup的onTouchEvent方法,同時把ACTION_CANCEL事件傳遞給子View。后續(xù)的事件都不會傳遞給子View了,而是直接調(diào)用ViewGroup的onTouchEvent方法去處理。
  2. View的onTouch方法會先于onTouchEvent方法執(zhí)行,如下所示(onClick在onTouchEvent方法中執(zhí)行):
button1.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Log.e("onTouch","touch:button1");
        //返回true不執(zhí)行onClick方法
        //返回false接著執(zhí)行onClick方法
        return false;
    }
});


button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Log.e("button1","點擊了:button1");
    }
});
  1. View的onClick方法是在ACTION_UP事件之后執(zhí)行的,并不是在ACTION_DOWN事件到來時就執(zhí)行。因此如果在父View中攔截了ACTION_MOVE或者ACTION_UP事件,是不會執(zhí)行該方法的。
  2. 可以調(diào)用getParent().requestDisallowInterceptTouchEvent(true)方法請求父View不要攔截Touch事件。注意這個方法不能在子View初始化時調(diào)用(無效),最好在子View接收到Touch事件也就是在子View的dispatchTouchEvent方法中調(diào)用。調(diào)用完該方法后,父View以及父View的父View就不會再調(diào)用onInterceptTouchEvent方法去判斷是否攔截了。

好了,觸摸事件的傳遞機制就講到這里啦,有不對的地方歡迎留言指正。

最后編輯于
?著作權(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)容