PS一句:最終還是選擇CSDN來整理發表這幾年的知識點,該文章平行遷移到CSDN。因為CSDN也支持MarkDown語法了,牛逼啊!
【工匠若水?[http://blog.csdn.net/yanbober](http://blog.csdn.net/yanbober)】
該篇承接上一篇[《Android觸摸屏事件派發機制詳解與源碼分析二(ViewGroup篇)》](http://blog.csdn.net/yanbober/article/details/45912661),閱讀本篇之前建議先閱讀。
### 1 背景
還記得前面兩篇從Android的基礎最小元素控件(View)到ViewGroup控件的觸摸屏事件分發機制分析嗎?你可能看完會有疑惑,View的事件是ViewGroup派發的,那ViewGroup的事件呢?他包含在Activity上,是不是Activity也有類似的事件派發方法呢?帶著這些疑惑咱們繼續實例驗證加源碼分析吧。
PS:閱讀本篇前建議先查看前一篇[《Android觸摸屏事件派發機制詳解與源碼分析二(ViewGroup篇)》](http://blog.csdn.net/yanbober/article/details/45912661)與[《Android觸摸屏事件派發機制詳解與源碼分析一(View篇)》](http://blog.csdn.net/yanbober/article/details/45887547),這一篇承接上一篇。
### 2 實例驗證
#### 2-1 代碼
如下實例與前面實例相同,一個Button在LinearLayout里,只不過我們這次重寫了Activity的一些方法而已。具體如下:
自定義的Button與LinearLayout:
~~~
public class TestButton extends Button {
public TestButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(null, "TestButton--dispatchTouchEvent--action="+event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(null, "TestButton--onTouchEvent--action="+event.getAction());
return super.onTouchEvent(event);
}
}
~~~
~~~
public class TestLinearLayout extends LinearLayout {
public TestLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(null, "TestLinearLayout--onInterceptTouchEvent--action="+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.i(null, "TestLinearLayout--dispatchTouchEvent--action=" + event.getAction());
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(null, "TestLinearLayout--onTouchEvent--action="+event.getAction());
return super.onTouchEvent(event);
}
}
~~~
整個界面的布局文件:
~~~
<com.example.yanbo.myapplication.TestLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/layout">
<com.example.yanbo.myapplication.TestButton
android:text="click test"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/button"/>
</com.example.yanbo.myapplication.TestLinearLayout>
~~~
整個界面Activity,重寫了Activity的一些關于觸摸派發的方法(三個):
~~~
public class MainActivity extends Activity implements View.OnClickListener, View.OnTouchListener {
private TestButton mButton;
private TestLinearLayout mLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (TestButton) this.findViewById(R.id.button);
mLayout = (TestLinearLayout) this.findViewById(R.id.layout);
mButton.setOnClickListener(this);
mLayout.setOnClickListener(this);
mButton.setOnTouchListener(this);
mLayout.setOnTouchListener(this);
}
@Override
public void onClick(View v) {
Log.i(null, "onClick----v=" + v);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i(null, "onTouch--action="+event.getAction()+"--v="+v);
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(null, "MainActivity--dispatchTouchEvent--action=" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public void onUserInteraction() {
Log.i(null, "MainActivity--onUserInteraction");
super.onUserInteraction();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(null, "MainActivity--onTouchEvent--action="+event.getAction());
return super.onTouchEvent(event);
}
}
~~~
如上就是實例測試代碼,非常簡單,沒必要分析,直接看結果吧。
#### 2-2 結果分析
直接點擊Button按鈕打印如下:
~~~
MainActivity--dispatchTouchEvent--action=0
MainActivity--onUserInteraction
TestLinearLayout--dispatchTouchEvent--action=0
TestLinearLayout--onInterceptTouchEvent--action=0
TestButton--dispatchTouchEvent--action=0
onTouch--action=0--v=com.example.yanbo.myapplication.TestButton
TestButton--onTouchEvent--action=0
MainActivity--dispatchTouchEvent--action=1
TestLinearLayout--dispatchTouchEvent--action=1
TestLinearLayout--onInterceptTouchEvent--action=1
TestButton--dispatchTouchEvent--action=1
onTouch--action=1--v=com.example.yanbo.myapplication.TestButton
TestButton--onTouchEvent--action=1
onClick----v=com.example.yanbo.myapplication.TestButton
~~~
分析可以發現,當點擊Button時除過派發Activity的幾個新方法之外其他完全符合前面兩篇分析的View與ViewGroup的觸摸事件派發機制。對于Activity
來說,ACTION_DOWN事件首先觸發dispatchTouchEvent,然后觸發onUserInteraction,再次onTouchEvent,接著的ACTION_UP事件觸發
dispatchTouchEvent后觸發了onTouchEvent,也就是說ACTION_UP事件時不會觸發onUserInteraction(待會可查看源代碼分析原因)。
直接點擊Button以外的其他區域:
~~~
MainActivity--dispatchTouchEvent--action=0
MainActivity--onUserInteraction
TestLinearLayout--dispatchTouchEvent--action=0
TestLinearLayout--onInterceptTouchEvent--action=0
onTouch--action=0--v=com.example.yanbo.myapplication.TestLinearLayout
TestLinearLayout--onTouchEvent--action=0
MainActivity--dispatchTouchEvent--action=1
TestLinearLayout--dispatchTouchEvent--action=1
onTouch--action=1--v=com.example.yanbo.myapplication.TestLinearLayout
TestLinearLayout--onTouchEvent--action=1
onClick----v=com.example.yanbo.myapplication.TestLinearLayout
~~~
怎么樣?完全符合上面點擊Button結果分析的猜想。
那接下來還是要看看Activity里關于這幾個方法的源碼了。
### 3 Android 5.1.1(API 22) Activity觸摸屏事件傳遞源碼分析
通過上面例子的打印我們可以確定分析源碼的順序,那就開始分析唄。
#### 3-1 從Activity的dispatchTouchEvent方法說起
#### 3-1-1 開始分析
先上源碼,如下:
~~~
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
~~~
哎呦!這次看著代碼好少的樣子,不過別高興,濃縮才是精華,這里代碼雖少,涉及的問題點還是很多的,那么咱們就來一點一點分析吧。
12到14行看見了吧?上面例子咱們看見只有ACTION_DOWN事件派發時調運了onUserInteraction方法,當時還在疑惑呢,這下明白了吧,不多解釋,咱們直接跳進去可以看見是一個空方法,具體下面會分析。
好了,自己分析15到17行,看著簡單吧,我勒個去,我怎么有點懵,這是哪的方法?咱們分析分析吧。
首先分析Activity的attach方法可以發現getWindow()返回的就是PhoneWindow對象(PhoneWindow為抽象Window的實現子類),那就簡單了,也就相當于PhoneWindow類的方法,而PhoneWindow類實現于Window抽象類,所以先看下Window類中抽象方法的定義,如下:
~~~
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
~~~
看見注釋沒有?用戶不需要重寫實現的方法,實質也不能,在Activity中沒有提供重寫的機會,因為Window是以組合模式與Activity建立關系的。好了
,看完了抽象的Window方法,那就去PhoneWindow里看下Window抽象方法的實現吧,如下:
~~~
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
~~~
又是看著好簡單的樣子哦,實際又是一堆問題,繼續分析。你會發現在PhoneWindow的superDispatchTouchEvent方法里又直接返回了另一個mDecor
對象的superDispatchTouchEvent方法,mDecor是啥?繼續分析吧。
在PhoneWindow類里發現,mDecor是DecorView類的實例,同時DecorView是PhoneWindow的內部類。最驚人的發現是DecorView extends FrameLayout implements RootViewSurfaceTaker,看見沒有?它是一個真正Activity的root view,它繼承了FrameLayout。怎么驗證他一定是root view呢?很簡單,不知道大家是不是熟悉Android App開發技巧中關于UI布局優化使用的SDK工具Hierarchy Viewer。咱們通過他來看下上面剛剛展示的那個例子的Hierarchy Viewer你就明白了,如下我在Ubuntu上截圖的Hierarchy Viewer分析結果:

看見沒有,我們上面例子中Activity中setContentView時放入的xml layout是一個LinearLayout,其中包含一個Button,上圖展示了我們放置的LinearLayout被放置在一個id為content的FrameLayout的布局中,這也就是為啥Activity的setContentView方法叫set content view了,就是把我們的xml放入了這個id為content的FrameLayout中。
趕快回過頭,你是不是發現上面PhoneWindow的superDispatchTouchEvent直接返回了DecorView的superDispatchTouchEvent,而DecorView又是FrameLayout的子類,FrameLayout又是ViewGroup的子類。機智的你想到了啥木有?
沒想到就繼續看下DecorView類的superDispatchTouchEvent方法吧,如下:
~~~
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
~~~
這回你一定恍然大悟了吧,不然就得腦補前面兩篇博客的內容了。。。
搞半天Activity的dispatchTouchEvent方法的15行`if (getWindow().superDispatchTouchEvent(ev))`本質執行的是一個ViewGroup的dispatchTouchEvent方法(這個ViewGroup是Activity特有的root view,也就是id為content的FrameLayout布局),接下來就不用多說了吧,完全是前面兩篇分析的執行過程。
接下來依據派發事件返回值決定是否觸發Activity的onTouchEvent方法。
#### 3-1-2 小總結一下
在Activity的觸摸屏事件派發中:
1. 首先會觸發Activity的dispatchTouchEvent方法。
1. dispatchTouchEvent方法中如果是ACTION_DOWN的情況下會接著觸發onUserInteraction方法。
1. 接著在dispatchTouchEvent方法中會通過Activity的root View(id為content的FrameLayout),實質是ViewGroup,通過super.dispatchTouchEvent把touchevent派發給各個activity的子view,也就是我們再Activity.onCreat方法中setContentView時設置的view。
1. 若Activity下面的子view攔截了touchevent事件(返回true)則Activity.onTouchEvent方法就不會執行。
### 3-2 繼續Activity的dispatchTouchEvent方法中調運的onUserInteraction方法
如下源碼:
~~~
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
* <p>All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
* <p>Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}
~~~
搞了半天就像上面說的,這是一個空方法,那它的作用是啥呢?
此方法是activity的方法,當此activity在棧頂時,觸屏點擊按home,back,menu鍵等都會觸發此方法。下拉statubar、旋轉屏幕、鎖屏不會觸發此方法。所以它會用在屏保應用上,因為當你觸屏機器 就會立馬觸發一個事件,而這個事件又不太明確是什么,正好屏保滿足此需求;或者對于一個Activity,控制多長時間沒有用戶點響應的時候,自己消失等。
這個方法也分析完了,那就剩下onTouchEvent方法了,如下繼續分析。
#### 3-3 繼續Activity的dispatchTouchEvent方法中調運的onTouchEvent方法
如下源碼:
~~~
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
~~~
看見沒有,這個方法看起來好簡單的樣子。
如果一個屏幕觸摸事件沒有被這個Activity下的任何View所處理,Activity的onTouchEvent將會調用。這對于處理window邊界之外的Touch事件非常有用,因為通常是沒有View會接收到它們的。返回值為true表明你已經消費了這個事件,false則表示沒有消費,默認實現中返回false。
繼續分析吧,重點就一句,mWindow.shouldCloseOnTouch(this, event)中的mWindow實際就是上面分析dispatchTouchEvent方法里的getWindow()對象,所以直接到Window抽象類和PhoneWindow子類查看吧,發現PhoneWindow沒有重寫Window的shouldCloseOnTouch方法,所以看下Window類的shouldCloseOnTouch實現吧,如下:
~~~
/** @hide */
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
~~~
這其實就是一個判斷,判斷mCloseOnTouchOutside標記及是否為ACTION_DOWN事件,同時判斷event的x、y坐標是不是超出Bounds,然后檢查
FrameLayout的content的id的DecorView是否為空。其實沒啥太重要的,這只是對于處理window邊界之外的Touch事件有判斷價值而已。
所以,到此Activity的onTouchEvent分析完畢。
### 4 Android觸摸事件綜合總結
到此整個Android的Activity->ViewGroup->View的觸摸屏事件分發機制完全分析完畢。這時候你可以回過頭看這三篇文章的例子,你會完全明白那些打印的含義與原理。
當然,了解這些源碼機制不僅對你寫普通代碼時有幫助,最重要的是對你想自定義裝逼控件時有不可磨滅的基礎性指導作用與技巧提示作用。
- 前言
- Android觸摸屏事件派發機制詳解與源碼分析一(View篇)
- Android觸摸屏事件派發機制詳解與源碼分析二(ViewGroup篇)
- Android觸摸屏事件派發機制詳解與源碼分析三(Activity篇)
- Android應用setContentView與LayoutInflater加載解析機制源碼分析
- Android應用Context詳解及源碼解析
- Android異步消息處理機制詳解及源碼分析
- Android應用Activity、Dialog、PopWindow、Toast窗口添加機制及源碼分析
- Android ListView工作原理完全解析,帶你從源碼的角度徹底理解
- Activity啟動過程全解析
- Android應用AsyncTask處理機制詳解及源碼分析
- 說說 PendingIntent 的內部機制
- Android Activity.startActivity流程簡介
- Activity界面顯示全解析
- 框架層理解Activity生命周期(APP啟動過程)
- APK安裝過程及原理詳解
- Android構建過程簡述
- Android應用層View繪制流程與源碼分析