本想分析一下触摸事件的分发响应机制,但是发现分发事件的方法在Activity、View以及ViewGroup中各自存在 ,如图1表所示
图一
这样的话又牵扯到了三者之间的关系,那索性先理清楚Activity与另外两者的关系 ,在去分析触摸事件比较好。
什么是Activity 、View 、 Window?
Activity:是Android 四大组件之一, 是存放View对象的容器,也是我们界面的载体,可以用来展示一个界面。它有一个SetContentView()方法 ,可以将我们定义的布局设置到界面上。
View:就是一个个视图的对象,实现了KeyEvent.Callback和Drawable.Callback。
Window:是一个抽象类,是一个顶层的窗口,它的唯一实例是PhoneWindow它提供标准的用户界面策略,如背景、标题、区域,默认按键处理等。
分析下三者之间的关系吧
View包含很多,TextView 、Imageview 、Listview 、 Button..就是一个一个展示不同图形的对象。我们可以把view通过xml布局,或者通过new View(),然后通过addview方法或动态或静态添加到Activity的布局上。我们都知道我们定义了layout布局,通过SetContentView就可以设置到Activity上,而Activity中的SetContentView()方法,又调用了Window的SetContentView方法,也就是View通过Activity最终添加到了Window上面。
那我们今天就看一下这个方法到底如何把layout布局加载进去,到底加载到哪里去了?
- /**
- * Set the activity content from a layout resource. The resource will be
- * inflated, adding all top-level views to the activity.
- *
- * @param layoutResID Resource ID to be inflated.
- *
- * @see #setContentView(android.view.View)
- * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
- */
- public void setContentView(@LayoutRes int layoutResID) {
- getWindow().setContentView(layoutResID);
- initWindowDecorActionBar();
- }
- 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) {
- attachBaseContext(context);
- mFragments.attachHost(null /*parent*/);
- mWindow = new PhoneWindow(this);
- mWindow.setCallback(this);
- mWindow.setOnWindowDismissedCallback(this);
- mWindow.getLayoutInflater().setPrivateFactory(this);
- if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
- mWindow.setSoftInputMode(info.softInputMode);
- }
- if (info.uiOptions != 0) {
- mWindow.setUiOptions(info.uiOptions);
- }
- mUiThread = Thread.currentThread();
- mMainThread = aThread;
- mInstrumentation = instr;
- mToken = token;
- mIdent = ident;
- mApplication = application;
- mIntent = intent;
- mReferrer = referrer;
- mComponent = intent.getComponent();
- mActivityInfo = info;
- mTitle = title;
- mParent = parent;
- mEmbeddedID = id;
- mLastNonConfigurationInstances = lastNonConfigurationInstances;
- if (voiceInteractor != null) {
- if (lastNonConfigurationInstances != null) {
- mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
- } else {
- mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
- Looper.myLooper());
- }
- }
- mWindow.setWindowManager(
- (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
- mToken, mComponent.flattenToString(),
- (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
- if (mParent != null) {
- mWindow.setContainer(mParent.getWindow());
- }
- mWindowManager = mWindow.getWindowManager();
- mCurrentConfig = config;
- }
在第11行初始化mWindow对象,这个对象是window 接口的实现类 PhoneWindow 的实例。
那我们看一下PhoneWindow方法中的SetContentView方法代码如下所示:
- @Override
- 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 {
- mLayoutInflater.inflate(layoutResID, mContentParent);
- }
- final Callback cb = getCallback();
- if (cb != null && !isDestroyed()) {
- cb.onContentChanged();
- }
- }
这里我们只看下第6和第11行,首先判断mContentParent是不是 null,我们先搞明白mContentParent是什么东西?
- // This is the top-level view of the window, containing the window decor.
- private DecorView mDecor;
- // This is the view in which the window contents are placed. It is either
- // mDecor itself, or a child of mDecor where the contents go.
- private ViewGroup mContentParent;
OK,搞明白了mContentParent是一个ViewGroup对象 ,那我们继续往下看
如果是installDecor()不用想我们也知道这个方法肯定是初始化了mContentParent,一起看下是不是我们想的那样吧。
- private void installDecor() {
- if (mDecor == null) {
- mDecor = generateDecor();
- mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
- mDecor.setIsRootNamespace(true);
- if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
- mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
- }
- }
- if (mContentParent == null) {
- mContentParent = generateLayout(mDecor);
- // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
- //.....
- //...... 省略若干代码
- }
- }
这里先判断 mDecor是不是null,如果是,初始化mDecor,然后判断mContentParent是不是null,如果是,通过mDecor去初始化 mContentParent对象。 对吧,跟我们想的一样是去初始化。
OK ,这里创建出了mContentParent对象,我们接着看PhoneWindow的SetContentView方法的第11行,这里先进行了判断,具体判断 我们先不关心,我们继续往下执行在看第12行或者17行,我们就清楚了我们在Activity中设置的layoutid 在这里加载到了mContentParent 上面。也就是所有的所有的View 对象都是加载到了mContentParent对象上面,而我们前面知道mContentParent是根据DecorView而来的,这样我们就清楚了Activity与Window以及View的关系,这里用图2 表示一下他们的关系。
看图识关系
图二
对着这张图,打个比喻来帮助理解。
Activity就像是一扇贴着窗花的窗口,Window就想上窗口上面的玻璃,而View对象就像一个个贴在玻璃上的窗花。
最后的Activity与Window View的关联在画一个图3:
图三
总结起来说就是 Activity会调用PhoneWindow的setContentView()将layout布局添加到DecorView上,而此时的DecorView就是那个最底层的View。然后通过LayoutInflater.infalte()方法加载布局生成View对象并通过addView()方法添加到Window上,(一层一层的叠加到Window上)所以,Activity其实不是显示视图,Window才是真正的显示视图。
注:一个Activity构造的时候只能初始化一个Window(PhoneWindow),另外这个PhoneWindow有一个View容器 mContentParent,这个
View容器是一个ViewGroup,是最初始的跟视图,然后通过addView方法将View一个个层叠到mContentParent上,这些层叠的View最终放在Window这个载体上面。