美文网首页Android开发社区Android知识Android开发
Android高级UI系列(1)-- setContentVie

Android高级UI系列(1)-- setContentVie

作者: lllerry | 来源:发表于2017-05-09 11:16 被阅读311次

    想要学好自定义View, 就要知道Android是如何将View显示到屏幕上的, 我们都知道我们创建Activity,设置setContentView()后, 我们的布局就显示在了我们的屏幕上, 今天我们就来看看setContentView()到底做了什么神奇的事情。

    在开始之前, 我们要带着疑问去看源码。

    疑问:

    • setContentView()到底做了什么, 为什么调用后就可以先出我们想要的布局页面?
    • PhoneWindow是什么东西?Window和它是什么关系?
    • DecorView是干什么用的?和我们的布局有什么关系?
    • 为什么requesetFeature()要在setContentView()之前调用?

    相信大家在做开发的时候都会遇到这些疑问。

    Activity的setContentView

    注: 我们先讲activity中的setContentView()

    我们现在就点进setConentView()一探究竟。我们点进去一看

    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
    

    它调用的是getWindow()的setContentView(), 那这个Window是什么呢。我们可以看Window类的注释。这里解释下注释:Window就是显示View的顶层的窗口, 并包含一些行为的封装。 这个Window的实例要作为一个顶级View添加到WindowManager中, 它提供一些背景, 标题栏, 默认的key事件的一些处理。

    /**
     * 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的实现类只有一个PhoneWindow, 因此, 刚才的getWindow().setContentView(layoutResID)其实调用的是PhoneWindow的setContentView()。好,我们现在去PhoneWindow的setContentView()看看。

    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.
            //首先他会对mContentParent判空
            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);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    上面的代码,我们一句一句看, 首先它会对mContentParent进行判空, 那这个mContentParent是什么呢? 它是一个ViewGroup, 也就是它是一个容器,那么通过它的方法名, 我们也可以猜到, 它是一个现实内容的容器, 也就是我们的Activity要显示的内容,就会包到这个容器中。我们可以看这个mContentParent的注释。 它说这个mContentParent可以是DeocrView或者是它的子类。

      // 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.
        ViewGroup mContentParent;
    

    在PhoneWindow类中我们再看DecorView的注释。说这是window的顶层view,包含了window的装饰。那这个DecorView是什么呢, 我们点这个类一看它就是一个FrameLayout.

    // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;
    

    至此, 我们大体上可以知道我们的Activity显示的是什么了。首先最外面是一个PhoneWindow, PhoneWindow里面包含了一个DecorView。

    好, 我们继续往下看PhoneWindow的setContentView()。我们会看到这么一句话

     mLayoutInflater.inflate(layoutResID, mContentParent);
    

    上面的代码把我们的XML布局添加到了mContentParent的容器中。我们暂且先这样猜测一下。

    那我们DecorView是如何初始化的呢?那我们要看它的installDecor()方法了。我们点击去看。代码有点长, 我先贴上来。

    private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                mDecor = generateDecor(-1);
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
    
                // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
                mDecor.makeOptionalFitsSystemWindows();
    
                final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                        R.id.decor_content_parent);
    
                if (decorContentParent != null) {
                    mDecorContentParent = decorContentParent;
                    mDecorContentParent.setWindowCallback(getCallback());
                    if (mDecorContentParent.getTitle() == null) {
                        mDecorContentParent.setWindowTitle(mTitle);
                    }
    
                    final int localFeatures = getLocalFeatures();
                    for (int i = 0; i < FEATURE_MAX; i++) {
                        if ((localFeatures & (1 << i)) != 0) {
                            mDecorContentParent.initFeature(i);
                        }
                    }
    
                    mDecorContentParent.setUiOptions(mUiOptions);
    
                    if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
                            (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
                        mDecorContentParent.setIcon(mIconRes);
                    } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
                            mIconRes == 0 && !mDecorContentParent.hasIcon()) {
                        mDecorContentParent.setIcon(
                                getContext().getPackageManager().getDefaultActivityIcon());
                        mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
                    }
                    if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
                            (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
                        mDecorContentParent.setLogo(mLogoRes);
                    }
    
                    // Invalidate if the panel menu hasn't been created before this.
                    // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
                    // being called in the middle of onCreate or similar.
                    // A pending invalidation will typically be resolved before the posted message
                    // would run normally in order to satisfy instance state restoration.
                    PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
                    if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
                        invalidatePanelMenu(FEATURE_ACTION_BAR);
                    }
                } else {
                    mTitleView = (TextView) findViewById(R.id.title);
                    if (mTitleView != null) {
                        if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                            final View titleContainer = findViewById(R.id.title_container);
                            if (titleContainer != null) {
                                titleContainer.setVisibility(View.GONE);
                            } else {
                                mTitleView.setVisibility(View.GONE);
                            }
                            mContentParent.setForeground(null);
                        } else {
                            mTitleView.setText(mTitle);
                        }
                    }
                }
    
                if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                    mDecor.setBackgroundFallback(mBackgroundFallbackResource);
                }
    
                // Only inflate or create a new TransitionManager if the caller hasn't
                // already set a custom one.
                if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
                    if (mTransitionManager == null) {
                        final int transitionRes = getWindowStyle().getResourceId(
                                R.styleable.Window_windowContentTransitionManager,
                                0);
                        if (transitionRes != 0) {
                            final TransitionInflater inflater = TransitionInflater.from(getContext());
                            mTransitionManager = inflater.inflateTransitionManager(transitionRes,
                                    mContentParent);
                        } else {
                            mTransitionManager = new TransitionManager();
                        }
                    }
    
                    mEnterTransition = getTransition(mEnterTransition, null,
                            R.styleable.Window_windowEnterTransition);
                    mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowReturnTransition);
                    mExitTransition = getTransition(mExitTransition, null,
                            R.styleable.Window_windowExitTransition);
                    mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowReenterTransition);
                    mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
                            R.styleable.Window_windowSharedElementEnterTransition);
                    mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
                            USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowSharedElementReturnTransition);
                    mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
                            R.styleable.Window_windowSharedElementExitTransition);
                    mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
                            USE_DEFAULT_TRANSITION,
                            R.styleable.Window_windowSharedElementReenterTransition);
                    if (mAllowEnterTransitionOverlap == null) {
                        mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
                                R.styleable.Window_windowAllowEnterTransitionOverlap, true);
                    }
                    if (mAllowReturnTransitionOverlap == null) {
                        mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
                                R.styleable.Window_windowAllowReturnTransitionOverlap, true);
                    }
                    if (mBackgroundFadeDurationMillis < 0) {
                        mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
                                R.styleable.Window_windowTransitionBackgroundFadeDuration,
                                DEFAULT_BACKGROUND_FADE_DURATION_MS);
                    }
                    if (mSharedElementsUseOverlay == null) {
                        mSharedElementsUseOverlay = getWindowStyle().getBoolean(
                                R.styleable.Window_windowSharedElementsUseOverlay, true);
                    }
                }
            }
        }
    

    开始解释: 首先当Decor为空的时候它先会generateDecor(-1)生成一个Decor, 点进去看一下。这个方法就是new 了一个DeocrView ,代码我就不贴上来了。我们继续往下看,它把Decor生成之后, 它会把它丢到generateLayout(mDecor)方法中, 并且返回值赋给我们的ContentParent。 在这个地方, 我们来看一下generateLayout()方法里面做了些什么事情。这个方法非常的长,我就不贴上来了,我们挑重点的说, 其他的自行查看源码。

    首先, 它会去拿我们的WindowStyle, 我们在XML设置的一些window相关style属性, 就是在这里加进来的。我们往后看, 比如, 它会判断window是不是floating的, 像Dialog这类的就是floating的。就是浮着的类型。我们在往后走, 会有一个我们熟悉的windowNoTitle的style, 它会调用requestFeature()方法。还有好多判断,大家可以自行去看。

    看到这里我们开头的一个问题到这里已经渐渐明朗了, 为什么requesetFeature()要在setContentView()之前调用? 因为在我们setContentView()的时候, 就要根据不同的feature, 去加载不同的布局文件。这个布局文件, 就是将要加载到我们的Decor里面的。代码好多 就不贴上来了。

     // Inflate the window decor.
    
            int layoutResource;
            int features = getLocalFeatures();
            ...
             if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
            } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            ...
            }
    

    我们就挑个简单的布局(R.layout.screen_simple)看一下。

    <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>
    

    其中id为content的就是要显示我们真正布局的地方, 我们来验证下。继续看源码,它会调用

     mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    

    这个就是加载我们的布局, 同时findViewById,找到了我们刚才看的framelayout, 我们最后可以发现它返回的就是这个这个framelayout.

    return contentParent;
    

    那么讲到这里, 也就是说我们的mContentParent它最终代表的就是我们的DecorView布局里面id为content的FrameLayout.而我们刚才说了在PhoneWindow的setContentView()中mContentParent会最终加载我们的布局。也就是说, 我们Activity的布局最终显示在id为content的FrameLayout下面的。

    如果有同学看不明白, 我这里放一张图, 能更加清晰的理解。

    1_Activity加载UI-类图关系和视图结构.png

    而我们开头的有些疑惑都可以解开了:

    PhoneWindow是什么东西?Window和它是什么关系? 我们每个Activity有一个这样的window, 这个window就是用来显示我们的UI的。

    DecorView是干什么用的?和我们的布局有什么关系? DecorView是我们Window顶层的一个view, 我们自己的布局是加载到DecorView中的id为content的FrameLayout中的。

    讲到这里, 我们仅仅只是把DecorView的视图结构给分析了一把, 那么我们自己的布局到底是怎么从我们的XML生成并且添加到DecorView上面的呢?因此,我们就要来看下LayoutInflater, 它是怎么把XML添加到DecorView中的?

    当然我们继续要带着疑问去看源码。

    相信我们平时在写我们的布局的时候, 会用到我们的include标签, 会用到我们的merge标签。你们会不会有这样的疑问:

    • include标签为什么不能作为我们的XML的根节点吗?
    • merge标签为什么它要作为XML资源布局的根节点?

    接下来我们就带着疑问去看一下源码。其实我们所有的疑惑都可以在源码中找到答案。所以在开发过程中,不能有一知半解, 一定要花时间去探究一下。

    我们的PhoneWindow的setContentView()源码并没有分析完, 因此我们继续看, 这时候就要看它的inflate方法了。

      mLayoutInflater.inflate(layoutResID, mContentParent);
    

    刚才我们猜测过, 它这里肯定是把我们XML解析成view添加到mContentParent中去, 我们点进去看一下,它调用的是mLayoutInflater.inflate(layoutResID, mContentParent,true)。

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
            return inflate(resource, root, root != null);
        }
        
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
            final Resources res = getContext().getResources();
            if (DEBUG) {
                Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                        + Integer.toHexString(resource) + ")");
            }
    
            final XmlResourceParser parser = res.getLayout(resource);
            try {
                return inflate(parser, root, attachToRoot);
            } finally {
                parser.close();
            }
        }
    

    首先它会去哪一个XML的解析器, 这个不细说了不是重点,我们继续到下一个inflate方法去,

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            synchronized (mConstructorArgs) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
    
                final Context inflaterContext = mContext;
                final AttributeSet attrs = Xml.asAttributeSet(parser);
                Context lastContext = (Context) mConstructorArgs[0];
                mConstructorArgs[0] = inflaterContext;
                View result = root;
    
                try {
                    // Look for the root node.
                    int type;
                    while ((type = parser.next()) != XmlPullParser.START_TAG &&
                            type != XmlPullParser.END_DOCUMENT) {
                        // Empty
                    }
    
                    if (type != XmlPullParser.START_TAG) {
                        throw new InflateException(parser.getPositionDescription()
                                + ": No start tag found!");
                    }
    
                    final String name = parser.getName();
                    
                    if (DEBUG) {
                        System.out.println("**************************");
                        System.out.println("Creating root view: "
                                + name);
                        System.out.println("**************************");
                    }
    
                    if (TAG_MERGE.equals(name)) {
                        if (root == null || !attachToRoot) {
                            throw new InflateException("<merge /> can be used only with a valid "
                                    + "ViewGroup root and attachToRoot=true");
                        }
    
                        rInflate(parser, root, inflaterContext, attrs, false);
                    } else {
                        // Temp is the root view that was found in the xml
                        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
                        ViewGroup.LayoutParams params = null;
    
                        if (root != null) {
                            if (DEBUG) {
                                System.out.println("Creating params from root: " +
                                        root);
                            }
                            // Create layout params that match root, if supplied
                            params = root.generateLayoutParams(attrs);
                            if (!attachToRoot) {
                                // Set the layout params for temp if we are not
                                // attaching. (If we are, we use addView, below)
                                temp.setLayoutParams(params);
                            }
                        }
    
                        if (DEBUG) {
                            System.out.println("-----> start inflating children");
                        }
    
                        // Inflate all children under temp against its context.
                        rInflateChildren(parser, temp, attrs, true);
    
                        if (DEBUG) {
                            System.out.println("-----> done inflating children");
                        }
    
                        // We are supposed to attach all the views we found (int temp)
                        // to root. Do that now.
                        if (root != null && attachToRoot) {
                            root.addView(temp, params);
                        }
    
                        // Decide whether to return the root that was passed in or the
                        // top view found in xml.
                        if (root == null || !attachToRoot) {
                            result = temp;
                        }
                    }
    
                } catch (XmlPullParserException e) {
                    final InflateException ie = new InflateException(e.getMessage(), e);
                    ie.setStackTrace(EMPTY_STACK_TRACE);
                    throw ie;
                } catch (Exception e) {
                    final InflateException ie = new InflateException(parser.getPositionDescription()
                            + ": " + e.getMessage(), e);
                    ie.setStackTrace(EMPTY_STACK_TRACE);
                    throw ie;
                } finally {
                    // Don't retain static reference on context.
                    mConstructorArgs[0] = lastContext;
                    mConstructorArgs[1] = null;
    
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
    
                return result;
            }
        }
    

    首先,回去拿attrs, xml中设置的一些属性, 往下走, 它会把我们这支的contentParent赋值给result, 它会去找根节点, 它会去判断是不是merge标签, 如果root为空, 或者attachToRoot我false, 就会抛异常,这也就是为什么merge要作为顶层标签。接着他会调用rInflate(parser, root, inflaterContext, attrs, false);这个放着先待会再讲。

    当是一些常用的标签的时候, 它首先会final View temp = createViewFromTag(root, name, inflaterContext, attrs);去生成常用布局,假设我们这个标签是LinearLayout, 接着会调用params = root.generateLayoutParams(attrs); 也就是说, 我们XML中的那些layout属性,都是调用对应的父节点的root.generateLayoutParams去解析属性,生成layoutParams, 添加上去的。

    当把我们的根节点解析完成后, 会去解析子节点

     // Inflate all children under temp against its context.
                        rInflateChildren(parser, temp, attrs, true);
    

    这个方法还是调用了void rInflate(XmlPullParser parser, View parent, Context context,AttributeSet attrs, boolean finishInflate)方法。这个方法在是merge和不是merage的时候都会调用, 只是最后的一个参数传的不同, 为merge标签的时候,最后参数为false, 其他情况为true.这个方法内部也是遍历循环解析.在这个方法里面我们就能回答刚才提出的两个问题了。

    void rInflate(XmlPullParser parser, View parent, Context context,
                AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    
            final int depth = parser.getDepth();
            int type;
    
            while (((type = parser.next()) != XmlPullParser.END_TAG ||
                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
    
                if (type != XmlPullParser.START_TAG) {
                    continue;
                }
    
                final String name = parser.getName();
                
                if (TAG_REQUEST_FOCUS.equals(name)) {
                    parseRequestFocus(parser, parent);
                } else if (TAG_TAG.equals(name)) {
                    parseViewTag(parser, parent, attrs);
                } else if (TAG_INCLUDE.equals(name)) {
                    if (parser.getDepth() == 0) {
                        throw new InflateException("<include /> cannot be the root element");
                    }
                    parseInclude(parser, context, parent, attrs);
                } else if (TAG_MERGE.equals(name)) {
                    throw new InflateException("<merge /> must be the root element");
                } else {
                    final View view = createViewFromTag(parent, name, context, attrs);
                    final ViewGroup viewGroup = (ViewGroup) parent;
                    final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                    rInflateChildren(parser, view, attrs, true);
                    viewGroup.addView(view, params);
                }
            }
    
            if (finishInflate) {
                parent.onFinishInflate();
            }
        }
    

    到这里如果还没有明白的同学, 可以结合下图,可以更加容易的理解整个流程。

    LayoutInflater.png

    讲到这里我们小结一下:

    每个Activity都有一个关联的Window对象, 用来描述应用程序窗口, 每一个窗口内部又包含一个DecorView对象, DecorView对象用来描述窗口的视图--xml布局。

    AppCompatActivity的setContentView

    上述是在activity下创建DecorView的过程。

    接下来我们要加个餐。我们用AS开发并不是继承Activity, 而是继承AppCompatActivity。所以他的setContentView()是谷歌工程做过优化的, 我们点进去看一下。

        public void setContentView(@LayoutRes int layoutResID) {
            getDelegate().setContentView(layoutResID);
        }
    

    一看名字, 就知道这是个代理。它这个代理就是代理了一些专门用来做兼容的一些类。getDelegate()获取一个AppCompatDelegate类, 这是一个抽象类, 它有好多实现, 这个自己点进去看, 很容易看明白。

    那我们这个setContentView()是在哪个类中实现的呢? 不卖关子了, 是AppCompatDelegateImplV9中实现的。我们就去这个类中看看。我们到这个类的setContentView()中去看

     @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            mOriginalWindowCallback.onContentChanged();
        }
    

    我们看第一行的方法。ensureSubDecor()里面显示调用了createSubDecor()这个里面会很熟悉。我们点进去看。这个方法和PhoneWindow的generateLayout()很像,你们发现了吗? 这个方法里, 它会去先获取一些AppCompatTheme属性,这里就能解释为什么我们在style里面不去使用AppCompatTheme的主题的话,程序就会闪退了。因为我们默认继承的是AppCompatActivity。它的setContentView()内部获取的主题是AppCompatTheme的。我们继续往下看,我们会看到这么一行代码。

     // Now let's make sure that the Window has installed its decor by retrieving it
            mWindow.getDecorView();
    

    这个类还是会去初始化decorView的, 我们点进去看它还是调用了installDecor()去生成DecorView。

    我们上面讲过PhoneWindow的generateLayout()里面是根据不同的style去加载不同的布局, 这个createSubDecor()也不例外, 我们看它后面的代码, 也是根据获取的style去加载不同的布局。

    我们挑一个简单的subDecor看看,R.layout.abc_screen_simple。

    <android.support.v7.widget.FitWindowsLinearLayout
    +    xmlns:android="http://schemas.android.com/apk/res/android"
    +    android:id="@+id/action_bar_root"
    +    android:layout_width="match_parent"
    +    android:layout_height="match_parent"
    +    android:orientation="vertical"
    +    android:fitsSystemWindows="true">
    +
    +    <android.support.v7.widget.ViewStubCompat
    +        android:id="@+id/action_mode_bar_stub"
    +        android:inflatedId="@+id/action_mode_bar"
    +        android:layout="@layout/abc_action_mode_bar"
    +        android:layout_width="match_parent"
    +        android:layout_height="wrap_content" />
    +
    +    <include layout="@layout/abc_screen_content_include" />
    +
    +</android.support.v7.widget.FitWindowsLinearLayout>
    

    里面的include就是一个framelayout

    <merge xmlns:android="http://schemas.android.com/apk/res/android">
    
        <android.support.v7.widget.ContentFrameLayout
                android:id="@id/action_bar_activity_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:foregroundGravity="fill_horizontal|top"
                android:foreground="?android:attr/windowContentOverlay" />
    
    </merge>
    

    所以它整个结构就是一个linearlayout里面一个viewstub和一个framelayout。为什么要这个搞? 我们接着看,等下你就明白了。这就是一个subDecor。

    我们继续看代码。首先会把id为action_bar_activity_content的这个Framelayout找到, 然后找到android.R.id.content, 然后谷歌工程师它把这个content的id抹掉, 赋值到这个Framelayout上去了。然后把这个subDecor设置到window的DecorView中的FrameLayout中去了。

    谷歌工程师为了做兼容真的是花了很多心思。看到这里应该都明白了吧,谷歌工程师有套了一层decorView进去,并把content的id名重新赋值给新添加的subDecor中的FrameLayout作为我们要设置内容的布局。

    谷歌这么做是为了不用改变我们自己的编程习惯,以前是叫content,现在还是叫content, 对开发者来说比较方便。

     final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                    R.id.action_bar_activity_content);
    
            final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
            ...
             // Change our content FrameLayout to use the android.R.id.content id.
                // Useful for fragments.
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
            // Now set the Window's content view with the decor
            mWindow.setContentView(subDecor);
    

    如果还没有理解的同学,我再放上一张图用来更好的理解一下兼容的setContentView吧。

    AppCompatActivity的View布局结构.png

    大家也可以自己用AS去自己看看view层级结构。

    好了, 关于setContentView也已经讲述完毕了,下一节我们讲解DecorView是如何添加到Window上的,敬请期待。

    参考

    课后笔记

    本文为猫舍原创作品,转载请注明原文出处: http://imlerry.com/2017/05/08/02.Android%E9%AB%98%E7%BA%A7UI%E7%B3%BB%E5%88%97%EF%BC%881%EF%BC%89-setContentView-%E8%AF%A6%E8%A7%A3/

    相关文章

      网友评论

        本文标题:Android高级UI系列(1)-- setContentVie

        本文链接:https://www.haomeiwen.com/subject/inubtxtx.html