美文网首页
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