setContentView()
办法在咱们 onCreate()
办法的第二行,咱们常常运用这段代码,但是似乎并没有真实的了解过这个办法,今日咱们来一同看看它内部是怎样完结的吧。
setContentView()
的目的便是将咱们自定义的 xml
文件解析烘托到屏幕上进行显现。
setContentView()
流程
setContentView() 在承继 Acitvity
和 AppCompatAcitivity
是有些稍微差异的,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
。到这儿咱们将剖析流程使命放一放,咱们先讲讲 DecorView
和 content
。
DecorView 与 content
它是 FrameLayout
的子类,它能够被认为是 Android
视图树的根节点视图。用来放一些体系中 xml
文件,而 content
便是体系 xml
文件中 ViewGroup
的 id
,全名叫作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
是体系中最简略的布局文件,它首要有上下两部分,ViewStub
和 FrameLayout
。这个 FrameLayout
的 id
便是前面说到的 com.android.internal.R.id.content
。因而假如咱们将 activity_main.xml
文件烘托加载到屏幕上便是这样的:
承继 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
相似,也是一个 ViewGroup
。subDecorView
中也有体系定义的 xml
布局文件,这布局文件里同样也有一个 ViewGroup
用来装咱们自定义的 xml
布局文件,相关于承继 Activity
,实践上便是进行了一层封装,原来是直接将 xml
放到 com.android.internal.R.id.content
,而现在是将它放到 subDecor
的某个 ViewGroup
中,再将 subDecor
放到原来那个 id
叫作 com.android.internal.R.id.content
的 ViewGroup
中。
画个图就像这样:
接下里咱们继续点进 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
中的原来那个 id
为 android.R.id.content
的ViewGroup
的 id
设为 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_content
的 ViewGroup
,咱们自己的 xml
文件便是加载烘托到这儿面去。
由于篇幅过多可能导致咱们阅读效率低,因而关于上面说到的
screen_simple.xml
和abc_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()整个流程图
这是承继自 Activity
的 setContentView()
。咱们能够自己脑补 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;
}
}