轉(zhuǎn)載請標(biāo)注: http://www.itdecent.cn/p/bea1bb4aac95
一、UI overview
在說 View的事件傳遞過程之前先看下UI overview。

這里的 screen_simple.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
Activity并不負(fù)責(zé)視圖控制,它只是控制生命周期和事件處理,真正控制視圖的是Window。一個Activity包含一個Window,Window才是真正代表一個窗口,Window 中持有一個 DecorView,而這個DecorView才是 view 的根布局。
打個不恰當(dāng)比喻吧,Window類相當(dāng)于一幅畫(抽象概念,什么畫我們未知) ,PhoneWindow為一副齊白石先生的山水畫(具體概念,我們知道了是誰的、什么性質(zhì)的畫),DecorView則為該山水畫的具體內(nèi)容(有山、有水、有樹,各種界面)。DecorView呈現(xiàn)在PhoneWindow上。
上面兩段文字摘自網(wǎng)絡(luò)
DecorView繼承FrameLayout, 即它本是一個ViewGroup. PhoneWindow通過 installDecor()來安裝DecorView. 如圖所示,

installDecor() 主要做了以下幾件事情
- 生成DecorView的實(shí)例 (這樣PhoneWindow就可以有View根的引用了)
- Android為DecorView做了一些通用的Layout(比如帶標(biāo)題、ActionBar等等)
如:
FEATURE_SWIPE_TO_DISMISS -> screen_swipe_dismiss.xml
FEATURE_ACTION_MODE_OVERLAY -> screen_simple_overlay_action_mode.xml
DEFAULE -> screen_simple.xml
這里的Layout會根據(jù)Window的Feature去做具體的選擇,也就是開發(fā)者自己選擇。但它們都有一個共同的特點(diǎn),就是所有的Layout里都包含有android:id/content這個子ViewGroup, 而這個content就是給Activity里 setContentView所使用的。
- PhoneWindow引用到App需要定制的mContentParent.
mContentParent指向通用Layout里的 android:id/content, 而這個content 即是Activity里setContentView里的Parent View.
下面來看下setContentView關(guān)鍵代碼, 可見它將開發(fā)者自定義的layout inflate到了mContentParent里了
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//將Activity里的layout inflate到 mContentParent里
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
二、Activity/ViewRootImpl以及WMS之間的關(guān)系
可以參考下這篇文章,http://www.itdecent.cn/p/c223b993b1ec

上圖發(fā)生的順序如下:
- 初始化Activity
ActivityThread在實(shí)例化一個Activity后,會將AMS 傳遞過來的信息attach到Activity里,同時生成一個PhoneWindow對象.
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
…
}
- 接著為PhoneWindow安裝DecorView (一般是在 Activity.onCreate()調(diào)用setContentView來最終調(diào)用到installDecor)
- WindowManager 將ViewRootImpl.W傳給 WMS管理, (具體是 addView )
a) 生成 ViewRootImpl
b) 將DecorView作為 根view
c) 生成 WindowInputEventReceiver 用于接收觸摸按鍵等事件(具體的注冊動作是在WMS里進(jìn)行的)
d) 通過Session.addToDisplay 將W 傳給WMS
從上面3點(diǎn)可知:
- 一個App只有一個WindowManager (WindowManagerGlobal.getInstance())
- WindowManagerGlobal管理著App里所有的RootView(DecorView或addView里的根View), ViewRootImpl,以及對應(yīng)的LayoutParams
- 每個Activity僅有一個DecorView
- 每個Activity僅有一個PhoneWindow
- ViewRootImpl的個數(shù)與addView的個數(shù)相關(guān), 并不一定只有一個。
ViewRootImpl.W
該類用于WindowManagerService通知一些UI相關(guān)事件發(fā)生了,如:
dispatchAppVisiblity
windowFocusChanged
activateWindow
三、View的事件流程
圖3 的WindowInputEventReceiver類用于接收IMS傳遞過來的按鍵、觸屏等相關(guān)事件。下面以觸屏事件為例來看下事件傳遞過程

上圖灰色背景一般是APP不能直接處理的,而淺綠色背景的模塊則是APP需要定制的。
3.1 View都不處理事件

上圖表示所有的View都沒有處理 ACTION_DOWN事件,那么就沒有必要把后面的事件(ACTION_MOVE/ACTION_UP)再往下發(fā)送了。
這樣后面來的ACTION_MOVE和ACTION_UP都將不會處理了。
具體的實(shí)現(xiàn)是在ViewGroup.dispatchTouchEvent, 通過判斷mFirstTouchTarget是否為空來決定是否還繼續(xù)往子view發(fā)送。 因?yàn)槿绻凶觱iew consume了ACTION_DOWN事件的話,那么 mFirstTouchTarget將不會為NULL.
3.2 子View處理事件

View里因?yàn)樵O(shè)置了 onClickListener(), 這樣就導(dǎo)致 View是 clickable (或者可以直接在xml里加上android:clickable=”true”),即可點(diǎn)擊,那么View.onTouchEvent就會永遠(yuǎn)返回 True, 代表View consume了該事件。
注意:只要View consume了該事件,那么該事件既不會往下傳(不會傳給子view),也不會往上傳(后面Activity/ViewGroup 的 onTouchEvent將不會再調(diào)用)。
3.3 ViewGroup攔截

onInterceptTouchEvent 是攔截的意思,它是攔截事件不往下傳(不會再傳給子View),但是會將事件往上傳(相關(guān)的 Activity/ViewGroup 會調(diào)用 onTouchEvent).
上圖 onInterceptTouchEvent攔截了,所以不會再將ACTION_DOWN往View里傳了,直接返回到 onTouchEvent. 因?yàn)锳CTION_DOWN沒有被任何View consume, 所以也沒必要繼續(xù)發(fā)送 ACTION_MOVE/ACTION_UP事件了。
3.4 ViewGroup consume事件

上圖是ViewGroup的 onTouchEvent consume了事件,那么說明是ViewGroup需要處理事件,所以將后續(xù)的 ACTION_MOVE/ACTION_UP直接發(fā)送給ViewGroup處理了,就不再需要往子View傳遞事件了。
四、總結(jié)
onTouchEvent 返回值表示是否 consume該事件,它的傳遞方向是往上傳遞,View -> ViewGroup->Activity. 但是一旦某個View或ViewGroup的onTouchEvent返回True, 就不會再往上傳遞了。
onInterceptTouchEvent 是ViewGroup特有的,表示是否攔截事件往下(子View)傳遞,Activity->ViewGroup->View, 如果ViewGroup攔截了事件,那么會直接調(diào)用ViewGroup 的onTouchEvent。