Android事件傳遞機制詳解及最新源碼分析——Activity篇


版權聲明:本文出自汪磊的博客,轉載請務必注明出處。

在前兩篇我們共同探討了事件傳遞機制《View篇》《ViewGroup篇》,我們知道View觸摸事件是ViewGroup傳遞過去的,比如一個很簡單的布局最外層是LinearLayout,里面就一個Button,我們點擊Button的時候觸摸事件是由外層LinearLayout傳遞給里面Button的,但是有沒有想過當前觸摸事件是誰傳遞給外層的LinearLayout的呢?帶着這個疑問我們繼續來共同探討一下。

從Demo示例說起

我們還是先寫一個簡單的demo,很簡單,代碼如下:自定義Button:

 1 public class MyButton extends Button {  2 
 3     private final String TAG = "WL";  4     
 5     public MyButton(Context context, AttributeSet attrs) {  6  super(context, attrs);  7  }  8 
 9  @Override 10     public boolean dispatchTouchEvent(MotionEvent ev) { 11         // 12         Log.i(TAG, "MyButton_dispatchTouchEvent_Action:"+ev.getAction()); 13         return super.dispatchTouchEvent(ev); 14  } 15     
16  @Override 17     public boolean onTouchEvent(MotionEvent event) { 18         // 19         Log.i(TAG, "MyButton_onTouchEvent_Action:"+event.getAction()); 20         return super.onTouchEvent(event); 21  } 22 }

 

自定義LinearLayout:

 1 public class MyLinearLayout extends LinearLayout {  2     
 3     private final String TAG = "WL";  4 
 5     public MyLinearLayout(Context context, AttributeSet attrs) {  6  super(context, attrs);  7         //  8  }  9     
10  @Override 11     public boolean dispatchTouchEvent(MotionEvent ev) { 12         // 13         Log.i(TAG, "MyLinearLayout_dispatchTouchEvent_Action:"+ev.getAction()); 14         return super.dispatchTouchEvent(ev); 15  } 16     
17  @Override 18     public boolean onTouchEvent(MotionEvent event) { 19         // 20         Log.i(TAG, "MyLinearLayout_onTouchEvent_Action:"+event.getAction()); 21         return super.onTouchEvent(event); 22  } 23 }

 

布局文件:

 1 <com.wl.activitydispatchtouchevent.MyLinearLayout  2     xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:layout_width="match_parent"
 5     android:layout_height="match_parent"
 6     android:background="#0099cc"
 7     android:id="@+id/mylinearlayout"
 8     android:gravity="center"
 9     tools:context="com.wl.activitydispatchtouchevent.MainActivity" >
10 
11     <com.wl.activitydispatchtouchevent.MyButton 12         android:id="@+id/mybutton"
13         android:layout_width="wrap_content"
14         android:layout_height="wrap_content"
15         android:textSize="20sp"
16         android:text="WL_Button" />
17 
18 </com.wl.activitydispatchtouchevent.MyLinearLayout>

 

Activity中代碼:

 1 public class MainActivity extends Activity implements OnClickListener,  2  OnTouchListener {  3 
 4     private final String TAG = "WL";  5 
 6  @Override  7     protected void onCreate(Bundle savedInstanceState) {  8         super.onCreate(savedInstanceState);  9  setContentView(R.layout.activity_fullscreen); 10         // 11         findViewById(R.id.mybutton).setOnClickListener(this); 12         findViewById(R.id.mybutton).setOnTouchListener(this); 13         // 14         findViewById(R.id.mylinearlayout).setOnClickListener(this); 15         findViewById(R.id.mylinearlayout).setOnTouchListener(this); 16  } 17 
18  @Override 19     public boolean onTouch(View v, MotionEvent event) { 20         // 21         Log.i(TAG, "onTouch___v:" + v + "___action:" + event.getAction()); 22         return false; 23  } 24 
25  @Override 26     public void onClick(View v) { 27         // 28         Log.i(TAG, "onClick___v:" + v); 29  } 30 
31  @Override 32     public boolean dispatchTouchEvent(MotionEvent ev) { 33         Log.i(TAG, "MainActivity__dispatchTouchEvent__action:" + ev.getAction()); 34         return super.dispatchTouchEvent(ev); 35  } 36 
37  @Override 38     public boolean onTouchEvent(MotionEvent event) { 39         Log.i(TAG, "MainActivity___onTouchEvent___action=" + event.getAction()); 40         return super.onTouchEvent(event); 41  } 42 }

 

怎么樣,很簡單吧。和上一篇講解ViewGroup傳遞機制的Demo幾乎差不多,主要差別就是在Activity中我們重寫了Activity的dispatchTouchEventonTouchEvent方法。

我們看一下運行效果,點擊Button,打印信息如下:

 1 MainActivity__dispatchTouchEvent__action:0
 2 MyLinearLayout_dispatchTouchEvent_Action:0
 3 MyButton_dispatchTouchEvent_Action:0
 4 onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:0
 5 MyButton_onTouchEvent_Action:0
 6 MainActivity__dispatchTouchEvent__action:1
 7 MyLinearLayout_dispatchTouchEvent_Action:1
 8 MyButton_dispatchTouchEvent_Action:1
 9 onTouch___v:com.wl.activitydispatchtouchevent.MyButton___action:1
10 MyButton_onTouchEvent_Action:1
11 onClick___v:com.wl.activitydispatchtouchevent.MyButton

 

除去與Activity有關的信息,其余信息打印順序相信你應該輕松理解了。我們看到觸摸事件實現傳遞到Activity中的,其次才傳遞到MyLinearLayout,最后傳遞給MyButton。是不是觸摸事件就是Activity先獲取到接下來才繼續向下傳遞的呢?別急着下結論,我們看看Activity中dispatchTouchEvent都做了什么。

Activity事件傳遞機制源碼分析(源碼版本為API23

Activity中dispatchTouchEvent方法源碼如下:

1  public boolean dispatchTouchEvent(MotionEvent ev) { 2         if (ev.getAction() == MotionEvent.ACTION_DOWN) { 3  onUserInteraction(); 4  } 5         if (getWindow().superDispatchTouchEvent(ev)) { 6             return true; 7  } 8         return onTouchEvent(ev); 9     }

是不是爽歪歪?這么短,我們看2-4行代碼,首先判斷如果是ACTION_DOWN事件則執行onUserInteraction()方法,對於onUserInteraction()方法這里不做具體分析,不是本篇重點。

我們繼續向下分析,5-9代碼,如果if條件成立則直接返回true,不成立則dispatchTouchEvent最終返回值由onTouchEvent決定,那么if判斷就是關鍵了。

5行代碼,getWindow()返回mWindow對象,在Activity的attach方法中進行的初始化,如下:

 1     final void attach(Context context, ActivityThread aThread,  2             Instrumentation instr, IBinder token, int ident,  3  Application application, Intent intent, ActivityInfo info,  4  CharSequence title, Activity parent, String id,  5  NonConfigurationInstances lastNonConfigurationInstances,  6  Configuration config, String referrer, IVoiceInteractor voiceInteractor) {  7         
 8  ...........  9         mWindow = new PhoneWindow(this); 10         mWindow.setCallback(this); 11  ........... 12 }

 

mWindow其實就是PhoneWindow對象,接下來我們找到PhoneWindow類(源碼目錄:...\sdk\sources\android-23\com\android\internal\policy\)。
PhoneWindow類繼承自Window類,我們先看看父類中superDispatchTouchEvent是怎么處理的。
Window類中superDispatchTouchEvent源碼如下:
1  /**
2  * Used by custom windows, such as Dialog, to pass the touch screen event 3  * further down the view hierarchy. Application developers should 4  * not need to implement or call this. 5  * 6      */
7 public abstract boolean superDispatchTouchEvent(MotionEvent event);
 
        

看到了吧,很簡單,父類中就是一個抽象方法, 看注釋就知道此方法主要用來屏幕事件傳遞的,開發者不需要實現或者調用這個方法。

接下來我們看看PhoneWindow類中的superDispatchTouchEvent方法:

1  @Override 2     public boolean superDispatchTouchEvent(MotionEvent event) { 3         return mDecor.superDispatchTouchEvent(event); 4   }

是不是更簡單?直接調用mDecor的superDispatchTouchEvent方法,mDecor是什么玩意呢?這里就直說說了,mDecor是DecorView的實例。

DecorView類是PhoneWindow類的內部類,源碼如下:

1 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker { 2 
3  .......... 4    public boolean superDispatchTouchEvent(MotionEvent event) { 5          return super.dispatchTouchEvent(event); 6  } 7     
8  .......... 9 }

我勒個去,搞半天DecorView 繼承自FrameLayout,我們知道 FrameLayout繼承自ViewGroup,最終就是調用ViewGroup中的dispatchTouchEvent方法進行事件分發。

但是到這里我們還有一個疑問,以我們Demo為例,通過上述分析事件先傳遞到Activity的dispatchTouchEvent方法,然后調用DecorView 的superDispatchTouchEvent方法最終調用的ViewGroup的dispatchTouchEvent方法,但是跟我們Demo中的MyLinearLayout有什么關系呢?或者說是怎么傳遞到MyLinearLayout的呢?

要解答這個疑問我們就必須熟知我們平時調用Activity中的setContentView方法設置布局的時候我們自己的布局到底是怎么掛載到Activity上的,這篇我們就不進入深入源碼解析了,不是本篇重點,直說一些結論性東西。后續會單獨寫一篇文章專門分析setContentView究竟都做了什么。

我們在調用setContentView設置布局的時候其實都是被放置在id為content的FrameLayout 布局中的,注意id為content的FrameLayout 布局並不是上面講的DecorView,具體層級關系如下:

看到了吧,id為content的FrameLayout 布局DecorView的子View布局。我們自己的布局最后總會替換掉id為content的FrameLayout

到這里你該明白了吧,Activity將觸摸事件經過層層傳遞給DecorView, DecorView會調用ViewGroup的dispatchTouchEvent方法將事件傳遞給子View。之后的邏輯就是我們上兩篇所講的內容了。

接下來我們回看Activity中dispatchTouchEvent方法,第5行根據我們上述分析的,如果最終找到子View消耗事件則返回值為true,進而整個方法返回true。如果沒有子View處理當前觸摸事件則返回false,執行Activity中onTouchEvent方法。

我們接下來分析一下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 
17         return false; 18     }

 邏輯也不復雜,主要就是第12行代碼,調用mWindowshouldCloseOnTouch方法,如果此方法返回true則整個方法返回true,反之則返回false。

mWindow上面我們分析過就是PhoneWindow的實例,好了我們就去PhoneWindow類中找shouldCloseOnTouch方法看一下吧,然而PhoneWindow中並沒有這個方法,那我們看看父類Window中有沒有這個方法呢,果然這個方法在其父類中找到,源碼如下:

1 public boolean shouldCloseOnTouch(Context context, MotionEvent event) { 2         if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN 3                 && isOutOfBounds(context, event) && peekDecorView() != null) { 4             return true; 5  } 6         return false; 7   }

 

這里主要邏輯也是一個判斷,判斷mCloseOnTouchOutside標記以及是否為ACTION_DOWN事件,然后判斷點擊事件的坐標x,y有沒有超出邊界,最后調用peekDecorView()判斷是否為空。peekDecorView()在Window類中就是一個抽象方法,具體實現在PhoneWindow類中如下:

1     public final View peekDecorView() { 2         return mDecor; 3     }

 

很簡單,就是返回mDecor,上面我們分析過mDecor就是DecorView的實例,這里我們需要知道我們在Activity中調用setContentView的時候mDecor就會初始化,具體分析會在下一篇文章中,這里只要知道mDecor不為null就可以了。

其余的都不難理解但是mCloseOnTouchOutside 是個什么鬼呢?我們知道Activity設置成Dialog樣式的時候默認點擊外部的時候是會關閉的,同樣我們也可以調用setFinishOnTouchOutside(false)設置為點擊外部時候Activity不關閉,mCloseOnTouchOutside 就是用來記錄這個的,如果我們將Activity設置為Dialog樣式mCloseOnTouchOutside 默認就被設置為true,我們知道大部分情況下Activity是不會設置為Dialog樣式的,所以mCloseOnTouchOutside 默認為false。(關於mCloseOnTouchOutside其實是想從源碼角度分析一下的,但是這部分內容實在和傳遞機制不沾邊,就這部分有一個判斷,所以就不仔細分析了,在下一篇分析setContentView的時候在提一下吧 )

這里我們稍微總結一下:mCloseOnTouchOutside 默認情況下是false,如果Activity樣式設置為Dialog系統默認會將mCloseOnTouchOutside 設置為true,所以Dialog樣式的Activity默認情況下點擊外部會關閉,如果我們調用setFinishOnTouchOutside(false)或者在styles文件中設置了 <item name="android:windowCloseOnTouchOutside">false</item> 那么最終都會將mCloseOnTouchOutside 變量置為false,點擊Activity外部也就不會關閉了。

綜上分析,Window中shouldCloseOnTouch大多數情況下是返回false的,從而Activity中onTouchEvent大多說情況下也是返回false,除非我們進行了特殊設置。這也就是Activity中onTouchEvent注釋是The default implementation always returns false而不是The default implementation returns false,就多了一個always。

好了,到此為止關於安卓事件傳遞機制最重要的部分都已經講解完畢,最最核心的還是要掌握View以及ViewGroup的部分,至於Activity的傳遞大體了解一下流程就可以了。

下一篇我們一起探究一下Activity中setContentView方法究竟做了什么事情。反過來你能更好理解本篇中的某些點。

  


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM