前言html
轉載請註明,轉自【https://www.cnblogs.com/andy-songwei/p/11039252.html】謝謝!java
在上一篇文章【【朝花夕拾】Android自定義View篇之(五)Android事件分發機制(上)Touch三個重要方法的處理邏輯】【下文簡稱(五),請先閱讀完(五)再閱讀本文】,咱們經過示例和log來分析了Android的事件分發機制。這些,咱們只是看到了現象,若是要進一步瞭解事件分發機制,這是不夠的,咱們還須要透過現象看本質,去研究研究源碼。本文將從源碼(基於Android API-26)出發,去分析咱們上一篇文章中看到的現象,以及其它一些和事件相關的常見問題,如:事件是如何傳到View中的、requestDisallowInterceptTouchEvent爲何失效、view設置了focusable=「false」,爲何還能觸發點擊事件、Touch事件和Click事件誰先誰後等!android
本文的主要內容以下:安全
1、事件的前世此生app
前文中研究事件傳遞是從Activity的dispatchTouchEvent開始的,可是事件的起源確定不是Activity。由於觸摸事件是觸摸的硬件,因此很明顯事件必定是從底層傳過來的。可是,事件是如何傳遞到View的呢?這一節咱們簡單瞭解一下事件傳遞到Activity的通過。ide
首先,咱們在ViewInner類的dispatchTouchEvent方法中打印調用棧,看看這個方法的調用流程。函數
1 //=============ViewInner.java============ 2 @Override 3 public boolean dispatchTouchEvent(MotionEvent event) { 4 Log.i("songzheweiwang",Log.getStackTraceString(new Throwable())); 5 return super.dispatchTouchEvent(event); 6 }
點擊ViewInner區域,獲得以下log:
oop
1 java.lang.Throwable 2 at com.example.demos.customviewdemo.ViewInner.dispatchTouchEvent(ViewInner.java:24) 3 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 4 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 5 at com.example.demos.customviewdemo.ViewGroupMiddle.dispatchTouchEvent(ViewGroupMiddle.java:23) 6 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 7 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 8 at com.example.demos.customviewdemo.ViewGroupOuter.dispatchTouchEvent(ViewGroupOuter.java:23) 9 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 10 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 11 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 12 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 13 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 14 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 15 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 16 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 17 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 18 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 19 at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3043) 20 at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2727) 21 at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:526) 22 at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830) 23 at android.app.Activity.dispatchTouchEvent(Activity.java:3410) 24 at com.example.demos.customviewdemo.EventDemoActivity.dispatchTouchEvent(EventDemoActivity.java:37) 25 at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:68) 26 at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:475) 27 at android.view.View.dispatchPointerEvent(View.java:12768) 28 at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5455) 29 at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5258) 30 at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4766) 31 at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4819) 32 at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4785) 33 at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4934) 34 at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4793) 35 at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4991) 36 at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4766) 37 at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4819) 38 at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4785) 39 at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4793) 40 at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4766) 41 at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7490) 42 at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7448) 43 at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7409) 44 at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7593) 45 at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:199) 46 at android.os.MessageQueue.nativePollOnce(Native Method) 47 at android.os.MessageQueue.next(MessageQueue.java:326) 48 at android.os.Looper.loop(Looper.java:165) 49 at android.app.ActivityThread.main(ActivityThread.java:7477) 50 at java.lang.reflect.Method.invoke(Native Method) 51 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:500) 52 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:865)
實際上,Android輸入事件的源頭是位於/dev/input/下的設備節點,而輸入事件的終點是由WMS管理的某個窗口,最終由窗口中的View處理。最初的輸入事件爲內核生成的原始事件,而最終交付給窗口的則是KeyEvent(鍵盤)或MotionEvent(鼠標和觸摸屏)對象。上述log中(從下往上看),用三種顏色標註了3個階段:紅色部分表示底層活動,這裏咱們看到了很多熟悉的身影,ZygoteInit,ActivityThread,Native Method等;綠色部分第45行的InputEventReceiver.dispatchInputEvent()方法,事件經過該方法由Native層進入到Java層,並傳遞到Activity中,這裏咱們也看到了很多熟悉的身影,ViewRootImpl,DecorView等;從第24行開始,就是咱們前面熟悉的,從Acitivty開始調用dispatchTouchEvent一層層來分發事件。可能讀者看到從第20~9行會有疑惑,爲何中間還有這麼多過程?若是瞭解Android的View層次結構的話就會知道,在DecorView和開發者定義的佈局(如ViewGroupOuter)之間,隔着不少層ViewGroup,也須要一層層傳遞,這一點我在本系列第一篇【【朝花夕拾】Android自定義View篇之(一)View繪製流程】【後面簡稱(一)】的第二節作過講解,不明白的能夠先去看看。源碼分析
本文的重點是從Activity開始的事件分發,至於前面從底層到Activity的事件流程,我們這裏作必定的瞭解便可,有興趣的能夠自行研究。佈局
2、事件從Activity到View體系
在上一篇文章的代碼示例中,咱們的Boss——EventDemoActivity類中有以下代碼
1 //=============Boss:EventDemoActivity.java============ 2 @Override 3 public boolean dispatchTouchEvent(MotionEvent ev) { 4 Log.i("songzheweiwang", "[EventDemoActivity-->dispatchTouchEvent]ev=" + EventUtil.parseAction(ev.getAction())); 5 return super.dispatchTouchEvent(ev); 6 } 7 8 @Override 9 public boolean onTouchEvent(MotionEvent event) { 10 Log.i("songzheweiwang", "[EventDemoActivity-->onTouchEvent]event=" + EventUtil.parseAction(event.getAction())); 11 return super.onTouchEvent(event); 12 }
Activity是事件的起點,dispatchTouchEvent又是整個邏輯中最先分發事件的地方。可是Activity並非View體系中的一員,那它是怎樣把事件分發到View體系中的呢?追蹤super.dispatchTouchEvent方法,進入到Activity.java的方法中:
1 //==========================Activity.java======================= 2 /** 3 * Called to process touch screen events. You can override this to 4 * intercept all touch screen events before they are dispatched to the 5 * window. Be sure to call this implementation for touch screen events 6 * that should be handled normally. 7 * 8 * @param ev The touch screen event. 9 * 10 * @return boolean Return true if this event was consumed. 11 */ 12 public boolean dispatchTouchEvent(MotionEvent ev) { 13 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 14 onUserInteraction(); 15 } 16 if (getWindow().superDispatchTouchEvent(ev)) { 17 return true; 18 } 19 return onTouchEvent(ev); 20 }
如註釋中所說,在事件分發到window以前,能夠重寫這個方法來攔截全部的屏幕事件,若是事件被消費了,這個方法會返回true。這個方法我們只關注第15~18行。第15行中若是getWindow().superDispatchTouchEvent值爲true,表示後續的View體系把事件消費了,那麼執行第16行,直接返回true了,後面該Activity的onTouchEvent方法就再也不執行了。若是其值爲false,則表示事件沒有被消費,那該Activity就調用本身的onTouchEvent來消費該事件。這一點,在上一篇文章中多份log都體現了這個結論。固然咱們仍是須要繼續看看getWindow().superDispatchTouchEvent是如何實現的。
其主要代碼以下:
1 //=================Activity.java================= 2 ...... 3 private Window mWindow; 4 ...... 5 public Window getWindow() { 6 return mWindow; 7 } 8 ...... 9 10 //================Window.java================ 11 /** 12 * ...... 13 * <p>The only existing implementation of this abstract class is 14 * android.view.PhoneWindow, which you should instantiate when needing a 15 * Window. 16 */ 17 public abstract class Window { 18 ...... 19 public abstract boolean superDispatchTouchEvent(MotionEvent event); 20 ...... 21 }
superDispatchTouchEvent在Window.java中是個抽象方法,須要在實現類中來實現。如註釋所說,PhoneWindow是該抽象類惟一存在的實現類,因此咱們追蹤到PhoneWindow中。
1 //=============PhoneWindow.java========== 2 ...... 3 // This is the top-level view of the window, containing the window decor. 4 private DecorView mDecor; 5 ...... 6 @Override 7 public boolean superDispatchTouchEvent(MotionEvent event) { 8 return mDecor.superDispatchTouchEvent(event); 9 } 10 11 //===========DecorView.java========== 12 ...... 13 public boolean superDispatchTouchEvent(MotionEvent event) { 14 return super.dispatchTouchEvent(event); 15 } 16 .... 17 18 //========ViewGroup.java======= 19 @Override 20 public boolean dispatchTouchEvent(MotionEvent ev) { 21 ...... 22 }
在本系列第一篇文章【(一)】中,對DecorView作過講解,若是有看過這篇文章,那麼此時看到如上代碼,就不會陌生了。DecorView是整個View體系的根view,它是一個ViewGroup,因此事件流程就進入到ViewGroup中的dispatchTouchEvent中了。經過上述流程,Activity就經過dispatchTouchEvent將事件分發到View體系中來了。
若是View體系沒有消費掉事件,就會調用Activity本身的onTouchEvent方法,看看其源碼。
1 /** 2 * Called when a touch screen event was not handled by any of the views 3 * under it. This is most useful to process touch events that happen 4 * outside of your window bounds, where there is no view to receive it. 5 * 6 * @param event The touch screen event being processed. 7 * 8 * @return Return true if you have consumed the event, false if you haven't. 9 * The default implementation always returns false. 10 */ 11 public boolean onTouchEvent(MotionEvent event) { 12 if (mWindow.shouldCloseOnTouch(this, event)) { 13 finish(); 14 return true; 15 } 16 return false; 17 }
這裏註釋說得很是明確了:當它下面的全部view都沒有處理掉屏幕觸摸事件時,Activity中的這個方法會調用。當觸摸事件發生在window邊界之外的地方時,這個方法尤其有用,由於這些地方,沒有view來接收這個事件。【(五)】的第三點中有個模型圖,當觸摸白色的Boss區域時,下面三個區域中的view接收不到觸摸事件,因此最後只有Activity中的onTouchEvent響應了,【(五)】的第六節中log能夠明確這一點。若是您消費了這個事件,返回true;消費不了,則返回false,默認的實現,老是返回false。這一點好理解,Boss嘛,只安排底下的員工幹活,本身毫不會動手,就算他們幹不了,把事件返回到Boss這裏,本身也不會處理的。這裏能夠改動EventDemoActivity.java中重寫的onTouchEvent方法來驗證一下:
1 @Override 2 public boolean onTouchEvent(MotionEvent event) { 3 boolean b = super.onTouchEvent(event); 4 Log.i("songzheweiwang", "[EventDemoActivity-->onTouchEvent]event=" + EventUtil.parseAction(event.getAction())+";b="+b); 5 return b; 6 }
最終發現,打印的log中,b值老是false。
到這裏,Activity中的兩個重要方法就講完了,後面再來看看事件從Activity分發到View體系後,事件流程是如何在其中一層層分發和處理的。
3、葉子View的事件處理邏輯
控件分爲兩種:一種是容器類控件,即繼承自ViewGroup,如LinearLayout等;一種是葉子View,View的派生類,如TextView,Button等。與之對應,處理事件分發也分爲兩種情形:一種是ViewGroup處理事件分發;另外一種是View處理事件邏輯。ViewGroup會遞歸遍歷子View,若是是葉子View也會調用到View中的dispatchTouchEvent方法。因此,這裏我們先從葉子View處理事件開始分析。
一、葉子View處理事件常見的若干場景
(1)onTouch、onTouchEvent和onClick方法執行順序問題
咱們工做中常常須要爲控件設置監聽Touch事件和Click事件。這裏對【(五)】中的實例代碼作一點改動,爲ViewInner控件添加觸摸事件和點擊事件,以下所示:
1 //===========EventDemoActivity========== 2 ViewInner mViewInner = findViewById(R.id.viewInner); 3 mViewInner.setOnClickListener(new View.OnClickListener() { 4 @Override 5 public void onClick(View v) { 6 Log.i("songzheweiwang-2", "[EventDemoActivity-->onclick]"); 7 } 8 }); 9 mViewInner.setOnTouchListener(new View.OnTouchListener() { 10 @Override 11 public boolean onTouch(View v, MotionEvent event) { 12 Log.i("songzheweiwang-2", "[EventDemoActivity-->onTouch]event="+EventUtil.parseAction(event.getAction())); 13 return false; 14 } 15 });
點擊ViewInner控件後獲得以下log:
1 06-14 01:19:59.246 25518-25518/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onTouch]event=ACTION_DOWN 2 06-14 01:19:59.246 25518-25518/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_DOWN 3 06-14 01:19:59.277 25518-25518/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onTouch]event=ACTION_UP 4 06-14 01:19:59.277 25518-25518/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_UP 5 06-14 01:19:59.278 25518-25518/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onclick]
咱們發現,從執行順序上看,onTouch > onTouchEvent > onClick,而且onTouch方法執行了兩次,onClick在ACTION_UP以後才被執行。
(2)onTouch返回true的場景
另外,咱們發現,oTouch是一個boolean型方法,默認返回的是false。若是將其返回值該爲true,結果又怎樣呢?
1 06-14 01:59:00.487 25760-25760/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onTouch]event=ACTION_DOWN 2 06-14 01:59:00.492 25760-25760/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onTouch]event=ACTION_UP
打印log發現,onTouchEvent和onClick方法都沒有執行。
(3)控件設置clickable=「false」的場景
在【(五)】中咱們講過,若是控件的clickable屬性爲false,那麼它是沒法消費觸摸事件的。這裏加上Touch和Click事件後,情形如何呢?
1 06-14 02:15:50.279 26161-26161/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onTouch]event=ACTION_DOWN 2 06-14 02:15:50.279 26161-26161/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_DOWN 3 06-14 02:15:50.328 26161-26161/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onTouch]event=ACTION_UP 4 06-14 02:15:50.328 26161-26161/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_UP 5 06-14 02:15:50.330 26161-26161/com.example.demos I/songzheweiwang-2: [EventDemoActivity-->onclick]
這裏發現和clickable爲true時打印的log同樣,也就是說,此時clickable=「false」失效了,這是什麼緣由呢?
(4)控件設置setEnable(false)時的場景
默認狀況下,控件都是enable的,若是給控件執行setEnable(false),結果會怎樣呢?
1 06-14 02:19:56.369 26359-26359/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_DOWN 2 06-14 02:19:56.401 26359-26359/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_UP
發現onTouch和onClick都沒有執行。爲何?
上面4種場景是工做中常常會碰到的與事件相關的場景,下面咱們從源碼的角度來分析其緣由。
二、View.dispatchTouchEvent方法
View類中用於處理事件分發邏輯的兩個主要方法是dispatchTouchEvent和onTouchEvent,前者是事件分發到該控件時最早執行的方法,先分析這個方法。
1 //=========================View.java================== 2 /** 3 * Pass the touch screen motion event down to the target view, or this 4 * view if it is the target. 5 * ...... 6 * @return True if the event was handled by the view, false otherwise. 7 */ 8 public boolean dispatchTouchEvent(MotionEvent event) { 9 ...... 10 boolean result = false; 11 ...... 12 if (onFilterTouchEventForSecurity(event)) { 13 ...... 14 //noinspection SimplifiableIfStatement 15 ListenerInfo li = mListenerInfo; 16 if (li != null && li.mOnTouchListener != null 17 && (mViewFlags & ENABLED_MASK) == ENABLED 18 && li.mOnTouchListener.onTouch(this, event)) { 19 result = true; 20 } 21 if (!result && onTouchEvent(event)) { 22 result = true; 23 } 24 } 25 ...... 26 return result; 27 }
先看註釋:將屏幕動做事件傳遞給目標view,若是本身就是這個目標則將事件傳給本身。若是這個事件被該目標view處理了,會返回true,不然會返回false。當ViewInner被觸摸時,會執行到這裏。
第11行有個if語句判斷,它的做用是爲了安全判斷當前TouchEvent是否被過濾掉了,好比該控件不在最頂部,被其它控件遮住了等情形。若是被過濾了,該方法就會返回false,事件沒有被消費。咱們前面的幾種場景下,該函數返回的都是true。第16~18行中有多個判斷條件,咱們來看看這些條件:
1 //============View.java=========== 2 static class ListenerInfo { 3 ...... 4 private OnTouchListener mOnTouchListener; 5 ...... 6 } 7 8 /** 9 * Register a callback to be invoked when a touch event is sent to this view. 10 * @param l the touch listener to attach to this view 11 */ 12 public void setOnTouchListener(OnTouchListener l) { 13 getListenerInfo().mOnTouchListener = l; 14 }
前面在EventDemoActivity類中ViewInner調用了該方法,並設置了OnTouchListener,因此li.mOnTouchListener的值這裏不是null。(mViewFlags & ENABLED_MASK) == ENABLED表示該view是enable的,默認狀況下控件都是enable;li.mOnTouchListener.onTouch(this, event),就是咱們EventDemoActivity中ViewInner的onTouch方法的返回值,默認是返回falsle。因此會跳出該if判斷,進入到第21行。因爲前面的if條件爲false,因此這裏result的值仍是false,後面那個條件就開始執行onTouchEvent方法了。若是事件被處理了,onTouchEvent會返回true,返回的result值也就爲true了,這個事件就完美處理掉了;若是沒有處理掉,onTouchEvent就返回false,那返回值result天然就是false了,事件沒有被處理掉。因此此時,onTouchEvent方法都是執行了的。若是在onTouch方法中返回了true,那麼第16行的判斷條件就是true了,此時result被賦值爲true,那麼第21行的onTouchEvent方法就沒有機會執行了,這就是我們前面說的第(2)種場景「onTouch返回true的場景」了。這種情形下,dispatchTouchEvent返回true,事件也被處理掉了。這裏給一個結論:當控件是enable的時候,若是onTouch事件返回true,onTouchEvent和onClick方法均不會執行,且事件仍會被消費。下面,咱們再看看onTouchEvent方法的源碼。
三、View.onTouchEvent方法
方法很長,這裏選取比較關鍵的代碼進行分析。
1 /** 2 * Implement this method to handle touch screen motion events. 3 * ...... 4 * @return True if the event was handled, false otherwise. 5 */ 6 public boolean onTouchEvent(MotionEvent event) { 7 ...... 8 final int action = event.getAction(); 9 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE 10 || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) 11 || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE; 12 if ((viewFlags & ENABLED_MASK) == DISABLED) { 13 if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { 14 setPressed(false); 15 } 16 mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN; 17 // A disabled view that is clickable still consumes the touch 18 // events, it just doesn't respond to them. 19 return clickable; 20 } 21 ...... 22 if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) { 23 switch (action) { 24 case MotionEvent.ACTION_UP: 25 ...... 26 // Use a Runnable and post this rather than calling 27 // performClick directly. This lets other visual state 28 // of the view update before click actions start. 29 if (mPerformClick == null) { 30 mPerformClick = new PerformClick(); 31 } 32 if (!post(mPerformClick)) { 33 performClick(); 34 } 35 ...... 36 break; 37 case MotionEvent.ACTION_DOWN: 38 ...... 39 break; 40 case MotionEvent.ACTION_CANCEL: 41 ...... 42 break; 43 case MotionEvent.ACTION_MOVE: 44 ...... 45 break; 46 } 47 return true; 48 } 49 return false; 50 }
看看註釋:實現該方法來處理屏幕觸摸事件,若是處理了就返回true,不然返回false。這裏須要對比一下dispatchTouchEvent的註釋,它的關鍵字是「pass」,而onTouchEvent的關鍵字是「handle」,這裏能夠區別二者的做用。
第9行中的clickable值,前說過,有的控件默認是true,有的則是false。控件的flag爲能夠通常點擊和長按,都表示是可點擊的。第12行中,若是控件爲disable的,即調用了setEnable(false)時,這裏就return了,該方法後面的流程就結束了。若是該控件是可點擊的,即clickable的值爲true,那onTouchEvent返回的就是true,事件仍然被消費了;反之,控件不可點擊,那返回的就是false,事件沒有被消費,傳遞到上一級的onTouchEvent方法中,這一點咱們在【(五)】中有講過。第1七、18行的註釋也明確說明了:一個能夠點擊的disable的控件,仍然能夠消費觸摸事件,只不過它不能迴應這些控件。
第22行的第二個條件,tooltip的說明以下:
1 //=========================View.java==================== 2 1 /** 3 2 * <p>Indicates this view can display a tooltip on hover or long press.</p> 4 3 * {@hide} 5 4 */ 6 5 static final int TOOLTIP = 0x40000000;
這是一種比較特殊的用途,這裏不繼續深究,咱們平時的使用以及這裏demo中沒有使用該功能。因此我們這裏只考慮第一個條件。
可以走到第22行,說明該控件是enable的。若是該控件是不可點擊的,那麼這個if語句就跳過去了,返回false,此時前面的dispatchTouchEvent方法返回的也必定是false了,說明該控件沒有消費掉事件。相反,若是是可點擊的,能夠看到,後面必定是會返回true的,那麼事件就能被消費了。到這裏,結合第19行和dispatchTouchEvent方法,咱們能夠獲得一個結論:若是控件是clickable且沒有被過濾的,那麼這個控件必定是能夠消費掉事件的。在ACTION_UP的時候,會走到第29行。這裏咱們須要關注一下PerformClick類,其源碼以下:
1 private final class PerformClick implements Runnable { 2 @Override 3 public void run() { 4 performClick(); 5 } 6 }
而第32行的post方法,本質上就是一個Handler發的post,因此第3二、33行是必定要執行performClick()方法的。咱們着重看一看這個方法:
1 //====================View.java================ 2 /** 3 * Call this view's OnClickListener, if it is defined. Performs all normal 4 * actions associated with clicking: reporting accessibility event, playing 5 * a sound, etc. 6 * 7 * @return True there was an assigned OnClickListener that was called, false 8 * otherwise is returned. 9 */ 10 public boolean performClick() { 11 final boolean result; 12 final ListenerInfo li = mListenerInfo; 13 if (li != null && li.mOnClickListener != null) { 14 playSoundEffect(SoundEffectConstants.CLICK); 15 li.mOnClickListener.onClick(this); 16 result = true; 17 } else { 18 result = false; 19 } 20 ...... 21 return result; 22 }
這個方法就是用來執行點擊相關的工做的,第13行播放點擊音效,第14行響應點擊事件等。
1 //===============View.java============= 2 static class ListenerInfo { 3 ...... 4 public OnClickListener mOnClickListener; 5 ...... 6 } 7 ...... 8 /** 9 * Register a callback to be invoked when this view is clicked. If this view is not 10 * clickable, it becomes clickable. 11 * 12 * @param l The callback that will run 13 * 14 * @see #setClickable(boolean) 15 */ 16 public void setOnClickListener(@Nullable OnClickListener l) { 17 if (!isClickable()) { 18 setClickable(true); 19 } 20 getListenerInfo().mOnClickListener = l; 21 }
咱們在EventDemoActivity中ViewInner經過該方法設置了OnClickListener,因此EventDemoActivity中的onClick方法在這裏就被調用了,此時點擊事件就產生了。咱們能夠看到,點擊事件是發生在onTouchEvent方法的ACTION_UP事件中的。這裏結合前面講的onTouch方法,就能夠解釋場景(1)「onTouch、onTouchEvent和onClick方法執行順序問題」了:onTouch > onTouchEvent > onClick,而且要在ACTION_UP後纔會有onClick。在setOnClickListener方法中第1七、18行,咱們還能夠看到,若是這個控件自己不是clickable的,也會先讓它變成clickable的。因此,只要控件調用了setOnClickListener方法,那麼以前設置的clickable=「false」就會失效。這裏就解釋了場景(3)「控件設置clickable=「false」的場景」。此時,咱們返回去看看onTouchEvent方法的第19行,就會發現,即便控件是enable的,因爲咱們設置了點擊事件監聽,因此這裏是返回true的,事件仍然被消費了,再結合前面dispatchTouchEvent的第17行,就可以解釋場景(4)「控件設置setEnable(false)時的場景」下,沒有執行onTouch和onClick方法,但onTouchEvent方法仍然消費了事件。
這裏對關鍵的源碼進行了詳細的講解,且對前面提到的幾種常見的現象,從源碼角度進行了解釋,但願經過這些來加深對View事件分發的認識和理解。關於View中事件的分發,要講的就是這些了。
4、ViewGroup事件分發處理解析
ViewGroup中處理事件邏輯的方法有兩個,分別是dispatchTouchEvent和onInterceptTouchEvent。當一個ViewGroup控件收到上一級傳遞來的事件時,也是先通過dispatchTouchEvent處理。我們先看看該方法的源碼,該方法很長,這裏僅截取了其中關鍵流程的代碼:
1 //=========================ViewGroup.java======================== 2 @Override 3 public boolean dispatchTouchEvent(MotionEvent ev) { 4 ...... 5 boolean handled = false; 6 //該判斷條件在View的dispatchTouchEvent部分講過,這個方法用於判斷控件是否被遮擋。 7 if (onFilterTouchEventForSecurity(ev)) { 8 final int action = ev.getAction(); 9 final int actionMasked = action & MotionEvent.ACTION_MASK; 10 // Handle an initial down. 11 if (actionMasked == MotionEvent.ACTION_DOWN) { 12 // Throw away all previous state when starting a new touch gesture. 13 // The framework may have dropped the up or cancel event for the previous gesture 14 // due to an app switch, ANR, or some other state change. 15 /**當開始一個新的觸摸時,這兩句會清除掉之前的狀態, 16 * 清除FLAG_DISALLOW_INTERCEPT設置, 17 * 代碼中還會執行mFirstTouchTarget = null 18 */ 19 cancelAndClearTouchTargets(ev); 20 resetTouchState(); 21 } 22 // Check for interception. 23 final boolean intercepted; 24 /**事件由子View去處理時mFirstTouchTarget會賦值並指向子View。 25 * mFirstTouchTarget != null表示事件由子View來處理; 26 * mFirstTouchTarget = null表示事件由本身處理 27 */ 28 if (actionMasked == MotionEvent.ACTION_DOWN 29 || mFirstTouchTarget != null) { 30 /**FLAG_DISALLOW_INTERCEPT是子View經過requestDisallowInterceptTouchEvent(true)來設置的 31 * disallowIntercept等同於表示是否調用了該方法。 32 */ 33 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 34 if (!disallowIntercept) { 35 //子View經過onInterceptTouchEvent來判斷是否須要攔截。 36 intercepted = onInterceptTouchEvent(ev); 37 ev.setAction(action); // restore action in case it was changed 38 } else { 39 intercepted = false; 40 } 41 } else { 42 // There are no touch targets and this action is not an initial down 43 // so this view group continues to intercept touches. 44 //沒有找處處理事件的子View,而且這個action不是ACTION_DOWN,因此該View開始攔截觸摸事件。 45 intercepted = true; 46 } 47 ...... 48 //若是事件沒有取消且沒有被攔截 49 if (!canceled && !intercepted) { 50 ...... 51 //尋找可以接收該事件的View 52 for (int i = childrenCount - 1; i >= 0; i--) { 53 ...... 54 final View child = getAndVerifyPreorderedView( 55 preorderedList, children, childIndex); 56 ...... 57 /**這兩個判斷條件分別爲一、View可見而且沒有播放動畫; 58 * 二、點擊事件的座標在View的範圍內。 59 * 若是其中一個條件不知足,就進行下一次循環。 60 */ 61 if (!canViewReceivePointerEvents(child) 62 || !isTransformedTouchPointInView(x, y, child, null)) { 63 ev.setTargetAccessibilityFocus(false); 64 continue; 65 } 66 ...... 67 //true表示找到了子View來處理事件,false表示沒有子View來處理。 68 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 69 // Child wants to receive touch within its bounds. 70 ...... 71 //addTouchTarget方法中,會給mFirstTouchTarget賦值。 72 newTouchTarget = addTouchTarget(child, idBitsToAssign); 73 ...... 74 break; 75 } 76 ...... 77 } 78 } 79 //沒有找到能夠處理事件的子View,須要自身來處理。 80 if (mFirstTouchTarget == null) { 81 // No touch targets so treat this as an ordinary view. 82 handled = dispatchTransformedTouchEvent(ev, canceled, null, 83 TouchTarget.ALL_POINTER_IDS); 84 } else { 85 ...... 86 //這裏有一部分源碼暫時沒有讀懂 87 } 88 ...... 89 } 90 ...... 91 return handled; 92 }
代碼中梳理出了關鍵的流程,並對幾處要點作了一些註釋說明,我們這裏再解讀一下這段代碼:
(1)第11行中,當一個新的事件開始時,即ACTION_DOWN觸發時,會進行初始化,清理掉以前的一些狀態,這一點很容易理解。
(2)第24~27行對字段mFirstTouchTarget進行了簡單的說明,這裏再也不贅述了,後面幾個關鍵點須要用到這個字段來進行判斷。
(3)第28行,若是是ACTION_DOWN事件或者其它事件下找到了處理事件的子View時,會進入到該方法中。
(4)第30~33行,主要是FLAG_DISALLOW_INTERCEPT相關。這裏須要簡單介紹一下requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法,該方法用於子View請求父View,不讓父View攔截,即讓父View的onInterceptTouchEvent方法返回true時,失效。在沒有調用該方法時,默認其參數爲false,即requestDisallowInterceptTouchEvent(false)效果等同於沒有調用該方法。FLAG_DISALLOW_INTERCEPT的標誌位就是經過該方法來進行設置的。該方法對除ACTION_DOWN以外的事件有效,對ACTION_DOWN事件無效,在第12~20行中講過,ACTION_DOWN時,會清除以前狀態,FLAG_DISALLOW_INTERCEPT標誌位也被恢復。這裏簡單演示一下該方法的使用:
首先在ViewGroupMiddle類中修改onInterceptTouchEvent爲如下代碼,ViewInner中先不作修改,且爲了作對比,要讓ViewInner添加clickable=「true」,可以消費事件:
1 //===========ViewGroupMiddle.java=========== 2 @Override 3 public boolean onInterceptTouchEvent(MotionEvent ev) { 4 Log.i("songzheweiwang-2", "[ViewGroupMiddle-->onInterceptTouchEvent]ev=" + EventUtil.parseAction(ev.getAction())); 5 switch (ev.getAction()) { 6 case MotionEvent.ACTION_DOWN: 7 return false; 8 default: 9 return true; 10 } 11 }
查看log爲:
06-12 00:21:27.800 6906-6906/com.example.demos I/songzheweiwang-2: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 06-12 00:21:27.800 6906-6906/com.example.demos I/songzheweiwang-2: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 06-12 00:21:27.800 6906-6906/com.example.demos I/songzheweiwang-2: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 06-12 00:21:27.800 6906-6906/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_DOWN 06-12 00:21:27.832 6906-6906/com.example.demos I/songzheweiwang-2: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP 06-12 00:21:27.832 6906-6906/com.example.demos I/songzheweiwang-2: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_UP 06-12 00:21:27.832 6906-6906/com.example.demos I/songzheweiwang-2: [ViewInner-->dispatchTouchEvent]event=ACTION_CANCEL 06-12 00:21:27.832 6906-6906/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_CANCEL
ACTION_DOWN沒有被攔截,正常傳遞到了ViewInner中,而其餘事件卻被攔截了。這裏還能夠看到,一個完整的事件,若是隻有ACTION_DOWN,卻沒有ACTION_UP,最終會觸發ACTION_CANCEL。
如今在ViewInner類的onTouchEvent方法中調用getParent().requestDisallowInterceptTouchEvent(true)方法,以下所示:
1 //=============ViewInner.java============= 2 @Override 3 public boolean onTouchEvent(MotionEvent event) { 4 Log.i("songzheweiwang-2","[ViewInner-->onTouchEvent]event="+EventUtil.parseAction(event.getAction())); 5 getParent().requestDisallowInterceptTouchEvent(true); 6 return super.onTouchEvent(event); 7 }
之因此要在ViewGroup類中不攔截ACTION_DOWN事件,是由於若是這裏攔截了,ViewInner中的方法就不會執行了,第5行的代碼也天然起不到效果。獲得的log以下:
1 06-12 00:37:24.505 7509-7509/com.example.demos I/songzheweiwang-2: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_DOWN 2 06-12 00:37:24.505 7509-7509/com.example.demos I/songzheweiwang-2: [ViewGroupMiddle-->onInterceptTouchEvent]ev=ACTION_DOWN 3 06-12 00:37:24.505 7509-7509/com.example.demos I/songzheweiwang-2: [ViewInner-->dispatchTouchEvent]event=ACTION_DOWN 4 06-12 00:37:24.505 7509-7509/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_DOWN 5 06-12 00:37:24.524 7509-7509/com.example.demos I/songzheweiwang-2: [ViewGroupMiddle-->dispatchTouchEvent]ev=ACTION_UP 6 06-12 00:37:24.524 7509-7509/com.example.demos I/songzheweiwang-2: [ViewInner-->dispatchTouchEvent]event=ACTION_UP 7 06-12 00:37:24.524 7509-7509/com.example.demos I/songzheweiwang-2: [ViewInner-->onTouchEvent]event=ACTION_UP
能夠看到,ACTION_UP事件在ViewInner中正常執行了。因此在第33行的disallowIntercept變量值,在調用該方法且當前事件不是ACTION_DOWN時(由於會清除掉該方法的效果),其值爲true,反之則爲false。在這裏,能夠看到,因爲調用了該方法,因此在ACTION_UP事件中,ViewGroupMiddle中的onInterceptTouchEvent方法沒有執行了。
該方法在平常解決事件衝突時常常會用到,因此這裏花了一點篇幅來說解了一下該方法。
(5)第36行中,調用了onInterceptTouchEvent方法。這個方法咱們在【(五)】中提到無數遍了,這裏總算看到了它的身影。天然,這裏必定要去看看其源碼。
1 //=================ViewGroup.java============= 2 public boolean onInterceptTouchEvent(MotionEvent ev) { 3 if (ev.isFromSource(InputDevice.SOURCE_MOUSE) 4 && ev.getAction() == MotionEvent.ACTION_DOWN 5 && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY) 6 && isOnScrollbarThumb(ev.getX(), ev.getY())) { 7 return true; 8 } 9 return false; 10 }
What?這麼簡單?if語句看起來是和鼠標有關係的,貌似和我們的示例及平時使用沒有半毛錢的關係,因此除去這個判斷語句,其實就只有第8行的代碼了。這裏,在【(五)】中講過,對於重寫的onInterceptTouchEvent方法,返回false和返回默認的super.onInterceptTouchEvent,結果是同樣的。
(6)第28~46行就肯定了是否須要對事件進行攔截了。這裏也能夠明確看到,onInterceptTouchEvent方法,不必定每次都會被調用了。這裏咱們在【(五)】中也屢次看到這種情形。
(7)第49行中,咱們關注後面這個條件,在沒有被攔截的狀況下才會進入這個if語句內部。它的主要做用就是遞歸遍歷其子view,找到能夠接收該事件的view。這裏重點須要關注的是第68行的dispatchTransformedTouchEvent方法。這個方法咱們在第一節的log中就屢次看到過,在ViewGroup中它和dispatchTouchEvent方法老是成對出現的。它的源碼以下:
1 //===============ViewGroup.java============== 2 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 3 View child, int desiredPointerIdBits) { 4 final boolean handled; 5 ...... 6 //transformedEvent是對event的加工處理 7 if (child == null) { 8 handled = super.dispatchTouchEvent(transformedEvent); 9 } else { 10 ...... 11 handled = child.dispatchTouchEvent(transformedEvent); 12 } 13 return handle; 14 }
這裏傳入的child參數是有值的,不是null,因此會調用第10行,進行遞歸。當遍歷到葉子View的時候,就跳轉到View類中的diapatchTouchEvent方法中了,咱們前面已經講解過了。因此,若是找到了處理事件的view了,handle就是true,該方法返回true,就會跳出for循環,再也不須要便利了,不然繼續便利。這裏面有個addTouchTarget方法,其中會對mFirstTouchTarge進行賦值,說明找到了處理事件的子View,若是沒找到,這個值會一直爲null。
(8)第79~83行,既然前面沒有找到能夠處理該事件的子view,那麼就只能本身來接手該事件了。第82行,第三個參數傳入的是nul,因此該方法體中就會執行第7行。ViewGroup的父類是View,因此會走到View類中,走View處理事件的邏輯了,這裏也須要注意,不要混淆父類和父控件了。而若是子View可以處理該事件,那後面當前ViewGroup控件就和這個事件沒啥關係了。如今,咱們也就可以解釋【(五)】中的不少現象了,如:ViewInner沒法消費掉事件時,ViewGroupMiddle會回調自身的onTouchEvent方法;若是ViewInner消費了當前的事件,那麼本次事件就不會再返回到上級控件了。
(9)最後該方法的返回值仍然是,若是返回true,那麼表示事件被消費了,不然返回false。
ViewGroup類中重點須要關注的方法就講完了,其實主要就是dispatchTouchEvent方法的邏輯。如今咱們應該能夠理解,在【(五)】在各類現象和結論了吧。
5、參考文章
【Android事件分發機制徹底解析,帶你從源碼的角度完全理解(上)】
【Android事件分發機制徹底解析,帶你從源碼的角度完全理解(下)】
結語
Android的View事件傳遞機制的源碼分析,到這裏就講完了,不知道本文中提到的一些場景,是否是也曾經困擾過讀者呢?固然,筆者這裏只是抓取了主要的關鍵流程和方法進行了講解,真實的源碼邏輯遠遠比這要複雜,由於平時會碰到各類不一樣的狀況,好比長按,tip功能的邏輯處理等。總的來講,本文還只不過是登堂入室的開始而已,其中若是有不妥或者不正確的地方,歡迎來拍磚。
原文出處:https://www.cnblogs.com/andy-songwei/p/11039252.html