前言
android事件分發(fā)算是自定義View不可缺失的一部分,事件分發(fā)是指那一類跟屏幕交互的操作等事件,例如滑動,點擊,長按這類。這些事件都是由摁下、移動、抬起等基本事件組成的。那事件分發(fā)是指當(dāng)你點擊了屏幕,這個事件是如何從Activity傳遞到真正處理這個事件的View上的過程。例如,我們在做ListView跟ViewPager嵌套的時候,既能左右滑動,又能上下滑動,這些事件是如何避免彼此間的沖突的。下面我會分三節(jié)來介紹這一原理。
首先,這次的源碼分析是基于25.0.3版本進(jìn)行的。
分發(fā)事件
分發(fā)的事件主要是MotionEvent這個類所表示的點擊、移動、抬起、取消等事件
MotionEvent.ACTION_DOWN
MotionEvent.ACTION_MOVE
MotionEvent.ACTION_UP
MotionEvent.ACTION_CANCEL
...
分發(fā)的對象
分發(fā)的對象是指收到上述事件的類
Activity
ViewGroup
View
上述三個類是主要的事件分發(fā)對象,后期的討論也是集中在這三個類里面。這里有個點得提一下就是ViewGroup是View的子類。這些事件會在這三者的dispatchTouchEvent、onTouchEvent這兩個方法里面?zhèn)鬟f,還有一個ViewGroup特有的onInterceptTouchEvent方法。下面給出以上提到的類以及方法之間的關(guān)系。
從這張圖,我們可以看出,不同返回值,事件的分發(fā)方向不同,這里不做詳細(xì)分析,這張圖是否畫得出來作為你對這次源碼閱讀的成績。
Demo
Activity
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
CusViewGroup
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.i(TAG,"onInterceptTouchEvent"+ev.getAction());
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
View
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.i(TAG,"dispatchTouchEvent"+ev.getAction());
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.i(TAG,"onTouchEvent"+event.getAction());
return super.onTouchEvent(event);
}
布局
<?xml version="1.0" encoding="utf-8"?>
<com.example.coffeetime.cusviewdemo.CusLineaLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.coffeetime.cusviewdemo.MainActivity">
<com.example.coffeetime.cusviewdemo.CusView
android:layout_width="200dp"
android:layout_height="200dp"
android:background="@color/colorPrimary"/>
</com.example.coffeetime.cusviewdemo.CusLineaLayout>
結(jié)果
ACTION_DOWN的值為0
ACTION_MOVE的值為1
從輸出的結(jié)果的第一行可以看出來最先獲取事件的是Activity這一層,從倒數(shù)第三行可以看出,ACTION_DOWN這個事件最終消費是在Activity的onTouchEvent這個方法被消費。這個結(jié)果跟上面的圖片是一致的。
再看看輸出結(jié)果的倒數(shù)兩行,ACTION_MOVE事件從dispatchTouchEvent直接就傳給了同級的onTouchEvent方法去了,說明ACTION_DOWN事件在哪里被消費了,后續(xù)事件也在那里消費,并且跳過中間傳遞。這個原因會在分析源碼的時候給出解釋。
Activity
從上述的結(jié)果,我們可以得出事件最先被攔截的地方是從Activity的dispatchTouchEvent方法開始,那我們的源碼攻略也從這里開始。
/**
* 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);
}
這個方法首先判斷事件類型,如果是ACTION_DOWN事件,則先執(zhí)行onUserInteraction()方法;
public void onUserInteraction() {
}
這個方法沒有實現(xiàn),根據(jù)文檔注釋,當(dāng)有任意一個按鍵、觸屏或者軌跡球事件發(fā)生時,棧頂Activity的onUserInteraction會被觸發(fā)。如果我們需要知道用戶是不是正在和設(shè)備交互,可以在子類中重寫這個方法,去獲取通知(比如取消屏保這個場景)。跟這個方法配對的還有onUserLeaveHint方法,這個方法是在用戶離開設(shè)備的時候觸發(fā)的。
我們接著回到剛剛那個地方,判斷完事件ACTION_DOWN事件之后,會執(zhí)行getWindow().superDispatchTouchEvent(ev)這個方法,這個getWindow獲取的是哪個Window呢,我們進(jìn)去看看。
public Window getWindow() {
return mWindow;
}
/**
* 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.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*/
public abstract class Window {
通過這塊注釋,我們可以看出,window的實現(xiàn)類是phoneWindow。其實在Android里面很多都是可以通過閱讀注釋或者是官方文檔去找出具體的實現(xiàn)類,不是只能依靠百度;好了,那我們可以直接進(jìn)到phoneWindow類去看下superDispatchTouchEvent這個方法做了些什么
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
這個方法又是調(diào)用mDecor的同名方法去實現(xiàn),mDecor又是什么
phoneWindow
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView 是window的頂級View,而DecorView是繼承FrameLayout的布局,接著去FragmeLayout里面尋找dispatchTouchEvent方法,而FragmeLayout并沒有實現(xiàn)這個方法,也即這個是直接交給ViewGroup去處理的。
小結(jié)
在Activity層事件的傳遞過程如下圖
從Activity的dispatchTouchEvent獲取事件經(jīng)過PhoneWindow、DecorView再到最終的ViewGroup,這一層代碼比較簡單,沒有比較難分析的,只是經(jīng)過的類比較多,其實只要把握的主線,分析起來還是比較簡單的。這一篇比較少,下一篇是ViewGroup的分析,由于ViewGroup的源碼比較多,所以才拆開來寫,不然太長了。