Android 事件分發機制原始碼詳解-最新 API
大體內容如下
- 概念
- Activity 對事件的分發
- ViewGroup 的事件分發過程
- View 的事件分發過程
- View 的點選事件處理
-
總結
分析了原始碼可以得到的結論
事件的傳遞過程文字描述
事件傳遞機制流程圖
虛擬碼展示事件分發攔截和消費三者的關係
本篇文章是基於最新 Android 原始碼 ( API27 ),進行分析總結的,可以直接翻到文章末尾檢視「原始碼總結」和「事件傳遞流程圖」,帶著大體流程和結論去看原始碼,效率更高。
概念
事件:就是使用者手指從觸控螢幕的那一刻起,到手指離開螢幕的那一刻為止,中間產生的一系列動作,(DOWN MOVE UP等)都是事件,都被封裝到了 MotionEvent 中。
所謂事件分發:就是當一個 MotionEvent 產生以後,系統需要把它傳遞給某一個具體的 View,而這個傳遞的過程就是分發過程。
事件的大體流向:
事件一級一級的往下傳遞,如果沒有任何一個 View 消耗掉事件,那麼最終還是會傳遞給 Activity 的,於是就有了網上的說法,事件傳遞是由外向內,事件消耗是由內向外傳遞的。
分發過程中幾個重要的方法:
dispatchTouchEvent(MotionEvent)
onInterceptTouchEvent(MotionEvent)
onTouchEvent(MotionEvent)
requestDisallowInterceptTouchEvent(boolean)
下面開始一步一步詳細分析原始碼:
Activity 對事件的分發
事件分發的第一個回撥方法就是 dispatchTouchEvent,每次都會呼叫
/**
* Activity#dispatchTouchEvent
* You can override this to intercept all touch screen events before they are dispatched to the window.
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
說明:
onUserInteraction() 是一個空實現的方法,官方示意為:實現這個方法,就會告訴你使用者與裝置已經開始互動了;與之對應的還有一個 onUserLeaveHint(),這兩個方法可以配合起來,來決定狀態列顯示通知和取消的時機。
第二個 if 是重點,如果 Window.superDispatchTouchEvent(ev) 返回 true,那麼事件被消費,到此就結束了。如果返回的 false,即事件一級一級向下傳遞,直至到最後一個 View#OnTouchEvent() 全部返回了 false,那麼最終會回撥到 Activity#onTouchEvent() 方法。
getWindow() 返回的就是一個 Window,是一個抽象類,它的唯一實現類是 PhoneWindow,Window#superDispatchTouchEvent(MotionEvent)也是一個抽象方法,所以事件是傳遞到了PhoneWindow#superDispatchTouchEvent(MotionEvent)
// PhoneWindow#superDispatchTouchEvent(MotionEvent)
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
我們看到事件進一步通過 mDecor 進行分發了,DecorView 就是我們在 Activity 裡邊通過 setContentView(xxx) 設定的佈局掛載到的一個頂級父 View,換句話說,我們在 Activity 中通過 setContentView(xxx) 設定的 View,其實就是 DecorView的一個子 View。
DecorView 繼承自 FrameLayout,是 ViewGroup 型別。
簡單貼下 DecorView 的程式碼,下一篇會分析 Activity 的 setContentView(xxx) 到底是怎麼載入我們的佈局的
Activity#setContentView(int layoutResID) -> 會回撥到 PhoneWindow#setContentView(int)
// DecorView的例項化過程
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
......
// PhoneWindow#setContentView(int)
@Override
public void setContentView(int layoutResID) {
// installDecor() new 出來一個物件
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
......
}
說明:
installDecor() -> 回撥到 generateDecor(int),在該方法中最終通過 new DecorView(Context, int, PhoneWindow,WindowManager.LayoutParams) 出來的,留到下一篇分析
......
}
// 在 API 27,DecorView 單獨是一個類,是 ViewGroup 型別
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
......
// DecorView#superDispatchTouchEvent(MotionEvent)
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event); // 實際呼叫的就是 ViewGrup 的方法
}
......
}
看到這裡,我們已經知道了,其實啥也沒幹,事件就是簡單的從 Activity 傳到了 ViewGroup 的dispatchTouchEvent()。
ViewGroup 的事件分發過程
ViewGroup 對事件的分發,就是通過 ViewGroup#dispatchTouchEvent(MotionEvent) 來進行事件傳遞的過程
// ViewGroup#dispatchTouchEvent(MotionEvent)
// 這個方法每次都會呼叫
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// 該 if 條件預設返回 true;除非當前的 Window 被另一個可見的 Window 部分或者全部遮擋掉了,就會丟棄掉該事件
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 程式碼片段一
if (actionMasked == MotionEvent.ACTION_DOWN) {
// 這個方法很關鍵,只要是 DOWN 事件傳遞到這裡,會清除一些狀態
// 注意 執行完 cancelAndClearTouchTargets(ev) 方法後 mFirstTouchTarget == null,
// 這個具體是什麼等下細說
// 先記住一個結論:事件如果能夠正常傳遞給子 View,並且被子 View 消費掉,
// 那麼mFirstTouchTarget 就會被賦值(即 mFirstTouchTarget != null)
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 程式碼片段二 這個地方檢測 ViewGroup 是否攔截事件
// DOWN 事件時,這個 mFirstTouchTarget == null,會判斷ViewGroup 是否攔截事件
// 情況一:ViewGroup 攔截事件,即onInterceptTouchEvent(ev) 方法返回 true,
// 會直接呼叫 ViewGroup.onTouchEvent(MotionEvent)方法自己處理事件,這個時候 mFirstTouchTarget == null;
// 當 MOVE/UP事件到來時,該 if 條件不滿足,ViewGroup.onInterceptTouchEvent(ev) 不會被呼叫,此時的 MOVE/UP事件直接傳遞到了 ViewGroup.onTouchEvent(MotionEvent)中
// 情況二:如果 ViewGroup 不攔截事件,事件順利傳遞給子 View ,並且事件被子 view 消費掉的話,
// mFirstTouchTarget 會被賦值並指向子元素,mFirstTouchTarget != null 條件才成立;後續的 MOVE/UP 事件會走「程式碼片段四」進行傳遞
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;
}
......
// A. 往下走 DOWN 事件,會有兩種情況
// 情況A1:onInterceptTouchEvent預設返回 false,不攔截;不取消,即預設的 if 條件滿足,
// for 迴圈遍歷所有的子 view,事件繼續往下傳遞;注意:子 View 是否消費 DOWN 事件,
// 會影響到後續的 MOVE UP等事件的傳遞情況(即會影響到情況 B)
// 情況A2:如果我們重寫 ViewGroup#onInterceptTouchEvent(MotionEvent)並返回 true,
// 那麼這個 if 不滿足,就會走下面 ViewGroup 自己的 OnTouchEvent()方法 (即程式碼片段三)
// B. 後續的MOVE/UP等事件 走到這裡時,if條件不滿足,又分兩種情況:
// 情況B1:如果子 View 消費了 DOWN 事件,那麼會直接走到 「程式碼片段四 」,進行事件的傳遞
// 情況B2:如果子 View 不消費 DOWN 事件,那麼事件就會交給父View 處理,會走「程式碼片段三 」
TouchTarget newTouchTarget = null; // TouchTarget在這裡宣告
// 先看不攔截事件的情況
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 除了 DOWN 事件,後續的 MOVE 和 UP 事件進不來
final int childrenCount = mChildrenCount;
// DOWN事件走到這裡,如果同時 ViewGroup 有子 View,就繼續往下走了
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
// 遍歷所有的子 View,
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
......
// 剔除中間幾行不重要的程式碼,刪掉的程式碼直白的說下:如果當前的某個 view 處於獲取到焦點狀態,
// 那麼優先把這個事件傳遞給它,如果該 View 不消費,不處理的話,事件就繼續正常分發下去
// 重點來了:這個地方分為2種情況,取決於dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)返回值
// 情況1:返回 true,表示子 View 消費了事件(先別管是怎麼消費的)
// 情況2:返回 false,表示子 View 不消費事件,if 條件不滿足,這個時候mFirstTouchTarget == null,就不會被賦值
// 這裡先分析 假設消費 true 的情況,那麼 if 滿足,正常進入,這時候 child!=null
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //呼叫一 該 if 條件具體原始碼及分析 在下面
......
// 下面這句程式碼執行完 mFirstTouchTarget != null
// 同時 newTouchTarget != null,跳出迴圈
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
......
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// 程式碼片段三
// 這裡有3種情況,都會走到這裡
// 1. ViewGroup 主動攔截事件
// 2. ViewGroup 沒有子 View
// 3. ViewGroup 有子 View,但是都不消費事件 dispatchTransformedTouchEvent() 返回了 false
// 注意這個時候,引數三 child傳的為 null -> 會呼叫 ViewGroup.onTouchEvent(MotionEvent)
// 呼叫二
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// intercepted = false 的情況會 即 ViewGroup#onInterceptTouchEvent(ev) 返回了 false,不攔截事件,同時事件正常的傳遞到了目標子 View
// 程式碼片段四
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;
// 子 View 消費了 DOWN 事件,那麼後續的 MOVE/UP 等事件,就是在這裡傳遞給對應的子 View 的
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;
}
}
}
......
return handled;
}
預設第一次,就是DOWN事件傳遞到程式碼片段二的時候,if 滿足條件,同時mFirstTouchTarget == null;是否攔截事件,這時候取決於 FLAG_DISALLOW_INTERCEPT,預設disallowIntercept = false,直接會走 ViewGroup#onInterceptTouchEvent(MotionEvent),這個值是子 View通過呼叫 requestDisallowInterceptTouchEvent(true) 來改變的,子 View 一旦呼叫設定後,ViewGroup將無法攔截除了 DOWN 以外的其它事件。
之所以說除了 DOWN 以外的其它事件,是因為每次當 DOWN 事件傳遞到 ViewGroup 的 dispatchTouchEvent()時候,會呼叫「程式碼片段一」 resetTouchState();
標記位 FLAG_DISALLOW_INTERCEPT會被重置,這將導致子 View 中設定的這個標記無效;
所以,當 DOWN 事件傳遞到 ViewGroup 時,ViewGroup 總是會呼叫自己的 onInterceptTouchEvent() 來詢問是否要攔截事件;
/** 常量的宣告 轉為二進位制位:1000 0000 0000 0000
* When set, this ViewGroup should not intercept touch events.
* {@hide}
*/
protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000;
// 子 View 呼叫,從而影響到父 View 是否能攔截事件
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
......
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// Pass it up to our parent
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
# ViewGroup#onInterceptTouchEvent(MotionEvent)
/**
* 可以實現該方法攔截所有的觸控事件,
* 返回 true,就會呼叫自己的onTouchEvent()
* 返回 false,有可能會呼叫子 View 的 onTouchEvent()
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
/**
* View 的事件分發:把MotionEvent 傳遞給相關的 childview,如果傳遞過來的 child == null,那麼該 ViewGroup 的onTouchEvent(MotionEvent)方法就會被呼叫
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// DOWN 事件傳遞過來,if 不成立
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;
}
......
// 重點 這裡有2種情況
// 情況1:當前 ViewGroup 是有子 View 的情況,此時傳過來的 child != null,由此事件就傳遞到了具體的 childView 了,事件消耗與否取決於子 View
// 情況2:ViewGroup 攔截了事件,此時傳遞過來的 child == null
if (child == null) {
// 呼叫 super 的方法,最終會呼叫到 ViewGroup#onTouchEvent(MotionEvent)裡
handled = super.dispatchTouchEvent(transformedEvent);
} else {
......
// 事件繼續向下傳遞,由此事件已經從父 View 傳遞給了下一層 View,接下來的傳遞過程與頂級父 View 的傳遞過程是一致的,如此迴圈,完成整個事件的分發
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
接下來繼續往下分析 View.dispatchTouchEvent(MotionEvent)
View 的事件分發過程
/**
* View#dispatchTouchEvent(MotionEvent)
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
* @return True 表示當前 view 消費掉此事件
*/
public boolean dispatchTouchEvent(MotionEvent event) {
......
boolean result = false;
final int actionMasked = event.getActionMasked();
if (onFilterTouchEventForSecurity(event)) {
// 預設情況下都是會進來的;除非當前的 Window 被另一個可見的 Window 部分或者全部遮擋掉了,就會丟棄掉該事件
// 重中之重的地方來了;首先 ListenerInfo 是 View.java 裡邊一個靜態類,裡邊封裝了各種監聽事件,比如 焦點變化的監聽,滑動狀態改變的監聽 點選、長按事件、觸控事件的監聽等等
//這裡我們如果設定了 觸控事件,那麼就會回撥到觸控事件的 onTouch()方法中,根據返回值決定了 result 的值
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 情況一:如果設定了觸控事件,並且li.mOnTouchListener.onTouch(this, event)返回 true,表示事件被消費掉了,不再往下傳遞;那麼 View.onTouchEvent(MotionEvent)方法就不會執行
// 情況二:如果設定了觸控事件,但是返回了 false,或者使用者就沒有設定觸控事件,那麼最終就會回撥到 onTouchEvent(event)方法
// 由此我們可以看到設定的觸控事件的優先順序會高於OnTouchEvent(),給使用者提供一個外部處理觸控事件的回撥,可以提前做一些事情
if (!result && onTouchEvent(event)) {
result = true;
}
}
......
return result;
}
說明:
如果是從上邊 「呼叫二」super.dispatchTouchEvent(transformedEvent) 呼叫的,那麼就會呼叫 ViewGroup.onTouchEvent(MotionEvent),事件就交給 ViewGroup 自己處理;
如果是具體的 View 呼叫的「呼叫一」,那麼事件就相當於傳遞到末端了,是否消費事件取決於 onTouch(View, MotionEvent) 和 onTouchEvent(MotionEvent)這兩個方法的返回值;
如果最終的 result = true,那麼就表示事件被子 View 消費掉了,事件不再往上傳遞(子 View 都不處理,那麼事件就會一層一層的傳遞給父 View ,父 View 的 OnTouchEvent(MotionEvent) 就會被呼叫)
View 的點選事件處理
我們給 View 設定的點選事件到目前為止還沒有看到,實際上,點選事件的回撥時機是在 View#onTouchEvent(MotionEvent) 的 case MotionEvent.ACTION_UP:
手指抬起中進行回撥的,簡單的貼下程式碼
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
// 值得一提的是,只要設定了點選事件或者長按事件,就會改變 viewFlags 的值,clickable = true
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
......
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
......
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
// 點選事件
if (!post(mPerformClick)) {
performClick();
}
}
......
break;
......
}
// 只要是能夠進入 if 條件,預設都是返回 true 的,即消費掉這個事件
return true;
}
總結
分析了原始碼可以得到的結論
ViewGroup 預設不攔截任何事件,
ViewGroup#onInterceptTouchEvent(MotionEvent)
方法預設返回 falseViewGroup#onInterceptTouchEvent(MotionEvent)
是用來攔截某個事件的,如果當前 ViewGroup 攔截了某個事件(這個事件可能是 DOWN 或者其它事件),那麼同一個事件序列中的事件再次傳遞過來時,該方法不會被再次呼叫,而是直接呼叫了ViewGroup#onTouchEvent(MotionEvent)
方法事件傳遞到某個 View,如果它不消耗 DOWN 事件( onTouchEvent(MotionEvent) 返回了 false),那麼後續的MOVE UP 等事件都不會再傳遞給它,並且事件將重新交由它的父 View 去處理,即父 View 的 onTouchEvent(MotionEvent)會被呼叫。(mFirstTouchTarget == null,走「程式碼片段三」的情況)
View 沒有onInterceptTouchEvent(MotionEvent) 方法,一旦事件傳遞給它,那麼它的 onTouchEvent(MotionEvent) 就會被呼叫
onTouchEvent(MotionEvent) 返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前 View 無法再次接收到其它事件
如果給 View 設定了setOnTouchListener() 和 onTouchEvent(),要想兩者都可以執行,觸控事件 onTouch() 返回 false 即可
事件的傳遞過程文字描述
事件的傳遞順序是 Activity -> Window -> ViewGroup -> View,當一個點選事件產生後,就會按如下順序傳遞到 父 ViewGroup 的 dispatchTouchEvent(),如果 ViewGroup 攔截事件,那麼事件會直接傳遞到 ViewGroup 的 onTouchEvent() 方法中;如果父 ViewGroup 不攔截事件,那麼就會 for 迴圈遍歷子 View,進行事件的下發,如果事件往下傳遞的過程中有一個子 View 的 onTouchEvent() 返回 true消費掉事件,那麼事件到此為止;
考慮一種情況:所有子 View 都不處理事件,那麼這個事件就會傳遞到父 View 的 onTouchEvent(),如果父 View 也不消費事件,那麼這個事件就會一級一級往上一個父 View 傳遞,如果所有的 View 都不消費事件,那麼這個事件最終會傳遞到 Activity 的 onTouchEvent(MotionEvent),最終 Activity 預設也是把該事件丟棄掉的,但是原始碼裡邊給了我們一個思路,就是說如果事件是在 Window 的邊界外產生的,那麼我們就可以重寫 Activity.onTouchEvent(MotionEvent) 來處理
事件傳遞機制流程圖
最後貼一張圖 Android 事件分發的流程圖:
虛擬碼展示事件分發攔截和消費三者的關係
一段有意思的虛擬碼[虛擬碼來源於藝術探索一書]:
public boolean dispatchTouchEvent(MotionEvent event) {
boolean consume = false;
if (onInterceptTouchEvent(event)) {
consume = onTouchEvent(event);
} else {
consume = childView.dispatchTouchEvent(event);
}
return consume;
}
推薦閱讀:
手寫酷狗側滑選單效果
Jenkins 自動化構建 Android 專案圖文教程(一)
Jenkins 自動化構建 Android 專案圖文教程(二)
相關文章
- android事件分發機制詳解Android事件
- Android事件分發:從原始碼角度分析View事件分發機制Android事件原始碼View
- Android 事件分發機制原始碼解析-view層Android事件原始碼View
- Android從原始碼角度剖析View事件分發機制Android原始碼View事件
- 基於原始碼分析 Android View 事件分發機制原始碼AndroidView事件
- Android事件分發機制Android事件
- 事件分發機制(二):原始碼篇事件原始碼
- vscode原始碼分析【五】事件分發機制VSCode原始碼事件
- Android事件分發機制,你瞭解過嗎?Android事件
- Android--Handler機制及原始碼詳解Android原始碼
- Android 事件分發機制的理解Android事件
- Android的MotionEvent事件分發機制Android事件
- Android事件分發機制三:事件分發工作流程Android事件
- Android View 的事件體系 -- 事件分發機制AndroidView事件
- Android事件分發機制本質是樹的深度遍歷(圖+原始碼)Android事件原始碼
- 淺談Android 事件分發機制(二)Android事件
- Android事件分發機制簡單理解Android事件
- 從原始碼看 Android 事件分發原始碼Android事件
- Android事件分發原始碼歸納Android事件原始碼
- Android View 事件分發原始碼分析AndroidView事件原始碼
- 淺談Android中的事件分發機制Android事件
- 【Android基礎】講講Android的事件分發機制Android事件
- Android系統原始碼剖析-事件分發Android原始碼事件
- 面試:講講 Android 的事件分發機制面試Android事件
- NodeJS中的事件(EventEmitter) API詳解(附原始碼)NodeJS事件MITAPI原始碼
- Spring事件機制詳解Spring事件
- Redis 事件機制詳解Redis事件
- 「Android」分析EventBus原始碼擴充套件Weex事件機制Android原始碼套件事件
- Android自定義View之事件分發機制總結AndroidView事件
- Android原始碼角度分析事件分發消費(徹底整明白Android事件)Android原始碼事件
- React原始碼分析 – 事件機制React原始碼事件
- Android Handler訊息機制原始碼解讀Android原始碼
- android觸控事件分發機制,曾困惑你我的地方Android事件
- Android事件分發機制:基礎篇:最全面、最易懂Android事件
- Android事件分發機制五:面試官你坐啊Android事件面試
- View事件分發機制分析View事件
- cocos EventDispatcher事件分發機制事件
- Vue.js原始碼——事件機制Vue.js原始碼事件