<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                版權信息 見下文 --- # 事件分發機制詳解 在上一篇文章 [事件分發機制原理][dispatch-touchevent-theory] 中簡要分析了一下事件分發機制的原理,原理是十分簡單的,一句話就能總結:**責任鏈模式,事件層層傳遞,直到被消費。** 雖然原理簡單,但是隨著 Android 不斷的發展,實際運用場景也越來越復雜,所以想要徹底玩轉事件分發機制還需要一定技巧,本篇事件分發機制詳解將帶大家了解 ... > **你以為我接下來要講源碼?** > 我就不按套路,所有的源碼都是為了適應具體的應用場景而寫的,只要能夠理解運用場景,理解源碼也就十分簡單了。所以本篇的核心問題是:**正確理解在實際場景中事件分發機制的作用。** 會涉及到源碼,但不是主角。 **注意:本文中所有源碼分析部分均基于 API23(Android 6.0) 版本,由于安卓系統源碼改變很多,可能與之前版本有所不同,但基本流程都是一致的。** ## 常見事件 既然是事件分發,總要有事件才能分發吧,所以我們先了解一下常見的幾種事件。 根據面向對象思想,事件被封裝成 MotionEvent 對象,由于本篇重點不在于此,所以只會涉及到幾個與手指觸摸相關的常見事件: | 事件 | 簡介 | | ------------- | ------------------------- | | ACTION_DOWN | 手指 **初次接觸到屏幕** 時觸發。 | | ACTION_MOVE | 手指 **在屏幕上滑動** 時觸發,會會多次觸發。 | | ACTION_UP | 手指 **離開屏幕** 時觸發。 | | ACTION_CANCEL | 事件 **被上層攔截** 時觸發。 | 對于單指觸控來說,一次簡單的交互流程是這樣的: **手指落下(ACTION_DOWN) -> 移動(ACTION_MOVE) -> 離開(ACTION_UP)** > * 本次事例中 ACTION_MOVE 有多次觸發。 > * 如果僅僅是單擊(手指按下再抬起),不會觸發 ACTION_MOVE。 ![](http://ww4.sinaimg.cn/large/005Xtdi2jw1f8oz1704ylg30bo0jqgmx.gif) ## 事件分發、攔截與消費 關于這一部分內容,上一篇文章 [事件分發機制原理][dispatch-touchevent-theory] 已經將流程整理的比較清楚了,本文會深入細節來研究這些內容。之所以分開講,是為了防止大家被細節所迷惑而忽略了整體邏輯。 > `√` 表示有該方法。 > > `X` 表示沒有該方法。 | 類型 | 相關方法 | ViewGroup | View | | :--: | :-------------------: | :-------: | :--: | | 事件分發 | dispatchTouchEvent | √ | √ | | 事件攔截 | onInterceptTouchEvent | √ | X | | 事件消費 | onTouchEvent | √ | √ | ### View 相關 `dispatchTouchEvent` 是事件分發機制中的核心,所有的事件調度都歸它管。不過我細看表格, ViewGroup 有 dispatchTouchEvent 也就算了,畢竟人家有一堆 ChildView 需要管理,但為啥 View 也有?這就引出了我們的第一個疑問。 #### Q: 為什么 View 會有 dispatchTouchEvent ? A: 我們知道 View 可以注冊很多事件監聽器,例如:單擊事件(onClick)、長按事件(onLongClick)、觸摸事件(onTouch),并且View自身也有 onTouchEvent 方法,那么問題來了,這么多與事件相關的方法應該由誰管理?毋庸置疑就是 `dispatchTouchEvent`,所以 View 也會有事件分發。 相信看到這里很多小伙伴會產生第二個疑問,View 有這么多事件監聽器,到底哪個先執行? #### Q: 與 View 事件相關的各個方法調用順序是怎樣的? A: **如果不去看源碼,想一下讓自己設計會怎樣?** * 單擊事件(onClickListener) 需要兩個兩個事件(ACTION_DOWN 和 ACTION_UP )才能觸發,如果先分配給onClick判斷,等它判斷完,用戶手指已經離開屏幕,黃花菜都涼了,定然造成 View 無法響應其他事件,應該最后調用。(最后) * 長按事件(onLongClickListener) 同理,也是需要長時間等待才能出結果,肯定不能排到前面,但因為不需要ACTION_UP,應該排在 onClick 前面。(onLongClickListener > onClickListener) * 觸摸事件(onTouchListener) 如果用戶注冊了觸摸事件,說明用戶要自己處理觸摸事件了,這個應該排在最前面。(最前) * View自身處理(onTouchEvent) 提供了一種默認的處理方式,如果用戶已經處理好了,也就不需要了,所以應該排在 onTouchListener 后面。(onTouchListener > onTouchEvent) **所以事件的調度順序應該是 `onTouchListener > onTouchEvent > onLongClickListener > onClickListener`**。 ![](http://ww2.sinaimg.cn/large/005Xtdi2jw1f8r8jg9mw5j308y07mglw.jpg) 下面我們來看一下實際測試結果: > 手指按下,不移動,稍等片刻再抬起。 ```shell [Listener?]: onTouchListener ACTION_DOWN [GcsView??]: onTouchEvent ACTION_DOWN [Listener?]: onLongClickListener [Listener?]: onTouchListener ACTION_UP [GcsView??]: onTouchEvent ACTION_UP [Listener?]: onClickListener ``` 可以看到,測試結果也支持我們猜測的結論,因為長按 onLongClickListener 不需要 ACTION_UP 所以會在 ACTION_DOWN 之后就觸發。 接下來就看一下源碼是怎么設計的(省略了大量無關代碼): ```java public boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; // result 為返回值,主要作用是告訴調用者事件是否已經被消費。 if (onFilterTouchEventForSecurity(event)) { ListenerInfo li = mListenerInfo; /** * 如果設置了OnTouchListener,并且當前 View 可點擊,就調用監聽器的 onTouch 方法, * 如果 onTouch 方法返回值為 true,就設置 result 為 true。 */ if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } /** * 如果 result 為 false,則調用自身的 onTouchEvent。 * 如果 onTouchEvent 返回值為 true,則設置 result 為 true。 */ if (!result && onTouchEvent(event)) { result = true; } } ... return result; } ``` > **如果覺得源碼還是太長,那么用偽代碼實現應當是這樣的(省略若干安全判斷),簡單粗暴:** > > ```java > public boolean dispatchTouchEvent(MotionEvent event) { > if (mOnTouchListener.onTouch(this, event)) { > return true; > } else if (onTouchEvent(event)) { > return true; > } > return false; > } > ``` 正當你沉迷在源碼的"精妙"邏輯的時候,你可能沒發現有兩個東西失蹤了,等回過神來,定睛一看,哎呦媽呀,**OnClick 和 OnLongClick 去哪里了?** 不要擔心,OnClick 和 OnLongClick 的具體調用位置在 **onTouchEvent** 中,看源碼(同樣省略大量無關代碼): ```java public boolean onTouchEvent(MotionEvent event) { ... final int action = event.getAction(); // 檢查各種 clickable if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: ... removeLongPressCallback(); // 移除長按 ... performClick(); // 檢查單擊 ... break; case MotionEvent.ACTION_DOWN: ... checkForLongClick(0); // 檢測長按 ... break; ... } return true; // ??表示事件被消費 } return false; } ``` > **注意了,第一個重點要出現了(敲黑板)!** > > ![](http://ww3.sinaimg.cn/large/005Xtdi2jw1f8p431ehi3j304w04wmx1.jpg) > > **注意上面代碼中存在一個 `return true;` 并且是只要 View 可點擊就返回 true,就表示事件被消費了。** > > 舉個栗子: I have a **RelativeLayout**,I have a **View**,Ugh,**RelativeLayout - View** > > ```xml > <RelativeLayout > android:background="#CCC" > android:id="@+id/layout" > android:onClick="myClick" > android:layout_width="200dp" > android:layout_height="200dp"> > <View > android:clickable="true" > android:layout_width="200dp" > android:layout_height="200dp" /> > </RelativeLayout> > ``` > > 現在你有了一個 **RelativeLayout - View** 你開開心心的為 RelativeLayout 設置了一個點擊事件`myClick`,然而你會發現不論怎么點都不會接收到信息,仔細一看,發現內部的 View 有一個屬性 `android:clickable="true"` 正是這個看似不起眼的屬性把事件給消費掉了,由此我們可以得出如下結論: > **1. 不論 View 自身是否注冊點擊事件,只要 View 是可點擊的就會消費事件。** > **2. 事件是否被消費由返回值決定,true 表示消費,false 表示不消費,與是否使用了事件無關。** 關于 View 的事件分發先說這么多,下面我們來看一下 ViewGroup 的事件分發。 ### ViewGroup 相關 **ViewGroup(通常是各種Layout) 的事件分發相對來說就要麻煩一些,因為 ViewGroup 不僅要考慮自身,還要考慮各種 ChildView,一旦處理不好就容易引起各種事件沖突,正所謂養兒方知父母難啊。** #### VIewGroup 的事件分發流程又是如何的呢? 上一篇文章 [事件分發機制原理][dispatch-touchevent-theory] 中我們了解到事件是通過ViewGroup一層一層傳遞的,最終傳遞給 View,ViewGroup 要比它的 ChildView 先拿到事件,并且有權決定是否告訴要告訴 ChildView。在默認的情況下 ViewGroup 事件分發流程是這樣的。 * 1.判斷自身是否需要(詢問 onInterceptTouchEvent 是否攔截),如果需要,調用自己的 onTouchEvent。 * 2.自身不需要或者不確定,則詢問 ChildView ,一般來說是調用手指觸摸位置的 ChildView。 * 3.如果子 ChildView 不需要則調用自身的 onTouchEvent。 用偽代碼應該是這樣的: ```java public boolean dispatchTouchEvent(MotionEvent ev) { boolean result = false; // 默認狀態為沒有消費過 if (!onInterceptTouchEvent(ev)) { // 如果沒有攔截交給子View result = child.dispatchTouchEvent(ev); } if (!result) { // 如果事件沒有被消費,詢問自身onTouchEvent result = onTouchEvent(ev); } return result; } ``` **有人看到這里可能會有疑問,我看過源碼,ViewGroup 的 `dispatchTouchEvent` 可有二百多行呢,你弄這幾行就想忽悠我,別以為我讀書少。** 當然了,上述源碼是不完善的,還有很多問題是沒有解決的,例如: ##### 1. ViewGroup 中可能有多個 ChildView,如何判斷應該分配給哪一個? 這個很容易,就是把所有的 ChildView 遍歷一遍,如果手指觸摸的點在 ChildView 區域內就分發給這個View。 ##### 2. 當該點的 ChildView 有重疊時應該如何分配? 當 ChildView 重疊時,**一般會分配給顯示在最上面的 ChildView**。 如何判斷哪個是顯示在最上面的呢?后面加載的一般會覆蓋掉之前的,所以**顯示在最上面的是最后加載的**。 如下: ```xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.gcssloop.viewtest.MainActivity"> <View android:id="@+id/view1" android:background="#E4A07B" android:layout_width="200dp" android:layout_height="200dp"/> <View android:id="@+id/view2" android:layout_margin="100dp" android:background="#BDDA66" android:layout_width="200dp" android:layout_height="200dp"/> </RelativeLayout> ``` ![](http://ww2.sinaimg.cn/large/005Xtdi2jw1f8r0i301sgj308w0fmwez.jpg) 當手指點擊有重疊區域時,分如下幾種情況: 1. 只有 View1 可點擊時,事件將會分配給 View1,即使被 View2 遮擋,這一部分仍是 View1 的可點擊區域。 2. 只有 View2 可點擊時,事件將會分配給 View2。 3. View1 和 View2 均可點擊時,事件會分配給后加載的 View2,View2 將事件消費掉,View1接收不到事件。 **注意:** * 上面說的是可點擊,可點擊包括很多種情況,只要你給View注冊了 `onClickListener、onLongClickListener、OnContextClickListener` 其中的任何一個監聽器或者設置了 `android:clickable="true"` 就代表這個 View 是可點擊的。 另外,某些 View 默認就是可點擊的,例如,Button,CheckBox 等。 * 給 View 注冊 `OnTouchListener` 不會影響 View 的可點擊狀態。即使給 View 注冊 `OnTouchListener` ,**只要不返回 `true` 就不會消費事件**。 ##### 3. ViewGroup 和 ChildView 同時注冊了事件監聽器(onClick等),哪個會執行? 事件優先給 ChildView,會被 ChildView消費掉,ViewGroup 不會響應。 ##### 4. 所有事件都應該被同一 View 消費 在上面的例子中我們分析后可以了解到,同一次點擊事件只能被一個 View 消費,這是為什呢?主要是為了防止事件響應混亂,如果再一次完整的事件中分別將不同的事件分配給了不同的 View 容易造成事件響應混亂。 > ( View 中 onClick 事件需要同時接收到 ACTION_DOWN 和 ACTION_UP 才能觸發,如果分配給了不同的 View,那么 onClick 將無法被正確觸發)。 **安卓為了保證所有的事件都是被一個 View 消費的,對第一次的事件( ACTION_DOWN )進行了特殊判斷,View 只有消費了 ACTION_DOWN 事件,才能接收到后續的事件(可點擊控件會默認消費所有事件),并且會將后續所有事件傳遞過來,不會再傳遞給其他 View,除非上層 View 進行了攔截。** **如果上層 View 攔截了當前正在處理的事件,會收到一個 ACTION_CANCEL,表示當前事件已經結束,后續事件不會再傳遞過來。** **源碼:** > 其實如果能夠理解上面的內容,不看源碼也能非常順利的使用事件分發,但源碼中能挖掘出更多的內容。 ```java public boolean dispatchTouchEvent(MotionEvent ev) { // 調試用 if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // 判斷事件是否是針對可訪問的焦點視圖(很晚才添加的內容,個人猜測和屏幕輔助相關,方便盲人等使用設備) if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // 處理第一次ACTION_DOWN. if (actionMasked == MotionEvent.ACTION_DOWN) { // 清除之前所有的狀態 cancelAndClearTouchTargets(ev); resetTouchState(); } // 檢查是否需要攔截. 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); // 恢復操作,防止被更改 } else { intercepted = false; } } else { // 沒有目標來處理該事件,而且也不是一個新的事件事件(ACTION_DOWN), 進行攔截。 intercepted = true; } // 判斷事件是否是針對可訪問的焦點視圖 if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // 檢查事件是否被取消(ACTION_CANCEL). final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; // 如果沒有取消也沒有被攔截 (進入事件分發) if (!canceled && !intercepted) { // 如果事件是針對可訪問性焦點視圖,我們將其提供給具有可訪問性焦點的視圖。 // 如果它不處理它,我們清除該標志并像往常一樣將事件分派給所有的 ChildView。 // 我們檢測并避免保持這種狀態,因為這些事非常罕見。 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // 清除此指針ID的早期觸摸目標,防止不同步。 removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); // 獲取觸摸位置坐標 final float y = ev.getY(actionIndex); // 查找可以接受事件的 ChildView final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; // ▼注意,從最后向前掃描 for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // 如果有一個視圖具有可訪問性焦點,我們希望它首先獲取事件, // 如果不處理,我們將執行正常的分派。 // 盡管這可能會分發兩次,但它能保證在給定的時間內更安全的執行。 if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } // 檢查View是否允許接受事件(即處于顯示狀態(VISIBLE)或者正在播放動畫) // 檢查觸摸位置是否在View區域內 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } // getTouchTarget 中判斷了 child 是否包含在 mFirstTouchTarget 中 // 如果有返回 target,如果沒有返回 null newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // ChildView 已經準備好接受在其區域內的事件。 newTouchTarget.pointerIdBits |= idBitsToAssign; break; // ??已經找到目標View,跳出循環 } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } if (newTouchTarget == null && mFirstTouchTarget != null) { // 沒有找到 ChildView 接收事件 newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } // 分發 TouchTarget if (mFirstTouchTarget == null) { // 沒有 TouchTarget,將當前 ViewGroup 當作普通的 View 處理。 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // 分發TouchTarget,如果我們已經分發過,則避免分配給新的目標。 // 如有必要,取消分發。 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; } } // 如果需要,更新指針的觸摸目標列表或取消。 if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled; } ``` ## 核心要點 1. **事件分發原理: 責任鏈模式,事件層層傳遞,直到被消費。** 2. **View 的 `dispatchTouchEvent` 主要用于調度自身的監聽器和 onTouchEvent。** 3. **View的事件的調度順序是 onTouchListener > onTouchEvent > onLongClickListener > onClickListener 。** 4. **不論 View 自身是否注冊點擊事件,只要 View 是可點擊的就會消費事件。** 5. **事件是否被消費由返回值決定,true 表示消費,false 表示不消費,與是否使用了事件無關。** 6. **ViewGroup 中可能有多個 ChildView 時,將事件分配給包含點擊位置的 ChildView。** 7. **ViewGroup 和 ChildView 同時注冊了事件監聽器(onClick等),由 ChildView 消費。** 8. **一次觸摸流程中產生事件應被同一 View 消費,全部接收或者全部拒絕。** 9. **只要接受 ACTION_DOWN 就意味著接受所有的事件,拒絕 ACTION_DOWN 則不會收到后續內容。** 10. **如果當前正在處理的事件被上層 View 攔截,會收到一個 ACTION_CANCEL,后續事件不會再傳遞過來**。 ## 總結 本文啰嗦了這么多內容,但真正需要注意的就是核心要點中的幾個概念,只要能正確理解這些概念,相信理解事件分發機制將再也不是難題。 > 最后,個人推薦閱讀源碼的方法,先嘗試用自己的角度去分析,建立概念,然后看源碼進行驗證、對比,如果發現自己建立的概念有問題,就嘗試修正自己的概念,這樣比較容易理解原作者的意圖,也不容易被眾多的代碼所迷惑。 > > 就像 ViewGroup 中的 dispatchTouchEvent 內容非常多,主要是為了應對實際的場景,里面有很多 安全判斷,處理多指觸控 等內容,這些如果不先建立概念就去看源碼很容易被這些細節問題所迷惑。 ## 參考資料 [View ](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java) [ViewGroup.java](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java) [Android Touch事件分發詳解](https://github.com/CharonChui/AndroidNote/blob/master/Android%E5%8A%A0%E5%BC%BA/Android%20Touch%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E8%AF%A6%E8%A7%A3.md) [基于源碼來了解Android的事件分發機制](http://minjie.tech/2016/09/03/%E5%9F%BA%E4%BA%8E%E6%BA%90%E7%A0%81%E6%9D%A5%E4%BA%86%E8%A7%A3Android%E7%9A%84%E4%BA%8B%E4%BB%B6%E5%88%86%E5%8F%91%E6%9C%BA%E5%88%B6/) ## About Me ### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a> <a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a> [dispatch-touchevent-theory]: http://www.gcssloop.com/customview/dispatch-touchevent-theory "事件分發機制原理-GcsSloop"
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看