王志超 -Android 筑基——深入理解 LayoutInflater(2),移动端开发工程师面试题
这个方法中的第一个参数 XmlPullParser parser
,查看源码,可以看到:
final Resources res = getContext().getResources();XmlResourceParser parser = res.getLayout(resource);
是由 xml 转换而来的,用来对 xml 进行解析的一个类。
好了,我们已经了解了第一个参数的含义,就是传递要转换的 xml 布局过来。
接着看后面的两个参数:@Nullable ViewGroup root
和 boolean attachToRoot
。需要注意的是 ViewGroup root
前面有一个注解 @Nullable
,表示 ViewGroup root
这个参数可以为 null
。
这两个参数的取值组合有几种呢?4 种。
不同的取值组合,对于最后的返回值 View
有什么影响呢?
到这里,我们需要去查看一下 inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
方法的源码:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {final Context inflaterContext = mContext;final AttributeSet attrs = Xml.asAttributeSet(parser);View result = root;advanceToRootNode(parser);// 获取根节点的名字,比如 LinearLayout, FrameLayout 等。final String name = parser.getName();if (TAG_MERGE.equals(name)) {// 根节点的名字是 mergeif (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// 获取 xml 布局的根 View 对象,比如 LinearLayout 对象,FrameLayout 对象等。final View temp = createViewFromTag(root, name, inflaterContext, attrs);ViewGroup.LayoutParams params = null;if (root != null) {// Create layout params that match root, if suppliedparams = 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);}}// Inflate all children under temp against its context.rInflateChildren(parser, temp, attrs, true);// 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;}}return result;}}
我们先不考虑根节点为 merge
的情况,因为这是比较特殊的根节点。先按照一般的情况来分析,有助于解决普遍的问题。
2.1.1 根节点不是 merge 时,第一组取值情况分析
在第 5 行 View result = root;
把 root
的值赋值给 View result
,那么有 result
的值是 notNull
。
在第 21 行 if (root != null)
的判断语句判断为 true
,不能进入 if
语句。
在第 23 行 params = root.generateLayoutParams(attrs);
,通过 root
来获取根节点的布局参数 ViewGroup.LayoutParams
对象,也就是说,把 xml 中的根节点的 layout_
开头的属性,如layout_width
和 layout_height
对应的值转为布局参数对象中的字段值,如width
和 height
值。对应的源码在 ViewGroup
中如下:
public LayoutParams generateLayoutParams(AttributeSet attrs) {return new LayoutParams(getContext(), attrs);}public LayoutParams(Context c, AttributeSet attrs) {TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout);setBaseAttributes(a,R.styleable.ViewGroup_Layout_layout_width,R.styleable.ViewGroup_Layout_layout_height);a.recycle();}
这个方法被 ViewGroup
的子类重写后,会解析 xml 中更多的布局参数,例如在 LinearLayout
中重写后,还会解析 layout_weight
和 layout_gravity
参数。
在第 24 行 if (!attachToRoot)
判断,因为这里的 attachToRoot
取值为 false
,所以判断为 true
,进入 if
分支,到达第 25 行 temp.setLayoutParams(params);
,把布局参数设置给了根节点控件对象。
在第 34 行 if (root != null && attachToRoot)
判断,由于 attachToRoot
为 false
,所以判断为 false
,不会进入 if
语句,也就是说不会把根节点控件对象以及布局参数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,由于 attachToRoot
为 false
,所以判断为 true
,进入 if
语句,到达第 40 行 result = temp;
,也就是把根节点控件对象赋值给了 result
变量。
在第 43 行,return result;
,返回的就是根节点对象。
总结一下:
2.1.2 根节点不是 merge 时,第二组取值情况分析
我们直接从第 24 行开始,因为之前的代码流程和第一组取值情况是一模一样的。
在第 24 行,if (!attachToRoot)
判断,由于 attachToRoot
的取值为 true
,所以判断为 false
,不会进入 if
分支,也就是说不会把布局参数设置给了根节点控件对象。
在第 34 行 if (root != null && attachToRoot)
判断,由于 root
不为 null
并且 attachToRoot
为 true
,所以判断为 true
,会进入 if
语句,第 35 行:root.addView(temp, params);
,也就是说会把根节点控件对象以及布局参数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,因为 root
不为 null
且 attachToRoot
不为 false
,所以判断为 false
,不会进入此分支。
在第 43 行 return result;
,result
是在第 5 行被赋值为 root
,没有被重新赋值,所以返回的是 root
。
小结一下:
2.1.3 根节点不是 merge 时,第三组取值情况分析
在第 5 行 View result = root;
把 root
的值赋值给 View result
,那么有 result
的值是 null
。
在第 21 行 if (root != null)
判断,因为 root
为 null
,所以判断为 false
,不会进入 if
分支,也就是说 ViewGroup.LayoutParams params
的值仍然是 null
,没有发生变化。
在第 34 行 if (root != null && attachToRoot)
判断,因为 root
为 null
,所以判断为 false
,不会进入 if
分支,也就是说,不会把根节点控件对象以及布局参数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,因为 root
为 null
,所以判断为 true
,进入 if
分支,到达第 40 行,result = temp;
,把根节点控件对象 temp
赋值给了 View result
变量。
在第 43 行 return result;
,返回的是谁呢?返回的是没有布局参数的根节点控件对象。
小结一下:
2.1.4 根节点不是 merge 时,第四组取值情况分析
我们直接从第 34 行开始,因为之前的代码流程和第三组是一模一样的。
在第 34 行,if (root != null && attachToRoot)
判断,因为 root
为 null
,所以判断为 false
,不会进入 if
分支,也就是说,不会把根节点控件对象以及布局参
《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享
数设置给 root
。
在第 39 行 if (root == null || !attachToRoot)
判断,因为 root
为 null
,所以判断为 true
,进入 if
分支,到达第 40 行,result = temp;
,把根节点控件对象 temp
赋值给了 View result
变量。
在第 43 行 return result;
,返回的是谁呢?返回的是没有布局参数的根节点控件对象。
第四组取值情况和第三组的返回值是一样的。
2.1.5 根节点为 merge 时情况分析
在第 9 行 if (TAG_MERGE.equals(name))
判断,是 merge
根节点,进入 if
分支;
在第 11 行 if (root == null || !attachToRoot)
判断,若 root
为 null
,或者 attachToRoot
为 false
,判断都会成立,进入 if
语句后抛出异常。
throw new InflateException("<merge /> can be used only with a valid "
"ViewGroup root and attachToRoot=true");
这就是提醒我们,当根节点是 merge
时,root
必须不为 null
而且 attachToRoot
必须为 true
。
在第 43 行 return result;
,而 result
在第 5 行 View result = root;
被赋值为 root
。
总结一下取值情况:
2.2 实际应用
2.2.1 自定义控件填充布局
需要填充的布局 custom_view_layout.xml
如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:orientation="horizontal" android:layout_width="match_parent"android:layout_height="wrap_content">
<ImageViewandroid:layout_margin="16dp"android:id="@+id/icon"android:layout_gravity="center_vertical"app:srcCompat="@mipmap/ic_launcher"android:layout_width="wrap_content"android:layout_height="wrap_content" />
<TextViewandroid:id="@+id/title"android:text="标题"android:textColor="@android:color/black"android:textSize="16sp"android:layout_gravity="center_vertical"android:layout_width="wrap_content"android:layout_height="wrap_content" />
<Viewandroid:layout_weight="1"android:layout_width="0dp"android:layout_height="0dp" /><Switchandroid:layout_marginEnd="16dp"android:layout_gravity="center_vertical|end"android:layout_width="wrap_content"android:layout_height="wrap_content" /></LinearLayout>
CustomView
类如下:
public class CustomView extends LinearLayout {public CustomView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);inflate(context, R.layout.custom_view_layout, this);}}
这里的 inflate()
方法是 View
类的静态方法:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {LayoutInflater factory = LayoutInflater.from(context);return factory.inflate(resource, root);}
内部调用的是 LayoutInflater
的第一个 inflate()
方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {return inflate(resource, root, root != null);}
ViewGroup root
不为 null
且 boolean attachToRoot
为 true
,根节点不是 merge
标签,所以对应的是表格里的第二组情况,返回的是添加了根节点 View
对象以及布局参数的 root
对象,也就是说根节点 View
对象已经添加进入了 root
对象里面。
这里,我们使用 Android Studio 的 Layout Inspector 工具(在 Tools -> Layout Inspector 开启)来查看一下布局:

可以看到出现了重复布局。我们知道,merge
标签可以用于优化重复布局。
现在我们修改布局文件为 custom_merge_view_layout.xml
:
<?xml version="1.0" encoding="utf-8"?><merge xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto">
<ImageViewandroid:layout_margin="16dp"android:id="@+id/icon"android:layout_gravity="center_vertical"app:srcCompat="@mipmap/ic_launcher"android:layout_width="wrap_content"android:layout_height="wrap_content" />
<TextViewandroid:id="@+id/title"android:text="标题"android:textColor="@android:color/black"android:textSize="16sp"android:layout_gravity="center_vertical"android:layout_width="wrap_content"android:layout_height="wrap_content" />
<Viewandroid:layout_weight="1"android:layout_width="0dp"android:layout_height="0dp" /><Switchandroid:layout_marginEnd="16dp"android:layout_gravity="center_vertical|end"android:layout_width="wrap_content"android:layout_height="wrap_content" /></merge>
代码中填充修改后的布局:
public class CustomMergeView extends LinearLayout {public CustomMergeView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);inflate(context, R.layout.custom_merge_view_layout, this);}}
再次使用布局查看器查看布局:

可以看到使用 merge
标签消除了重复布局。
2.2.2 Fragment 填充布局
新建一个 FragmentInflateActivity.java
文件:
public class FragmentInflateActivity extends AppCompatActivity {
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.fragment_inflate_activity);getSupportFragmentManager().beginTransaction().add(R.id.fl_container, MyFragment.newInstance()).commit();}public static void start(Context context) {Intent starter = new Intent(context, FragmentInflateActivity.class);context.startActivity(starter);}}
对应的 fragment_inflate_activity.xml
:
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:background="@android:color/holo_purple"android:id="@+id/fl_container"android:padding="8dp"android:layout_width="match_parent"android:layout_height="match_parent" />
MyFragment.java
如下:
public class MyFragment extends Fragment {private static final String TAG = "MyFragment";@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,@Nullable Bundle savedInstanceState) {Log.d(TAG, "onCreateView: container=" + container);return inflater.inflate(R.layout.my_fragment, container, false);}
public static MyFragment newInstance() {Bundle args = new Bundle();MyFragment fragment = new MyFragment();fragment.setArguments(args);return fragment;}}
my_fragment.xml
如下:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="200dp"android:gravity="center"android:background="@android:color/holo_green_light"android:orientation="vertical">
<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="MyFragment"android:textAllCaps="false"android:textSize="24sp" /></LinearLayout>
运行后效果:<img src="https://img-blog.csdnimg.cn/20200808201439193.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dpbGx3YXlfd2FuZw==,size_16,color_FFFFFF,t_70" width="30%" height="30%">
注意看到在 onCreateView()
方法中打印的一行日志:
D/MyFragment: onCreateView: container=android.widget.FrameLayout{d613ebe V.E...... ......ID 0,0-0,0 #7f07005c app:id/fl_container}
文末
对于很多初中级 Android 工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对 Android 开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品 Android 架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频

附上:我们之前因为秋招收集的二十套一二线互联网公司 Android 面试真题?(含 BAT、小米、华为、美团、滴滴)和我自己整理 Android 复习笔记(包含 Android 基础知识点、Android 扩展知识点、Android 源码解析、设计模式汇总、Gradle 知识点、常见算法题汇总。)

评论