android中分發機制在自定義的View中尤為重要,貫穿android Activity – ViewGroup – View的Event傳遞;
為了提高了解,我們分部闡述
1.android分發機制概述
2.圖解
3.android源碼
1.android分發機制
- 在android中Event是自上而下分發的:Activity —-> ViewGroup —-> View
- 涉及到3個方法:
- dispatchTouchEvent—-事件的總排程,貫穿事件分發的主邏輯線
- onInterceptTouchEvent—- 攔截方法,攔截事件決定後序的分發是否依然向下分發
- onTouchEvent—- 事件Event真正的消費方法
- 在android中,首先是從Activity進行分發,Activity通過dispatchTouchEvent分發到DecorView的dispatchTouchEvent中,這裡的DecorView(Activity的根View)就是一個ViewGroup;在ViewGroup中進行事件分發,會有事件攔截onInterceptTouchEvent,onInterceptTouchEvent傳回true,則事件由本層消費,傳回false,繼續向下分發;
- 注意:
- Activityh和View隻有dispatchTouchEvent和onTouchEvent;
- ViewGroup有dispatchTouchEvent,onInterceptTouchEvent,onTouchEvent
以上都是基本常識,下面來看android分發的圖例;(圖解的出現更加有利于對android分發機制的了解)
2.android分發機制圖解
從圖中可以看到android的分發就是一個一層一層嵌套的過程
Activity中dispatchTouchEvent分發到ViewGroup,ViewGroup中dispatchTouchEvent分發到View,View樹一層一層的周遊分發,自上而下,然後父層dispatchTouchEvent通過接收下一層dispatchTouchEvent的傳回來決定是否終結分發流程;
有一點拗口,其實可以這樣了解:
android分發是自上而下分發,當然是在不攔截的情況下;如果攔截則交于本層消費;在不攔截的情況下,傳遞到最底層的View來消費,若底層不消費,則傳回到父類的dispatchTouchEvent,由父類消費,同理,若父類沒有消費,則繼續向上傳遞,直到Activity,就是整個分發的頂層,預設處理;
3.源碼解析
3.1首先來看Activity源碼:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//實作屏保功能
//a. 該方法為空方法
//b. 當此activity在棧頂時,觸屏點選按home,back,menu鍵等都會觸發此方法
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
- 首先在MotionEvent.ACTION_DOWN的時候會有onUserInteraction();他是一個空方法;主要是實作屏保功能,當activity在棧頂時候,點選home/back/menu均會觸發該方法;
- 然後來看核心代碼:getWindow().superDispatchTouchEvent(ev) ;提前劇透一下getWindow().superDispatchTouchEvent(ev)就是在調用ViewGroup的分發方法:dispatchTouchEvent(ev),如果ViewGroup中event被消費或者是被其子View消費掉(dispatchTouchEvent—>onTouchEvent),則Activity中dispatchTouchEvent傳回true,事件分發結束;否則Activity調用onTouchEvent(ev),由Activity完成事件消費;
### getWindow()---- return PhoneWindow
### PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
### DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
因為DecorView繼承自ViewGroup,so這裡的super.dispatchTouchEvent(event)就是ViewGroup的dispatchTouchEvent(event):
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
//解析一:
if (actionMasked == MotionEvent.ACTION_DOWN) {
//當開始一個新的觸摸手勢時,扔掉所有先前的狀态
//架構可能已經删除了前一個階段的UP或取消事件
//由于應用程式切換,ANR,或其他一些狀态改變。
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//解析二:
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != ;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
}
- 解析一:
- 在事件分發過程中首先通過 cancelAndClearTouchTargets(ev) 和resetTouchState()來cancel之前的狀态和事件,
- 解析二:
- 在ViewGroup中處理事件攔截:
-
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)不成立
事件攔截的标志位intercepted = true;事件直接攔截
-
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null)成立
繼續看disallowIntercept,如果為false,則會傳回
intercepted = onInterceptTouchEvent(ev)
為true,直接intercepted = true 攔截;
這裡需要注意一下 intercepted = onInterceptTouchEvent(ev)就是ViewGroup的攔截事件方法;
其次,父 View 會為子View 建立一個 TouchTarget 執行個體加傳入連結表中,連結清單的第一項為 mFirstTouchTarget。後續的 move 和 up 事件會直接交給該 View的dispatchTouchEvent。
在ViewGroup中攔截方法,之後姚根據攔截狀态,進行繼續的向下分發:
仍然是
public boolean dispatchTouchEvent(MotionEvent ev) {
......
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
}
//核心代碼
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
......
ViewGroup中代碼比較多,是以僅粘貼關鍵代碼,在攔截之後會周遊其子View,根據焦點和坐标定位點選的child,然後根據攔截狀态intercepted來進行處理;
這個handled就是在ViewGroup中傳回下一層的分發結果,如果handled傳回true則分發結束;如果傳回false,則事件有ViewGroup來處理;
在這裡dispatchTransformTouchEvent 完成了分發的最後過程:
1. 傳入的 child 不為空,轉化坐标為 child 的坐标系,調用 child.dispatchTouchEvent 向 child 分發事件
2. 傳入的 child 為空,調用 super.dispatchTouchEvent 分發事件到 onTouchEvent 中
由于代碼确實太多我們來簡化模拟一段僞代碼:
public boolean dispatchTouchEvent(MotionEvent e) {
boolean handled = false;
if (onInterceptTouchEvent(e)) {
//如果攔截 則在ViewGroup中消費時間
handled = onTouchEvent(e);
} else {
for (View view: childs) {
//Event在子View的處理結果
handled = view.dispatchTouchEvent(e);
if (handled) {
break;
}
}
//子View沒有消費event 傳回false ViewGroup自行處理
//當然 如果ViewGroup仍然傳回false則繼續向上傳遞
if (!handled) {
handled = onTouchEvent(e);
}
}
return consumed;
}
我們繼續看View的源碼:
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
這裡需要注意一下在View的dispatchTouchEvent中, View 為 ENABLE 的狀态并且有 mTouchListener,會先調用 onTouch。在 onTouch 傳回 false 時才會繼續調用 onTouchEvent,然後在onTouchEvent中調用performClick,傳回true則事件消費,傳回false則向上傳遞;
到這裡,事件分發整個流程源碼就結束了;由于代碼太多,是以僅粘貼關鍵代碼;
源碼解析完之後,回頭看圖解就會豁然開朗;
夏日炎炎,會有遺漏,期待回報;