图解Android View的事件传递

转载请标注: http://www.jianshu.com/p/bea1bb4aac95

一、UI overview

在说 View的事件传递过程之前先看下UI overview。

图1 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并不负责视图控制,它只是控制生命周期和事件处理,真正控制视图的是Window。一个Activity包含一个Window,Window才是真正代表一个窗口,Window 中持有一个 DecorView,而这个DecorView才是 view 的根布局。

打个不恰当比喻吧,Window类相当于一幅画(抽象概念,什么画我们未知) ,PhoneWindow为一副齐白石先生的山水画(具体概念,我们知道了是谁的、什么性质的画),DecorView则为该山水画的具体内容(有山、有水、有树,各种界面)。DecorView呈现在PhoneWindow上。

上面两段文字摘自网络

DecorView继承FrameLayout, 即它本是一个ViewGroup. PhoneWindow通过 installDecor()来安装DecorView. 如图所示,

图2 window and decorview

installDecor() 主要做了以下几件事情

  • 生成DecorView的实例 (这样PhoneWindow就可以有View根的引用了)
  • Android为DecorView做了一些通用的Layout(比如带标题、ActionBar等等)
    如:
    FEATURE_SWIPE_TO_DISMISS -> screen_swipe_dismiss.xml
    FEATURE_ACTION_MODE_OVERLAY -> screen_simple_overlay_action_mode.xml
    DEFAULE -> screen_simple.xml
    这里的Layout会根据Window的Feature去做具体的选择,也就是开发者自己选择。但它们都有一个共同的特点,就是所有的Layout里都包含有android:id/content这个子ViewGroup, 而这个content就是给Activity里 setContentView所使用的。
  • PhoneWindow引用到App需要定制的mContentParent.
    mContentParent指向通用Layout里的 android:id/content, 而这个content 即是Activity里setContentView里的Parent View.

下面来看下setContentView关键代码, 可见它将开发者自定义的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之间的关系

可以参考下这篇文章,http://www.jianshu.com/p/c223b993b1ec

图3 Activity ViewRootImpl Wms之间的关系

上图发生的顺序如下:

  1. 初始化Activity
    ActivityThread在实例化一个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);
…
}
  1. 接着为PhoneWindow安装DecorView (一般是在 Activity.onCreate()调用setContentView来最终调用到installDecor)
  2. WindowManager 将ViewRootImpl.W传给 WMS管理, (具体是 addView )
    a) 生成 ViewRootImpl
    b) 将DecorView作为 根view
    c) 生成 WindowInputEventReceiver 用于接收触摸按键等事件(具体的注册动作是在WMS里进行的)
    d) 通过Session.addToDisplay 将W 传给WMS

从上面3点可知:

  1. 一个App只有一个WindowManager (WindowManagerGlobal.getInstance())
  2. WindowManagerGlobal管理着App里所有的RootView(DecorView或addView里的根View), ViewRootImpl,以及对应的LayoutParams
  3. 每个Activity仅有一个DecorView
  4. 每个Activity仅有一个PhoneWindow
  5. ViewRootImpl的个数与addView的个数相关, 并不一定只有一个。

ViewRootImpl.W
该类用于WindowManagerService通知一些UI相关事件发生了,如:
dispatchAppVisiblity
windowFocusChanged
activateWindow

三、View的事件流程

图3 的WindowInputEventReceiver类用于接收IMS传递过来的按键、触屏等相关事件。下面以触屏事件为例来看下事件传递过程

图4 View事件传递

上图灰色背景一般是APP不能直接处理的,而浅绿色背景的模块则是APP需要定制的。

3.1 View都不处理事件

图5 View都不处理事件

上图表示所有的View都没有处理 ACTION_DOWN事件,那么就没有必要把后面的事件(ACTION_MOVE/ACTION_UP)再往下发送了。
这样后面来的ACTION_MOVE和ACTION_UP都将不会处理了。
具体的实现是在ViewGroup.dispatchTouchEvent, 通过判断mFirstTouchTarget是否为空来决定是否还继续往子view发送。 因为如果有子view consume了ACTION_DOWN事件的话,那么 mFirstTouchTarget将不会为NULL.

3.2 子View处理事件

图6 子view处理事件

View里因为设置了 onClickListener(), 这样就导致 View是 clickable (或者可以直接在xml里加上android:clickable=”true”),即可点击,那么View.onTouchEvent就会永远返回 True, 代表View consume了该事件。
注意:只要View consume了该事件,那么该事件既不会往下传(不会传给子view),也不会往上传(后面Activity/ViewGroup 的 onTouchEvent将不会再调用)。

3.3 ViewGroup拦截

图7 ViewGroup拦截事件

onInterceptTouchEvent 是拦截的意思,它是拦截事件不往下传(不会再传给子View),但是会将事件往上传(相关的 Activity/ViewGroup 会调用 onTouchEvent).
上图 onInterceptTouchEvent拦截了,所以不会再将ACTION_DOWN往View里传了,直接返回到 onTouchEvent. 因为ACTION_DOWN没有被任何View consume, 所以也没必要继续发送 ACTION_MOVE/ACTION_UP事件了。

3.4 ViewGroup consume事件

图8 ViewGroup consume事件

上图是ViewGroup的 onTouchEvent consume了事件,那么说明是ViewGroup需要处理事件,所以将后续的 ACTION_MOVE/ACTION_UP直接发送给ViewGroup处理了,就不再需要往子View传递事件了。

四、总结

onTouchEvent 返回值表示是否 consume该事件,它的传递方向是往上传递,View -> ViewGroup->Activity. 但是一旦某个View或ViewGroup的onTouchEvent返回True, 就不会再往上传递了。

onInterceptTouchEvent 是ViewGroup特有的,表示是否拦截事件往下(子View)传递,Activity->ViewGroup->View, 如果ViewGroup拦截了事件,那么会直接调用ViewGroup 的onTouchEvent。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容