android 事件分發機制
參考資料
Android 事件分發機制源碼和執行個體解析
Android View 事件分發機制詳解
圖解 Android 事件分發機制
圖解View的事件分發機制
原理
- 分發事件 的起始點:
從 Activity 開始,Activity 源碼
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);
}
getWindow().superDispathTouchEvent 就是用來分發事件到
DecorView 中。如果整個 ViewTree 沒有消費事件,會調用
Activity 的 onTouchEvent。
源碼中:
protected void onUserLeaveHint() {
}
Activity—onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
- 分發事件 的終點 也是 Activity 從源碼可知
應為 分發的方法是 acticity 自己定義的方法
dispatchTouchEvent,所有最後隻有他自己來處理該事件。
- 分發事件的過程
主要涉及 View 和 ViewGroup (在 xml 中 設定)
View 隻有 onTouchEvent 和 dispatchTouchEvent 兩個方法。
ViewGroup 有 onTouchEvent / dispatchTouchEvent 和 onInterceptTouchEvent 三個方法。
- 注意事項:
View 或 ViewGroup 有兩個核心的行為:攔截(intercept) 和 消費(consume)。這兩者是互相獨立的,攔截不一定消費。是否要攔截看 onIntercepTouchEvent。是否要消費看 onTouchEvent。
- 方法了解
dispatchTouchEvent,該方法封裝了事件分發的整個過程。是事
件分發的 排程者 和 指揮官 。的核心過程均在該方法中。下面的
onInterceptTouchEvent 和 onTouchEvent
的回調的調用就在該方法體中。是否傳遞事件到
onInterceptTouchEvent 和 onTouchEvent 由
dispatchTouchEvent 決定。
onInterceptTouchEvent,該方法決定了是否攔截事件。隻有
ViewGroup 有該回調。傳回 true 表示攔截,傳回 false
表示不攔截。自定義 View
的時候,可以重載該方法,通過一些特定的邏輯來決定是否攔截
事件。如果攔截,接下來會調用該 ViewGroup 的 onTouchEvent
來處理事件。
onTouchEvent,該方法處理了事件,并決定是否繼續消費後續
事件。該方法調用的前置條件:
該 View 攔截了事件
子 View 都不消費事件
沒有子 View
該方法正式處理 MotionEvent。傳回 true 表示消費,傳回
false 不消費。如果消費,接下來的事件還會傳遞到該 View 的
dispatchTouchEvent 中;如果不消費,後面的事件不會再傳過來。
onTouchListener 的 onTouch 回調,和 onTouchEvent
一樣,優先級比 onTouchEvent 高,如果有設定該監聽,并且
onTouch 傳回 true,就不會再調用 onTouchEvent 了。如果傳回
false,事件還是會傳遞到 onTouchEvent 中。
dispatchTransformedTouchEvent 關鍵部分
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
也就是 dispatchTransformTouchEvent 完成了分發的最後過程:
a. 傳入的 child 不為空,轉化坐标為 child 的坐标系,調用
child.dispatchTouchEvent 向 child 分發事件
b. 傳入的 child 為空,調用 super.dispatchTouchEvent
分發事件到 onTouchEvent 中
- 特殊情況 子 View –requestDisallowInterceptTouchEvent
比較特殊的情況有,子 View 可以使用 requestDisallowInterceptTouchEvent 影響去父 View
的分發,可以決定父 View 是否要調用 onInterceptTouchEvent
。比如,requestDisallowInterceptTouchEvent(true),父 View
就不用調用 onInterceptTouchEvent
來判斷攔截,而就是不攔截。
該方法可以用來解決手勢沖突。比如子 View
先消費了事件,但是後面父 View
也滿足了手勢觸發的條件而攔截事件,導緻子 View
手勢執行一半後無法繼續響應。可以使用
requestDisallowInterceptTouchEvent(true),這樣後面的事
件,父 View 不會走 onInterceptTouchEvent
回調來判斷是否要攔截事件,而是直接把事件繼續傳下來。