写点什么

王志超 -Android 筑基——深入理解 LayoutInflater(2),移动端开发工程师面试题

作者:嘟嘟侠客
  • 2021 年 11 月 28 日
  • 本文字数:6482 字

    阅读完需:约 21 分钟

这个方法中的第一个参数 XmlPullParser parser,查看源码,可以看到:


final Resources res = getContext().getResources();XmlResourceParser parser = res.getLayout(resource);


是由 xml 转换而来的,用来对 xml 进行解析的一个类。


好了,我们已经了解了第一个参数的含义,就是传递要转换的 xml 布局过来。


接着看后面的两个参数:@Nullable ViewGroup rootboolean 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_widthlayout_height 对应的值转为布局参数对象中的字段值,如widthheight 值。对应的源码在 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_weightlayout_gravity 参数。


在第 24 行 if (!attachToRoot) 判断,因为这里的 attachToRoot 取值为 false,所以判断为 true,进入 if 分支,到达第 25 行 temp.setLayoutParams(params);,把布局参数设置给了根节点控件对象。


在第 34 行 if (root != null && attachToRoot) 判断,由于 attachToRootfalse,所以判断为 false,不会进入 if 语句,也就是说不会把根节点控件对象以及布局参数设置给 root


在第 39 行 if (root == null || !attachToRoot) 判断,由于 attachToRootfalse,所以判断为 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 并且 attachToRoottrue,所以判断为 true,会进入 if 语句,第 35 行:root.addView(temp, params);,也就是说会把根节点控件对象以及布局参数设置给 root


在第 39 行 if (root == null || !attachToRoot) 判断,因为 root 不为 nullattachToRoot 不为 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) 判断,因为 rootnull,所以判断为 false,不会进入 if 分支,也就是说 ViewGroup.LayoutParams params 的值仍然是 null,没有发生变化。


在第 34 行 if (root != null && attachToRoot) 判断,因为 rootnull,所以判断为 false,不会进入 if 分支,也就是说,不会把根节点控件对象以及布局参数设置给 root


在第 39 行 if (root == null || !attachToRoot) 判断,因为 rootnull,所以判断为 true,进入 if 分支,到达第 40 行,result = temp;,把根节点控件对象 temp 赋值给了 View result 变量。


在第 43 行 return result;,返回的是谁呢?返回的是没有布局参数的根节点控件对象。


小结一下:


2.1.4 根节点不是 merge 时,第四组取值情况分析


我们直接从第 34 行开始,因为之前的代码流程和第三组是一模一样的。


在第 34 行,if (root != null && attachToRoot) 判断,因为 rootnull,所以判断为 false,不会进入 if 分支,也就是说,不会把根节点控件对象以及布局参


《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享


数设置给 root


在第 39 行 if (root == null || !attachToRoot) 判断,因为 rootnull,所以判断为 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) 判断,若 rootnull,或者 attachToRootfalse,判断都会成立,进入 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 不为 nullboolean attachToRoottrue,根节点不是 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 知识点、常见算法题汇总。)



本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

用户头像

嘟嘟侠客

关注

还未添加个人签名 2021.03.19 加入

还未添加个人简介

评论

发布
暂无评论
王志超-Android筑基——深入理解 LayoutInflater(2),移动端开发工程师面试题