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

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] # 基礎知識 ## Activity、PhoneWindow、DecorView ![](https://img.kancloud.cn/27/2a/272a20ae7493e435306de73c220ff3c6_281x418.png) ## 事件MotionEvent MotionEvent負責處理報告所有類型的設備輸入事件,如手指觸摸、鼠標、觸控筆、軌跡球等。在實際開發中我們主要關注手指觸摸事件即可。 事件|說明 ---|--- ACTION_DOWN|手指初次接觸到屏幕時觸發 ACTION_MOVE|手指在屏幕上滑動時觸發,可多次觸發 ACTION_UP|手指離開屏幕時觸發 ACTION_CANCEL|事件被上層攔截時觸發 ACTION_OUTSIDE|手指不在控件區域時觸發 ## Input系統 本段出自[http://gityuan.com/2015/09/19/android-touch/](http://gityuan.com/2015/09/19/android-touch/) 當手指觸摸到屏幕時,屏幕硬件一行行不斷地掃描每個像素點,獲取到觸摸事件后,從底層產生中斷上報。再通過native層調用Java層InputEventReceiver中的dispatchInputEvent方法。經過層層調用,交由Activity的dispatchTouchEvent方法來處理。 ![](https://img.kancloud.cn/89/44/89445a5d29b5bc8313ee33c566a145ba_529x749.png) # Activity 的事件分發 ## DecorView的分發 首先從上圖中DecorView的dispatchTouchEvent看起: ```java // DecorView.java public boolean dispatchTouchEvent(MotionEvent ev) { final Window.Callback cb = mWindow.getCallback(); return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); } ``` Activity在創建Window對象時,會為Window對象設置回調接口,也就是Activity自己,這樣Window在收到外界的狀態改變時,就會回調Activity的相應方法。 上面代碼中cb指Window.Callback,Activity實現了此接口,因此接下來調用Activity的dispatchTouchEvent: ```java // Activty.java public boolean dispatchTouchEvent(MotionEvent ev) { // 捕獲用戶正在和設備交互,為了更方便的管理通知欄的通知,暫不重要 if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } // 事件分發給 Window if (getWindow().superDispatchTouchEvent(ev)) { return true; } // 當 PhoneWindow 里的所有View都不消費事件時(多用于處理落在Window范圍外部的觸摸事件) return onTouchEvent(ev); } ``` 主要的代碼在Window的事件分發,一起來看看: ## PhoneWindow 的分發 先來看看 PhoneWindow 的分發: ```java // PhoneWindow.java public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); } ``` 可以看到,直接調用自身 DecorView 的 superDispatchTouchEvent 方法: ```java // DecorView.java public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); } ``` 而 DecorView 又直接調用了父類 ViewGroup 的 dispatchTouchEvent 方法。DecorView是一個FrameLayout,觸摸事件在這一步傳遞給了根 ViewGroup。 ## PhoneWindow不處理時,Activity 的處理 當 PhoneWindow 不消費事件,也就是當前 Activity 的所有 View 都不消費事件時,事件會回傳給 Activity,調用 onTouchEvent 方法: ```java public boolean onTouchEvent(MotionEvent event) { if (mWindow.shouldCloseOnTouch(this, event)) { finish(); return true; } return false; } ``` mWindow 為 PhoneWindow 對象,在 PhoneWindow 代碼中未找到 shouldCloseOnTouch 方法,來看看其父類 Window 中的該方法: ```java public boolean shouldCloseOnTouch(Context context, MotionEvent event) { if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event) && peekDecorView() != null) { return true; } return false; } ``` 當設置了 mCloseOnTouchOutside 為 true,并且為 MotionEvent.ACTION_DOWN 事件,手指在 Activity 界面以外,且當前 Activity 包含子 View 時,會返回 true,當前 Activity 會被 finish。 下面來仔細看看 ViewGroup 的事件分發。 # ViewGroup 的事件分發 ## 概述 **分發事件** 方法:dispatchTouchEvent() 結果:返回值為true代表事件被當前View消費了,為false代表沒被消費 1、由于Android視圖結構為樹狀結構,所以觸摸事件都是從rootView開始依次向下進行傳遞,稱之為事件分發。 2、在沒有攔截的情況下,事件最終將會分發給手指觸摸區域的子 View,子 View 重疊時分發給最上層的子 View **攔截事件** 方法:onInterceptTouchEvent() 1、事件在分發的過程中,可以被中途某個ViewGroup進行攔截,停止向下進行分發傳遞。 2、ViewGroup攔截后,直接調用其onTouchEvent方法看是否需要消費使用。 ![](https://img.kancloud.cn/1c/5f/1c5fda29d08c3337720fd95824fd94a9_387x454.png) **使用事件** 方法:onTouchEvent(MotionEvent event) 定義:拿到MotionEvent實例,可以獲取到事件內容并使用 結果:onTouchEvent返回值為true時代表消費了事件,上層View不會再接收事件回傳;返回值為false時代表沒消費事件,可能只是使用了事件的內容,但沒消費,事件會繼續進行回傳。 事件被消費,就意味著事件信息傳遞終止 1、如果事件沒有被攔截,會按視圖層次結構依次往下進行傳遞,直到傳遞到最底層的View,也就是最終命中的View。 2、命中的View拿到事件后,會決定是否消費,如果消費了就沒上層View什么事了。 3、如果命中的View沒有消費事件,事件會回傳,依次調用上層View的onTouchEvent方法,看上層View是否需要使用事件做些什么。 ![](https://img.kancloud.cn/06/28/0628cdfce78768557dd3af59e55bc594_394x580.png) ## ViewGroup 的事件分發機制偽代碼: 這一段偽代碼來源于 GcsSloop 大神的[文章](http://www.gcssloop.com/customview/dispatch-touchevent-source),很有嚼勁。理解了這段代碼基本對于 Android 事件分發機制就有了宏觀上的把握了。配合吳小龍同學的這篇 [Android 事件傳遞機制分析](http://wuxiaolong.me/2015/12/19/MotionEvent/)服用效果更佳! ```java // 返回值代表是否將事件消費掉 public boolean dispatchTouchEvent(MotionEvent event) { // 默認狀態為未消費過 boolean result = false; // 如果沒有攔截(ViewGroup一般不會進行攔截,可點擊的ViewGroup除外,下文源碼部分有分析) if (!onInterceptTouchEvent(event)) { // 則交給childView result = child.dispatchTouchEvent(event); } // 如果事件沒有被childView消費 if (!result) { // 則調用自身 onTouchEvent() result = onTouchEvent(event); } // 返回事件消費狀態 return result; } ``` ## ViewGroup 的事件分發機制源碼 ```java public boolean dispatchTouchEvent(MotionEvent ev) { //... final boolean intercepted; //這里首先對是否攔截此事件做了很多判斷 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { //... intercepted = onInterceptTouchEvent(ev); } else { intercepted = true; } //... // 如果不攔截才會往下走進行分發 if (!canceled && !intercepted) { final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final View[] children = mChildren; // 查找可以處理本次事件的childView for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); //... // 將事件交給childView if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //... } } if (mFirstTouchTarget == null) { // 此處會調用super.dispatchTouchEvent() handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //... } return handled; } ``` 其中交給childView處理的dispatchTransformedTouchEvent方法源碼如下: ```java private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; //... if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { //調用childView的dispatchTouchEvent方法 handled = child.dispatchTouchEvent(transformedEvent); } return handled; } ``` 最后來看看剛剛 onInterceptTouchEvent 方法的默認實現: ```java 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; } ``` 可以看到,會先檢查事件來源。當為鼠標事件,且當前為 ACTION_DOWN,且鼠標左鍵按下時,默認返回 true;大部分情況即非鼠標事件時,onInterceptTouchEvent 都是返回 false。 # View 的事件分發 在事件向下分發的過程中,如果中間沒有ViewGroup攔截事件,事件會一直傳遞到最底層的子View。調用View的dispatchTouchEvent方法。 ## 分發順序 View 可以注冊很多事件監聽器,如:單擊事件(onClick)、長按事件(onLongClick)、觸摸事件(onTouch),并且View自身也有 onTouchEvent 方法。這些方法由 dispatchTouchEvent() 方法進行分發。 事件的調度順序為:onTouchListener > onTouchEvent > onLongClickListener > onClickListener ## View 的事件分發機制偽代碼 ```java // 返回值代表是否將事件消費掉 public boolean dispatchTouchEvent(MotionEvent event) { if(mOnTouchListener.onTouch(this, event)) { return true; } else if (onTouchEvent(event)) { return true; } return false; } ``` onClickListener 和 onLongClickListener 在 onTouchEvent 中調用。 ## View 的事件分發機制源碼: ```java public boolean dispatchTouchEvent(MotionEvent event) { //... if (onFilterTouchEventForSecurity(event)) { ListenerInfo li = mListenerInfo; // 回調mOnTouchListener接口 if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } // 調用onTouchEvent方法(在里面會回調onLongClickListener、onClickListener) if (!result && onTouchEvent(event)) { result = true; } } //... return result; } public boolean onTouchEvent(MotionEvent event) { // 判斷View是否可點擊 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: //... // 執行單擊事件 performClick(); break; case MotionEvent.ACTION_DOWN: //... // 檢測長按,并執行長按事件 checkForLongClick(0, x, y); break; } // 只要是可點擊的,就會返回true,也就是當前View就會消費事件 return true; } return false; } ``` ## 點擊事件 View的onClick事件需要同時接收到ACTION_DOWN和ACTION_UP才能觸發,如果這兩個事件被分發給了不同的View,點擊事件就不會被觸發。 View只有消費了ACTION_DOWN事件,才能接收到后續的事件,并且后續事件傳遞來時,不會再傳遞給其他View,除非在途中被其他View攔截。如果后續事件確實被攔截,當前View會收到一個ACTION_CANCEL的事件,表示事件結束,不會有后續事件。 一次觸摸流程中產生的事件應被同一 View 消費,全部接收或者全部拒絕。 ## 注意要點 * 只要給 View 注冊了 onClickListener、onLongClickListener、OnContextClickListener 其中的任何一個監聽器或者設置了 android:clickable=”true” 就代表這個 View 是可點擊的。 * 可點擊的 View 就會消費事件,不可點擊的 View 不會消費事件 * ViewGroup 和 ChildView 同時注冊了點擊事件監聽器時,事件優先給 ChildView 消費 * 同一次點擊事件只能被一個 View 消費,防止事件混亂 # 總結 對于ViewGroup來說: * 用戶觸摸事件從rootView依次向下進行傳遞。在沒有攔截的情況下,事件會依次傳遞到最底部子View * 事件分發過程中,可被中途某個ViewGroup進行攔截,停止向下進行分發傳遞。ViewGroup攔截后直接調用其onTouchEvent看是否需要消費使用,如不消費事件往上進行回傳 * 如果事件沒被攔截順利傳遞給最底層的View,命中的View會決定是否消費,消費了就沒上層View的事情了 * 子View如果不消費事件,事件會進行回傳,每個層級的ViewGroup都可以拿到事件并決定是否消費 * 如果事件一直沒有被消費,最后會回傳給 Activity,如果 Activity 也不需要就被拋棄 對于子View來說: * 只要注冊了onClickListener、onLongClickListener、onContextClickListener或設置了clickable="true",就代表時可點擊的 * 可點擊的View就會消費事件,不可點擊的View不會消費事件 * 當ChildView重疊時,一般會分配給顯示在最上面的ChildView # 補充 關于 ViewGroup 對于觸摸事件的分發流程圖,可參考下面這幾幅圖,[來源](https://blog.csdn.net/xyz_lmn/article/details/12517911) 1、都不攔截且都不消費的情況 ![](https://img.kancloud.cn/a2/5c/a25cc0c7a0f9b0ab399d0aa2bc5af348_571x672.png) 2、View 消費了事件,不再進行回傳 ![](https://img.kancloud.cn/e0/06/e00648dce27442722a03c1414ee50771_685x562.png) 3、View 消費了 ACTION_DOWN ![](https://img.kancloud.cn/7c/8b/7c8bfac03d3ff75081ba338c65fc0e87_732x693.png) View 消費了 ACTION_DOWN,但后續的 ACTION_MOVE 和 ACTION_UP 被上層攔截了,會給它個ACTION_CANCEL ![](https://img.kancloud.cn/08/34/083446b32dfa4eef6f8c5b303e578275_715x628.png) 4、上層一開始就攔截了事件,此時子 View 對于事件無感 ![](https://img.kancloud.cn/d1/ff/d1ff3576cc05d6c3a32cbe079b36c3ac_619x628.png) # 參考文檔: [安卓自定義View進階-事件分發機制詳解](http://www.gcssloop.com/customview/dispatch-touchevent-source)
                  <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>

                              哎呀哎呀视频在线观看