美文网首页
Android UI | 从setContentView 了解V

Android UI | 从setContentView 了解V

作者: stamSuper | 来源:发表于2021-11-12 18:13 被阅读0次

    setContentView 方法如下:

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

    getWindow() , 实际上就是PhoneWindow , 继续查看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.
        if (mContentParent == null) {
           ///  这里初始化 decor 
            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;
    }
    

    继续查看如何初始化 decor

    private void installDecor() {
      if (mDecor == null) {
           // 初始化decor
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            // 设置DecorView的window 为PhoneWindow对象
            mDecor.setWindow(this);
        }
      if (mContentParent == null) {
          // 生成内容根节点
          mContentParent = generateLayout(mDecor);
      }
      // ...其他代码忽略
    }
    

    先查看 generateDecor

    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
    

    创建DecorView 对象

    在看看 generateLayout(decorView)

    protected ViewGroup generateLayout(DecorView decor) {
        // .....
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            // 这里的代码比较眼熟,设置主体的时候会用到
            requestFeature(FEATURE_ACTION_BAR);
        }
      
        /// ...
      
        // Inflate the window decor.
        int layoutResource;
        // 这里获取一个features , 根据这个值获取一个布局文件
        int features = getLocalFeatures();
        //  那最后一个布局查看
        if(...){...}
        else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        // 这个方法也很重要, 通过DecorView 加载上面获取到的布局文件
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        // 这里是拿到哪个id 为@android:id/content 的对象
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        // ... 
        // 最后返回这个content,那由此我们可以知道 PhoneWindow的mContentParent 对象其实就是id为@android:id/content 的FrameLayout对象
        return contentParent;
    }
    

    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>
    

    这里就是一个普通的LinearLayout , 重点是里面有个FrameLayout而且id为@android:id/content , 这个就是我们自定义开发布局都会放在这个FrameLayout 里面

    现在再回过头来看看setContentView 方法里面的 mLayoutInflater.inflate(layoutResID, mContentParent);

    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();
        // 这个根据名字可以猜出,是预加载布局文件
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        // 这个是获取xml资源解析器
        XmlResourceParser parser = res.getLayout(resource);
        try {
            // 重要的是这个方法
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
    

    继续查看 inflate(parser, root, attachToRoot);

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            // 拿到所有的属性
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            // 这个result 就算 decorView
            View result = root;
             if (TAG_MERGE.equals(name)) {
                   //  这里判断了是否是 <merge>解点, 不能是最外层的根节点
                  if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
                  // 继续通过递归调用的方式,加载出所有的子节点,并放在root节点,即decorView 下面
                  rInflate(parser, root, inflaterContext, attrs, false);
              }
        }
    }
    
    /// finishInflate 是否停止解析
    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    
        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;
    
        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)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                // 解析include 标签,不能是根节点标签
                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);
                // 把解析出来的子节点,放在上级节点view[]里面
                viewGroup.addView(view, params);
            }
        }
    
        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }
        // 停止解析
        if (finishInflate) {
            parent.onFinishInflate();
        }
    }
    

    通过上面一层一层的解析,就能把我们自己定义的布局,解析出来, 并放在mContentParent里面, 这个就是@android:id/content的FrameLayout。

    到这一步,好像还缺点啥,还没看到 mContentParent , 是如何放进DecorView 里面的。再回头看看PhoneWindow类里面的 generateLayout方法,刚才说的有一个加载资源的方法,我们漏看了
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    这个方法,从表面上看, 是拿到了某个主题的资源文件,并解析

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        // ... 
        mDecorCaptionView = createDecorCaptionView(inflater);
        // 这一步就是解析系统主题中的某个带有@android:id/content的哪个布局
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {
    
            // Put it below the color views.
            // 在这一步,把mContentParent 添加到 decorView 的第一个元素里面了
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }
    

    到这一步,基本上都清晰了

    image.png

    相关文章

      网友评论

          本文标题:Android UI | 从setContentView 了解V

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