Android事件傳遞機制之ViewGroup

ViewGroup事件傳遞

上篇筆記中介紹道,ViewGroup中參與事件傳遞的方法有以下三個:

public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent ev)

ViewGroup事件傳遞方法調(diào)用

dispatchTouchEvent

當(dāng)我們點擊控件時,首先調(diào)用的是布局組件(ViewGroup)的dispatchTouchEvent方法。這個方法和View中的不太一樣,它需要去確定用戶到底點擊的是哪個子View,并將事件分發(fā)給他。然后調(diào)用這個子View的dispatchTouchEvent,最后按照上篇筆記中記錄的流程,執(zhí)行事件的分發(fā)。ViewGroup.dispatchTouchEvent()部分源代碼如下:

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
                 || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}

上述代碼中,有個比較重要的局部變量:intercepted。這個變量決定了ViewGroup是否將事件分發(fā)給子View。而在代碼中我們可以看到,這個變量的值就是onInterceptTouchEvent()方法的返回值。接下來我們看onInterceptTouchEvent()方法.

onInterceptTouchEvent

onInterceptTouchEvent源代碼:

Public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
}

這個方法的代碼及其簡單。當(dāng)該方法返回true時,即事件交給ViewGroup自身來消費,后續(xù)調(diào)用ViewGroup的onTouch和onTouchEvent方法;當(dāng)返回false時,事件將分發(fā)給子View來觸發(fā)。
當(dāng)事件交由子View來觸發(fā)時,那么ViewGroup.diapatchTouchEvent方法的返回值完全有子View的diapatchTouchEvent返回值決定。如果子View是可點擊的,那么ViewGroup的diapatchTouchEvent返回true,也就是代表事件已經(jīng)被子View消費掉了,即不會執(zhí)行ViewGroup的onTouch和onTouchEvent方法。如果子View沒有消費事件,那么會調(diào)用ViewGroup的父類的diapatchTouchEvent方法,進行事件分發(fā),交由onTouch和onTouchEvent觸發(fā)。當(dāng)我們點擊ViewGroup的空白區(qū)域,同樣會調(diào)用父類的diapatchTouchEvent進行事件分發(fā)。

實踐

  1. 當(dāng)事件正常從ViewGroup分發(fā)到對應(yīng)的子View,log打印如下:
事件傳遞ViewGroup-->View.png

2.當(dāng)事件在ViewGroup中被攔截,onInterceptTouchEvent方法返回true,Log打印如下:

事件在ViewGroup中被攔截.png

3.當(dāng)點擊了ViewGroup中不可點擊的子View,Log打印如下:

Button屬性enable設(shè)置為false.png

Button要設(shè)置成不可點擊狀態(tài),只能通過將Button控件的enable屬性設(shè)置為false。

事件攔截

事件傳遞方法返回值都是boolean型,true代表事件已經(jīng)被消費,false代表事件沒有被消費,可以分發(fā)。

onTouch方法返回true

onTouch方法默認返回值都是false,當(dāng)我們使其返回true,將事件攔截在了touch事件。也就是說在dispatchTouchEvent方法中不會調(diào)用onTouchEvent方法(觸發(fā)點擊事件在該方法中)。

onInterceptTouchEvent方法返回true

onInterceptTouchEvent方法默認返回是false,當(dāng)我們將其返回值設(shè)定為true時,也就是讓事件不能分發(fā)給ViewGroup的子View。讓ViewGroup自身來消費事件。

Activity觸摸事件傳遞

其實,當(dāng)我們觸摸屏幕時,事件傳遞的順序應(yīng)該是activity->viewgroup->view。和VIewGroup,View相同,Activity也有dispatchTouchEvent和onTouchEvent方法。觸摸事件最先調(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);
}

首先判斷當(dāng)前事件是否是ACTION_DOWN,如果是調(diào)用onUserInteraction()方法。該方法可以在Activity中被重寫,在事件被分發(fā)前會調(diào)用該方法。該方法的返回值是void型,不會對事件傳遞結(jié)果造成影響。接著會判斷getWindow().superDispatchTouchEvent(ev)的執(zhí)行結(jié)果。這一步首先通過getWindow()方法得到Activity被加載時,創(chuàng)建的PhoneWindow對象,然后調(diào)用PhoneWindow對象中的DecorView成員變量的dispatchTouchEvent方法對事件進行分發(fā)。如果事件沒有被DecorView及其子View消費,那么調(diào)用Activity的onTouchEvent()方法消費事件。

參考

文獻1、文獻2文獻3、文獻4

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