setContentView() 办法在咱们 onCreate() 办法的第二行,咱们常常运用这段代码,但是似乎并没有真实的了解过这个办法,今日咱们来一同看看它内部是怎样完结的吧。

setContentView() 的目的便是将咱们自定义的 xml 文件解析烘托到屏幕上进行显现。

setContentView() 流程

setContentView() 在承继 AcitvityAppCompatAcitivity 是有些稍微差异的,AppCompatActivity 下的 setContentView() 更具兼容性,能适配一切的安卓版本。

咱们先来说说承继自 Activity 下的 setContentView()

由于流程比较复杂,为了便于了解我只展现中心代码,其它的一些if判别或许反常处理就省略了,咱们看懂整个流程能够自己去翻翻源码看看细节。本篇文章是在 API 31 基础下创作的。

承继 Activity 下的 setContentView()

Activity 在执行 attach() 办法时会 new PhoneWindow(),咱们知道在这儿能够得到 phoneWindow 的实例。咱们点进 MainActivity 中的 setContentView() 会发现其实内部调用的是 phoneWindow.setContentView()

public void setContentView(@LayoutRes int layoutResID) {
    //调用 phoneWindow.setContentView()
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

这一个办法的首要目的是创立一个 DecorView 以及拿到 content。这两个东西是什么?别急,咱们慢慢往下讲。

咱们继续看 phoneWindow 中的 setContentView()

public void setContentView(int layoutResID) {
    //中心代码
    if (mContentParent == null) {
        installDecor();
    ......
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        //将自己的 xml 烘托到mContentParent中去
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    //这儿比较重要,咱们调用 setContentView 后 mContentParentExplicitlySet 会变成 true
    //requestWindowFeature(Window.FEATURE_ACTION_BAR)中的第一行代码就会检测
    //mContentParentExplicitlySet,假如是 true 就抛出反常,因而咱们应该在 setcontentView
    //之前调用 requestWindowFeature
    //google 这样做的目的首要是由于 setContentView 会用到 requestWindowFeature
    mContentParentExplicitlySet = true;
}

在这儿咱们发现它实践调用了 installDecor(),这个办法有两行非常重要的代码 generateDecor()generateLayout。这两个办法便是真实创立 DecirView 和 拿到content 的实践代码。

到这儿咱们知道,MainActivity 中调用的 setContentView() 实践上是调用的 PhoneWindow 中的 setContentView() ,而这儿面又经过 installDecor() 帮咱们干了两件事:创立一个 DecorView 以及拿到 content。到这儿咱们将剖析流程使命放一放,咱们先讲讲 DecorViewcontent

DecorView 与 content

它是 FrameLayout 的子类,它能够被认为是 Android 视图树的根节点视图。用来放一些体系中 xml 文件,而 content 便是体系 xml 文件中 ViewGroupid,全名叫作com.android.internal.R.id.content。咱们自己定义的 xml,比方 activity_mian.xml 便是放在这个 ViewGroup 中的。


接下来咱们继续剖析 setContentView() 流程。

前面说到 installDecor() 有两行很重要的代码, generateDecor()generateLayout

generateDecor() 中的代码很简略,直接 new DecorView() ,至此 installDecor 的第一步就完结了。

protected DecorView generateDecor(int featureId) {
    Context context;
    //这儿便是拿到 context
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, this);
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    //创立 DecorView 在这儿完结
    return new DecorView(context, featureId, this, getAttributes());
}

接着看看 generateLayout()办法:

protected ViewGroup generateLayout(DecorView decor) {
    /**
    * 这个办法内容许多,在这之前的代码便是加载当前体系主题的布局和特点
    */
    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        layoutResource = R.layout.screen_progress;
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        layoutResource = R.layout.screen_simple;
    }
    /**
    * 上面这部分代码是依据不同状况拿到对应的体系 xml 布局文件
    * 一会讲到的 subDecorView 也有相似的代码
    */
    mDecor.startChanging();
    //重要代码,将 xml 文件加载到 DecorView 中,比方依据条件咱们拿到的xml文件是
    //R.layout.screen_simple,内部就会调用 inflate() 和 addView() 将它加载到 DecorView 中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //重要代码,将 xml 中的 ViewGroup 经过 findViewById 保存到 contentParent 变量中
    //假如xml文件是 R.layout.screen_simple,那么 ViewGroup 便是 FrameLayout
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    //拿到这个content就回来出去
    return contentParent;
}

至此咱们现已完结了两个进程,即创立 DecorView 以及拿到对应的 content 。现在咱们只需要将 xml 布局文件烘托到 content 中就功德圆满了。不过烘托这一点等我讲完 承继自 AppCompatActivity 后一同说,由于不管承继自谁,烘托部分的代码都是相同的。

R.layout.screen_simple.xml 是体系中最简略的布局文件,它首要有上下两部分,ViewStubFrameLayout。这个 FrameLayoutid 便是前面说到的 com.android.internal.R.id.content。因而假如咱们将 activity_main.xml 文件烘托加载到屏幕上便是这样的:

一篇文章带你深刻认识不一样的setContentView()

承继 AppCompatActivity 下的 setContentView()

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

能够看出AppCompatActivity 中的 setContentView() 实践调用的是 AppCompatDelegateImpl.setContentView()。它内部是这样的:

public void setContentView(int resId) {
    //重要办法
    ensureSubDecor();
    //与 承继自 Activity 不同,它是从 subDecor 中拿 content 的
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    //避免content中有其它的 view 视图,事前移除掉
    contentParent.removeAllViews();
    //将咱们自己的 xml 烘托到 content 中去
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

subDecor 是个什么呢?它和 decorView 相似,也是一个 ViewGroupsubDecorView 中也有体系定义的 xml 布局文件,这布局文件里同样也有一个 ViewGroup 用来装咱们自定义的 xml 布局文件,相关于承继 Activity ,实践上便是进行了一层封装,原来是直接将 xml 放到 com.android.internal.R.id.content,而现在是将它放到 subDecor 的某个 ViewGroup 中,再将 subDecor 放到原来那个 id 叫作 com.android.internal.R.id.contentViewGroup 中。

画个图就像这样:

一篇文章带你深刻认识不一样的setContentView()

接下里咱们继续点进 ensureSubDecor() 去看。

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        //假如没有创立 subDecor 就调用 createSubDecor 去创立
        mSubDecor = createSubDecor();
        ...
}

再进入 createSubDecor() ,它长这样:

private ViewGroup createSubDecor() {
    ...
    //从 Activity 中拿 PhoneWindow
    ensureWindow();
    //内部调用 installDecor() 创立 decorView 并得到 content,详情见承继 Activity 中的 installDecor()
    mWindow.getDecorView();
    //得到 SubDecorView,在这之前会依据不同状况调用 inflate() 将 xml 解析出来
    //假设依据状况拿到的是 abc_screen_simple.xml
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
        R.id.action_bar_activity_content);
    //经过findViewById() 得到 DecorView,
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    //将原始的 content id 置为 NO_ID
    windowContentView.setId(View.NO_ID);
    //将 subDecerView 中的abc_screen_simple.xml中的一个 ViewGroup id 为 R.id.action_bar_activity_content 
    //设置为 android.R.id.content
    contentView.setId(android.R.id.content);
    //将 subDecorView 加到 DecorView 中的 ViewGroup 中去(id 为 View.NO_ID)
    mWindow.setContentView(subDecor);
    ...
}

它首要干了这么几件事:

1.ensureWindow()Activity 中拿到 PhoneWindow

2.经过 findViewById() 的方式得到 SubDecorView,将其保存到 contentView 中。

3.getDecorView() 内部调用 installDecor() 创立 decorView 以及拿到 content。将 decorView 保存在 windowContentView中。

4.创立好 subDecorView将它保存到 windowContentView 中。

5.windowContentView.setId(View.NO_ID)decorView 中的原来那个 idandroid.R.id.contentViewGroupid 设为 View.NO_ID

6.将 subDecorView 中的某个 ViewGroup(拿到体系的 xml 不同,ViewGroup 就不相同) 的 id 设为 android.R.id.content

7.将有布局的 subDecorView 加载到 decorView 上。

诶,installDecor() 这个办法是不是很眼熟?没错这个办法咱们在承继自 Activity 时,剖析流程中也说到过,便是用来创立 decorView 以及拿到 content 的。

subDecorView 也和 decorView 相同,会依据不同的状况挑选体系对应的 xml 布局文件,这个布局文件也是上下结构。以 abc_screen_simple.xml举例,上面是一个 ViewSubCompat ,下面是一个 id action_bar_activity_contentViewGroup,咱们自己的 xml 文件便是加载烘托到这儿面去。

一篇文章带你深刻认识不一样的setContentView()

由于篇幅过多可能导致咱们阅读效率低,因而关于上面说到的 screen_simple.xmlabc_screen_simple.xml 的代码我就不贴了,直接看我画的图吧。确有需要的,请自行去 AS 上查看。

至此,第一大步搞定。

烘托进程

整个烘托进程大约分为两部分,对 根view 的创立以及 子view 的创立。整个进程便是调用 inflate() 去解析 xml 文件,然后创立对应的 view,再加载到屏幕上显现。

//承继 Activity 或许 AppCompatActivity 首先都会调用这个办法进行烘托
LayoutInflater.from(mContext).inflate(resId, contentParent);

这个办法的内部又会调用三参的 inflate()

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 {
        //重要办法,继续调用 inflate
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

这个办法中先对 xml 文件进行解析,随后将 parser 作为参数传递进去,咱们继续点进去:

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];
        //将传递过来的 ViewGroup 用 result 保存
        View result = root;
        try {
            advanceToRootNode(parser);
            final String name = parser.getName();
            ...
            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 {
                //1.重要办法,创立根view
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ViewGroup.LayoutParams params = null;
                if (root != null) {
                    //依据 root 创立布局参数
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        //假如 attachRoot 为 false,就讲 params 布局参数给它
                        temp.setLayoutParams(params);
                    }
                }
                //2.重要办法,加载烘托一切的子view
                rInflateChildren(parser, temp, attrs, true);
                ...
        return result;
    }
}

咱们先来看看 createViewFromTag() 办法是怎样创立 根view 的。

这个四参的 createViewFromTag(),会紧接着调用五参的 createViewFromTag()

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }
    try {
        View view = tryCreateView(parent, name, context, attrs);
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            //重要代码,会依据称号中是否有 . 来判别改是否是 sdk 中定义的view
            try {
                if (-1 == name.indexOf('.')) {
                    //比方 TextView 没有 . 是 sdk 中的,就会走这儿
                    //内部会做一个全类名拼接
                    //终究也会经过 createView() 创立 view
                    view = onCreateView(context, parent, name, attrs);
                } else {
                    //androidx.constraintlayout.widget.ConstraintLayout,是google自定义的view
                    //会走这儿
                    //依据全类名 内部经过反射创立 view
                    view = createView(context, name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        return view;
    }
}

咱们现在来看看 onCreateView()createView() 办法。这儿的 onCreateView() 实践上会调用两参的 PhoneLayoutInflater.onCreateView(name, attrs)

//PhoneLayoutInflater 承继抽象类 LayoutInflater
//重写了双参的 onCreateView() 在这做全类名拼接并创立view
public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
        //"android.view."
    };
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                //这个办法是经过反射创立 view
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {}
        }
        //假如拼接前面三个前缀也没有找到 view
        //就会调用父类的 onCreateView(),其实便是调用的是
        //createView(name, "android.view.", attrs)
        return super.onCreateView(name, attrs);
    }
    ...
}

经过这块代码,咱们发现 onCreateView() 会调用 createView() 为咱们做一次全类名拼接,并经过反射创立 view 。假如此刻还未创立好 view,就再做一次类名拼接(“android.view.”)。因而咱们能够把 sClassPrefixList 当作有四个元素,分别是 "android.widget.""android.webkit.""android.app.""android.view."

咱们发现其实 onCreateView() 终究也会走到 createView(),看来 createView() 才是真实创立 view 的地方。

public final View createView(@NonNull Context viewContext, @NonNull String name,
        @Nullable String prefix, @Nullable AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Objects.requireNonNull(viewContext);
    Objects.requireNonNull(name);
    //从 map 里边拿 结构器
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
        //重要代码,假如 constructor 为空,直接经过反射创立 constructor
        if (constructor == null) {
            //进行字符串拼接,然后经过反射拿到 class 对象
            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                    mContext.getClassLoader()).asSubclass(View.class);
            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, viewContext, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            //将 constructor 用 map 保存起来,便利下次用
            sConstructorMap.put(name, constructor);
            ...
        }
        Object lastContext = mConstructorArgs[0];
        mConstructorArgs[0] = viewContext;
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        //经过反射创立 view
        final View view = constructor.newInstance(args);
        ...
        return view;
        ...
}

至此,布局的 rootView 就创立好了。接着咱们回来到 inflate() 办法里边去看看 子view 是怎么创立的。

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 {
            advanceToRootNode(parser);
            final String name = parser.getName();
            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 {
                //讲过了,创立 根view(rootView)
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ViewGroup.LayoutParams params = null;
                if (root != null) {
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        temp.setLayoutParams(params);
                    }
                }
                //接下来回来到这儿,咱们着重看看这儿面的代码
                rInflateChildren(parser, temp, attrs, true);
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
        }
        return result;
    }
}

rInflateChildren(parser, temp, attrs, true) 便是创立 子view 的。咱们点进去看,发现它调用了 rInflate() 办法。

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;
    // 假如 xml 中还有元素,就一向循环调用这段代码
    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)) {
            //tag标签
            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)) {
            //merge标签
            throw new InflateException("<merge /> must be the root element");
        } else {
            //假如是上面以外的标签
            //和创立 rootView 相同,终究依据全类名创立 view
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            //给父容器设置布局参数
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            //递归调用,直到创立完 xml 中一切的view
            rInflateChildren(parser, view, attrs, true);
            //添加到父容器中
            viewGroup.addView(view, params);
        }
    }
}

至此烘托就完全结束,为了避免咱们懵逼,咱们从全体上看看烘托的进程。

--> LayoutInflater.from(mContext).inflate(resId, contentParent);
	// 经过反射创立 rootView
	--> final View temp = createViewFromTag(root, name, inflaterContext, attrs);
		--> 假如是 sdk 自带的 view 
                //name = LinearLayout
                view = onCreateView(context, parent, name, attrs);
                        //内部调用两参的onCreateView()
                	--> PhoneLayoutInflater.onCreateView(name, attrs);
                                //创立 view 的实践办法,经过反射创立
                		--> View view = createView(name, prefix, attrs);
                ---> 假如是自定义的 view
                // name = androidx.constraintlayout.widget.ConstraintLayout
                //经过反射创立 view
                view = createView(context, name, null, attrs);
                	// 经过反射创立 View 对象
                	--> clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
                	--> constructor = clazz.getConstructor(mConstructorSignature);
                	--> final View view = constructor.newInstance(args);
            }
        //创立子View,内部也是经过 createViewFromTag() 来创立 view
        //递归调用直到创立完一切的view
        --> rInflateChildren(parser, temp, attrs, true); 
            --> rInflate()
                //创立 view
    		--> View view = createViewFromTag(parent, name, context, attrs);
                    //将 view 添加到父容器中
                    ---> viewGroup.addView(view, params);
        //将 rootView 添加到父容器中
        --> addView(temp, params)

setContentView()整个流程图

一篇文章带你深刻认识不一样的setContentView()

这是承继自 ActivitysetContentView()。咱们能够自己脑补 AppCompatActivity 的流程图,其实也不复杂,反倒是烘托这一块办法跳来跳去的有点绕,建议咱们亲身去 AS 中点一点。

inflate()

接下来说说烘托中呈现频率很高的代码,其实 inflate() 在实践开发中呈现频率很高。比方咱们 adapter 中就会用到 inflate()

咱们通常会将 item 的布局加载到 recyclerView 中,比方这样:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    val binding = InfoItemLayoutBinding.inflate(inflater, parent, false)
    return MyViewHolder(binding)
}

或许直接是这样:

View view = inflater.inflate(R.layout.inflate_layout, parent, true);

有时候咱们会发现解析出来的 view 根本没有在屏幕上显现,这是由于参数设置的问题,由于刚刚剖析了烘托这块的源码,所以咱们能马上发现这儿面存在的问题。

看看源码:

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 {
            advanceToRootNode(parser);
            final String name = parser.getName();
            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 {
                //咱们重点看这块代码
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                ViewGroup.LayoutParams params = null;
                //传递的参数 root 不等于 null 而且 attchToRoot 是 false
                //依旧没有调用 addView() ,所以不会显现
                //需要自己调用 view.addView()
                //由于这儿为 temp 设置了布局参数,因而自己调用 view.addView() 后会正常显现
                if (root != null) {
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        temp.setLayoutParams(params);
                    }
                }
                //传递的参数 root 不等于 null 而且 attachToRoot 是 true
                //就会将视图添加到父容器中
                //这样就能成功显现出来
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }
                //传递的参数 root 是 null 或许 attachToRoot 是 false
                //就直接将 rootView 回来出去,并不会添加到父容器上
                //因而假如想要显现出来,就必须运用 view.addView()
                //但是 rootView 的布局参数将会失效,不过里边的子view仍是正常布局
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
            ...
        } 
        return result;
    }
}