<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>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                # 事件分發機制原理 之前講解了很多與View繪圖相關的知識,你可以在 [安卓自定義View教程目錄](http://www.gcssloop.com/customview/CustomViewIndex) 中查看到這些文章,如果你理解了這些文章,那么至少2D繪圖部分不是難題了,大部分的需求都能滿足,但是關于View還有很多知識點,例如: `讓繪圖更加炫酷的Paint`,`讓View動起來的動畫`,`與用戶交互的觸控事件` 等一系列內容。**本次就帶大家簡單的了解一下與交互息息相關的東西-事件分發原理**。 > 本次魔法小火車的終點站是事件分發,請各位魔法師帶好裝備,準備登車啟程。 **注意:本文中所有源碼分析部分均基于 API23(Android 6.0) 版本,由于安卓系統源碼改變很多,可能與之前版本有所不同,但基本流程都是一致的。** ## 為什么要有事件分發機制? **安卓上面的View是樹形結構的,View可能會重疊在一起,當我們點擊的地方有多個View都可以響應的時候,這個點擊事件應該給誰呢?為了解決這一個問題,就有了事件分發機制。** 如下圖,View是一層一層嵌套的,當手指點擊 `View1` 的時候,下面的`ViewGroupA`、 `RootView` 等也是能夠響應的,為了確定到底應該是哪個View處理這次點擊事件,就需要事件分發機制來幫忙。 ![](http://ww1.sinaimg.cn/large/005Xtdi2jw1f87nsnluf5j308c0eamxg.jpg) ## View的結構: 我們的View是樹形結構的,在上一個問題中實例View的結構大致如下: **layout文件:** ```XML <com.gcssloop.touchevent.test.RootView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="300dp" android:background="#4E5268" android:layout_margin="20dp" tools:context="com.gcssloop.touchevent.MainActivity"> <com.gcssloop.touchevent.test.ViewGroupA android:background="#95C3FA" android:layout_width="200dp" android:layout_height="200dp"> <com.gcssloop.touchevent.test.View1 android:background="#BDDA66" android:layout_width="130dp" android:layout_height="130dp"/> </com.gcssloop.touchevent.test.ViewGroupA> <com.gcssloop.touchevent.test.View2 android:layout_alignParentRight="true" android:background="#BDDA66" android:layout_width="80dp" android:layout_height="80dp"/> </com.gcssloop.touchevent.test.RootView> ``` **View結構:** ![](http://ww4.sinaimg.cn/large/005Xtdi2jw1f87juodlepj308q09ut8v.jpg) **可以看到在上面的View結構中莫名多出來的兩個東西,`PhoneWindow` 和 `DecorView` ,這兩個我們并沒有在Layout文件中定義過,但是為什么會存在呢?** > 仔細觀察上面的 layout 文件,你會發現一個問題,我在 layout 文件中的最頂層 View(Group) 的大小并不是填滿父窗體的,留下了大量的空白區域,由于我們的手機屏幕不能透明,所以這些空白區域肯定要顯示一些東西,那么應該顯示什么呢? > > 有過安卓開發經驗的都知道,屏幕上沒有View遮擋的部分會顯示主題的顏色。不僅如此,最上面的一個標題欄也沒有在 layout 文件中,這個標題欄又是顯示在哪里的呢? > > **你沒有猜錯,這個主題顏色和標題欄等內容就是顯示在`DecorView`中的。** **現在知道 `DecorView` 是干什么的了,那么`PhoneWindow` 又有什么作用?** > 要了解 PhoneWindow 是干啥的,首先要了解啥是 Window ,看官方說明: > > Abstract base class for a top-level window look and behavior policy. An instance of this class should be used as the top-level view added to the window manager. It provides standard UI policies such as a background, title area, default key processing, etc. > > > 簡單來說,Window是一個抽象類,是所有視圖的最頂層容器,視圖的外觀和行為都歸他管,不論是背景顯示,標題欄還是事件處理都是他管理的范疇,它其實就像是View界的太上皇(雖然能管的事情看似很多,但是沒實權,因為抽象類不能直接使用)。 > > 而 PhoneWindow 作為 Window 的唯一親兒子(唯一實現類),自然就是 View 界的皇帝了,PhoneWindow 的權利可是非常大大,不過對于我們來說用處并不大,因為皇帝平時都是躲在深宮里面的,雖然偶爾用特殊方法能見上一面,但想要完全指揮 PhoneWindow 為你工作是很困難的。 > > 而上面說的 DecorView 是 PhoneWindow 的一個內部類,其職位相當于小太監,就是跟在 PhoneWindow 身邊專業為 PhoneWindow 服務的,除了自己要干活之外,也負責消息的傳遞,PhoneWindow 的指示通過 DecorView 傳遞給下面的 View,而下面 View 的信息也通過 DecorView 回傳給 PhoneWindow。 ## 事件分發、攔截與消費 下表省略了 PhoneWidow 和 DecorView。 > `√` 表示有該方法。 > > `X` 表示沒有該方法。 | 類型 | 相關方法 | Activity | ViewGroup | View | | :--: | :-------------------: | :------: | :-------: | :--: | | 事件分發 | dispatchTouchEvent | √ | √ | √ | | 事件攔截 | onInterceptTouchEvent | X | √ | X | | 事件消費 | onTouchEvent | √ | √ | √ | 這個三個方法均有一個 boolean(布爾) 類型的返回值,通過返回 true 和 false 來控制事件傳遞的流程。 PS: 從上表可以看到 Activity 和 View 都是沒有事件攔截的,這是因為: > Activity 作為原始的事件分發者,如果 Activity 攔截了事件會導致整個屏幕都無法響應事件,這肯定不是我們想要的效果。 > > View最為事件傳遞的最末端,要么消費掉事件,要么不處理進行回傳,根本沒必要進行事件攔截。 ## 事件分發流程 前面我們了解到了我們的View是樹形結構的,基于這樣的結構,我們的事件可以進行有序的分發。 事件收集之后最先傳遞給 Activity, 然后依次向下傳遞,大致如下: ``` Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View ``` 這樣的事件分發機制邏輯非常清晰,可是,你是否注意到一個問題?如果最后分發到View,如果這個View也沒有處理事件怎么辦,就這樣讓事件浪費掉? 當然不會啦,如果沒有任何View消費掉事件,那么這個事件會按照反方向回傳,最終傳回給Activity,如果最后 Activity 也沒有處理,本次事件才會被拋棄: ``` Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View ``` **看到這里,我不禁微微一皺眉,這個東西咋看起來那么熟悉呢?再仔細一看,這不就是一個非常經典的[責任鏈模式](https://zh.wikipedia.org/wiki/%E8%B4%A3%E4%BB%BB%E9%93%BE%E6%A8%A1%E5%BC%8F)嗎,** 如果我能處理就攔截下來自己干,如果自己不能處理或者不確定就交給責任鏈中下一個對象。 **這種設計是非常精巧的,上層View既可以直接攔截該事件,自己處理,也可以先詢問(分發給)子View,如果子View需要就交給子View處理,如果子View不需要還能繼續交給上層View處理。既保證了事件的有序性,又非常的靈活。在我第一次將這個邏輯弄清楚的時候,看著這樣精妙的設計,簡直想歡呼慶賀一下。** 其實關于事件傳遞機制,吳小龍的 [Android事件傳遞機制分析](http://wuxiaolong.me/2015/12/19/MotionEvent/) 一文中的比喻非常有趣,本文也會借鑒一些其中的內容。 先確定幾個角色: > Activity - 公司大老板 > > RootView - 項目經理 > > ViewGroupA - 技術小組長 > > View1 - 碼農小王(公司里唯一的碼農) > > View2 - 跑龍套的路人甲,無視即可 **PS:由于 PhoneWindow 和 DecorView 我們無法直接操作,以下所有示例均省略了 PhoneWindow 和 DecorView。** ### 1.點擊 View1 區域但沒有任何 View 消費事件 ![](http://ww1.sinaimg.cn/large/005Xtdi2jw1f87nsnluf5j308c0eamxg.jpg) 當手指在 `View1` 區域點擊了一下之后,如果所有View都不消耗事件,你就能看到一個完整的事件分發流程,大致如下: > 紅色箭頭方向表示事件分發方向。 > > 綠色箭頭方向表示事件回傳方向。 ![](http://ww2.sinaimg.cn/large/005Xtdi2jw1f88i0q8uozj30nm0kqwhm.jpg) > **注意: 上圖顯示分發流程僅僅是一個示意流程,并不代表實際情況,如果按照實際情況繪制,會導致流程圖非常復雜和混亂,在糾結了好久之后做了一個艱難的決定,采用這樣一個簡化后的流程。** > > 上面的流程中存在部分不合理內容,請大家選擇性接受。 > > 1. 事件返回時 `dispatchTouchEvent` 直接指向了父View的 `onTouchEvent` 這一部分是不合理的,實際上它僅僅是給了父View的 `dispatchTouchEvent` 一個 false 返回值,父View根據返回值來調用自身的 `onTouchEvent`。 > 2. ViewGroup 是根據 `onInterceptTouchEvent` 的返回值來確定是調用子View的 `dispatchTouchEvent` 還是自身的 `onTouchEvent`, 并沒有將調用交給 `onInterceptTouchEvent`。 > 3. ViewGroup 的事件分發機制偽代碼如下,可以看出調用的順序。 > > ```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; > } > ``` **測試:** > **情景:老板: 我看公司最近業務不咋地,準備發展一下電商業務,下周之前做個淘寶出來試試怎么樣。** > > **事件順序,老板(MainActivity)要做淘寶,這個事件通過各個部門(ViewGroup)一層一層的往下傳,傳到最底層的時候,碼農小王(View1)發現做不了,于是消息又一層一層的回傳到老板那里。** > > 可以看到整個事件傳遞路線非常有序。從Activity開始,最后回傳給Activity結束(由于我們無法操作Phone Window和DecorView,所以沒有它們的信息)。 ``` MainActivity?[老板]: dispatchTouchEvent 經理,我準備發展一下電商業務,下周之前做一個淘寶出來. RootView?????[經理]: dispatchTouchEvent 呼叫技術部,老板要做淘寶,下周上線. RootView?????[經理]: onInterceptTouchEvent (老板可能瘋了,但又不是我做.) ViewGroupA???[組長]: dispatchTouchEvent 老板要做淘寶,下周上線? ViewGroupA???[組長]: onInterceptTouchEvent (看著不太靠譜,先問問小王怎么看) View1????????[碼農]: dispatchTouchEvent 做淘寶??? View1????????[碼農]: onTouchEvent 這個真心做不了啊. ViewGroupA???[組長]: onTouchEvent 小王說做不了. RootView?????[經理]: onTouchEvent 報告老板, 技術部說做不了. MainActivity?[老板]: onTouchEvent 這么簡單都做不了,你們都是干啥的(憤怒). ``` ### 2.點擊 View1 區域且事件被 View1 消費 如果事件被View1消費掉了則事件會回傳告訴上層View這個事件已經被我解決了,上層View就無需再響應了。 ![](http://ww2.sinaimg.cn/large/005Xtdi2jw1f88ll27wv9j30nm0kqtbo.jpg) > 注意:這張圖中的事件回傳路徑才是正確的路徑。 **測試:** > **情景:老板: 我覺得咱們這個app按鈕不好看,做的有光澤一點,要讓人有一種想點的欲望。** > > **事件順序,老板(MainActivity)要做改界面,這個事件通過各個部門(ViewGroup)一層一層的往下傳,傳到最底層的時候,碼農小王(View1)就在按鈕上添加了一道光(為啥是小王呢?因為公司沒有設計師)。** > > 可以看出,事件一旦被消費就意味著消息傳遞的結束,上層View知道了事件已經被消費掉,就不再處理了。 ``` MainActivity?[老板]: dispatchTouchEvent 把按鈕做的好看一點,要有光澤,給人一種點擊的欲望. RootView?????[經理]: dispatchTouchEvent 技術部,老板說按鈕不好看,要加一道光. RootView?????[經理]: onInterceptTouchEvent ViewGroupA???[組長]: dispatchTouchEvent 給按鈕加上一道光. ViewGroupA???[組長]: onInterceptTouchEvent View1????????[碼農]: dispatchTouchEvent 加一道光. View1????????[碼農]: onTouchEvent 做好了. ``` > 加一道光: > > ![](http://ww4.sinaimg.cn/large/005Xtdi2jw1f88oqj0o4jj304j03paa4.jpg) ### 3.點擊 View1 區域但事件被 ViewGroupA 攔截 > 上層的View有權攔截事件,不傳遞給下層View,例如 ListView 滑動的時候,就不會將事件傳遞給下層的子 View。 ![](http://ww4.sinaimg.cn/large/005Xtdi2jw1f88p3r45vfj30nm0kqn00.jpg) > 注意:可以看到,如果上層攔截了事件,下層View將接收不到事件信息。 **測試:** > **情景:老板: 報告一下項目進度。** > > **事件順序,老板(MainActivity)要知道項目進度,這個事件通過各個部門(ViewGroup)一層一層的往下傳,傳到技術組組長(ViewGroupA)的時候,組長(ViewGroupA)上報任務即可。無需告知碼農小王(View1)。** ``` MainActivity?[老板]: dispatchTouchEvent 現在項目做到什么程度了? RootView?????[經理]: dispatchTouchEvent 技術部,你們的app快做完了么? RootView?????[經理]: onInterceptTouchEvent ViewGroupA???[組長]: dispatchTouchEvent 項目進度? ViewGroupA???[組長]: onInterceptTouchEvent ViewGroupA???[組長]: onTouchEvent 正在測試,明天就測試完了 ``` ### 其他情況 事件分發機制設計到到情形非常多,這里就不一一列舉了,記住以下幾條原則就行了。 * 1.如果事件被消費,就意味著事件信息傳遞終止。 * 2.如果事件一直沒有被消費,最后會傳給Activity,如果Activity也不需要就被拋棄。 * 3.判斷事件是否被消費是根據返回值,而不是根據你是否使用了事件。 #### [文中測試用的源碼下載](https://raw.githubusercontent.com/GcsSloop/AndroidNote/master/CustomView/Demo/dispatchTouchEventDemo.zip) ## 總結 View的事件分發機制實際上就是一個非常經典的責任鏈模式,如果你了解責任鏈模式,那么事件分發對你來說并不是什么難題,如果你不了解責任鏈模式,剛好借此機會學習一下啦。 > **責任鏈模式:** > > 當有多個對象均可以處理同一請求的時候,將這些對象串聯成一條鏈,并沿著這條鏈傳遞改請求,直到有對象處理它為止。 Android 中事件分發機制原理雖然非常簡單,但由于實際場景非常復雜,一旦具體到某個場景中變得很麻煩,而本文僅僅是帶你簡單的了解一下事件分發機制,更詳細的內容和具體的一些特殊情形處理會在后續文章中進行講解。 由于個人水平有限,文章中可能會出現錯誤,如果你覺得哪一部分有錯誤,或者發現了錯別字等內容,歡迎在評論區告訴我,另外,據說關注[作者微博](http://weibo.com/GcsSloop)不僅能第一時間收到新文章消息,還能變帥哦。 ## 參考資料 [Activity](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/Activity.java)<br/> [PhoneWindow](https://android.googlesource.com/platform/frameworks/base/+/696cba573e651b0e4f18a4718627c8ccecb3bda0/policy/src/com/android/internal/policy/impl/PhoneWindow.java)<br/> [ViewGroup](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/ViewGroup.java)<br/> [View](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/view/View.java)<br/> [Android事件傳遞機制分析](http://wuxiaolong.me/2015/12/19/MotionEvent/)<br/> [Android ViewGroup/View 事件分發機制詳解](http://anany.me/2015/11/08/touchevent/)<br/> [ Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)](http://blog.csdn.net/guolin_blog/article/details/9097463)<br/> [ Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)](http://blog.csdn.net/sinyu890807/article/details/9153747)<br/> [更簡單的學習Android事件分發](http://www.idtkm.com/customview/customview11/)<br/> 《安卓開發藝術探索》 ## 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>
                  <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>

                              哎呀哎呀视频在线观看