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ā)。
實踐
- 當(dāng)事件正常從ViewGroup分發(fā)到對應(yīng)的子View,log打印如下:

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

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

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()方法消費事件。