ViewGroup事件傳遞
上篇筆記中介紹道,ViewGroup中參與事件傳遞的方法有以下三個:
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent ev)
ViewGroup事件傳遞方法調用
dispatchTouchEvent
當我們點擊控件時,首先調用的是布局組件(ViewGroup)的dispatchTouchEvent方法。這個方法和View中的不太一樣,它需要去確定用戶到底點擊的是哪個子View,并將事件分發給他。然后調用這個子View的dispatchTouchEvent,最后按照上篇筆記中記錄的流程,執行事件的分發。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是否將事件分發給子View。而在代碼中我們可以看到,這個變量的值就是onInterceptTouchEvent()方法的返回值。接下來我們看onInterceptTouchEvent()方法.
onInterceptTouchEvent
onInterceptTouchEvent源代碼:
Public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
這個方法的代碼及其簡單。當該方法返回true時,即事件交給ViewGroup自身來消費,后續調用ViewGroup的onTouch和onTouchEvent方法;當返回false時,事件將分發給子View來觸發。
當事件交由子View來觸發時,那么ViewGroup.diapatchTouchEvent方法的返回值完全有子View的diapatchTouchEvent返回值決定。如果子View是可點擊的,那么ViewGroup的diapatchTouchEvent返回true,也就是代表事件已經被子View消費掉了,即不會執行ViewGroup的onTouch和onTouchEvent方法。如果子View沒有消費事件,那么會調用ViewGroup的父類的diapatchTouchEvent方法,進行事件分發,交由onTouch和onTouchEvent觸發。當我們點擊ViewGroup的空白區域,同樣會調用父類的diapatchTouchEvent進行事件分發。
實踐
- 當事件正常從ViewGroup分發到對應的子View,log打印如下:
2.當事件在ViewGroup中被攔截,onInterceptTouchEvent方法返回true,Log打印如下:
3.當點擊了ViewGroup中不可點擊的子View,Log打印如下:
Button要設置成不可點擊狀態,只能通過將Button控件的enable屬性設置為false。
事件攔截
事件傳遞方法返回值都是boolean型,true代表事件已經被消費,false代表事件沒有被消費,可以分發。
onTouch方法返回true
onTouch方法默認返回值都是false,當我們使其返回true,將事件攔截在了touch事件。也就是說在dispatchTouchEvent方法中不會調用onTouchEvent方法(觸發點擊事件在該方法中)。
onInterceptTouchEvent方法返回true
onInterceptTouchEvent方法默認返回是false,當我們將其返回值設定為true時,也就是讓事件不能分發給ViewGroup的子View。讓ViewGroup自身來消費事件。
Activity觸摸事件傳遞
其實,當我們觸摸屏幕時,事件傳遞的順序應該是activity->viewgroup->view。和VIewGroup,View相同,Activity也有dispatchTouchEvent和onTouchEvent方法。觸摸事件最先調用的是Activity的dispatchTouchEvent方法,代碼如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
首先判斷當前事件是否是ACTION_DOWN,如果是調用onUserInteraction()方法。該方法可以在Activity中被重寫,在事件被分發前會調用該方法。該方法的返回值是void型,不會對事件傳遞結果造成影響。接著會判斷getWindow().superDispatchTouchEvent(ev)的執行結果。這一步首先通過getWindow()方法得到Activity被加載時,創建的PhoneWindow對象,然后調用PhoneWindow對象中的DecorView成員變量的dispatchTouchEvent方法對事件進行分發。如果事件沒有被DecorView及其子View消費,那么調用Activity的onTouchEvent()方法消費事件。