现有的方式
var rootNull = LayoutInflater.from(this).inflate(R.layout.item, null, false)
上面这段代码是使用了LayoutInflater进行填充,但是第二个参数root 设置为null, 第三个参数 attachRoot 设置为 false,从字面意思来看是:我要解析一个xml 布局, 我的root 是null,并且我不想把**给attach到 root 里面去
var rootNotNull = LayoutInflater.from(this).inflate(R.layout.item, container, false)
这是第二种填充方式,和上面差不多,只是参数不同,root 不为null了,但是 第三个参数还是false, 意思难道是:就算你root 不是null, 我也不想把**attach到root上?
var rootNullNotAttach = LayoutInflater.from(this).inflate(R.layout.item, null, true)
这是第三种填充方式, root 为null,但是attach 为ture, 意思是不是指: 不管你root 怎么样,反正我要把**attach 到root 上
var rootNotNullAttach = LayoutInflater.from(this).inflate(R.layout.item, container, true)
这是第四种填充方式,最喜欢这种完美参数调用(每个参数都不是null,也不是0,不是负数,不是false,都是积极向上的)。这个意思可能就是:刚好你root 不是null, 我也刚好想把**attch到你root上
val fuckWay = View.inflate(this, R.layout.item, container)
这是第五种填充方式,不用LayoutInflater, 我最终目的是要获取一个View, 我找你LayoutInflater干嘛,直接找View 不香吗?直接通过调用View的方法来解析xml。(这个方法看起来香,但实际运用起来还是有坑的)
用哪一个.png
此时此刻我只想说,你给我这么多,到底是让我用哪一个来解析布局啊,你搞得我好乱啊!!
效果展示
都说要“透过表象看本质”,我觉得他们说的对,但是这次我还是要通过表象去看本质,我们直接看效果。
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<RelativeLayout
android:id="@+id/ll"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f00" />
</LinearLayout>
这是我们MainActivity的布局文件,这个布局中id 是ll 的RelativeLayout将会被当做第二个参数root来使用
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#ff0"/>
这个布局的文件名是item.xml, 这个也就是最终我们要被解析的xml布局。
第一种方式的 root为null ,attach 为false 的情况
val container = findViewById<RelativeLayout>(R.id.ll)
var rootNull = LayoutInflater.from(this).inflate(R.layout.item, null, false)
一共两行代码,第一行是找到我们id为ll viewGroup, 第二行代码直接LayoutInflater.from(this).inflate(R.layout.item, null, false)将xml解析为view,这个方法返回了一个view,我们怎么将这个被解析出来的view 显示出来? 很简单直接通过addView 加到ViewGroup里面
val container = findViewById<RelativeLayout>(R.id.ll)
var rootNull = LayoutInflater.from(this).inflate(R.layout.item, null, false)
container.addView(item)
rootNullAttachFalse.png
是的,你没看错,我也没有上传错效果图,上面三行代码的效果就是这样的,你的疑惑是,明明给Button设置的宽高是200dp,显示出来却不是,要是说是宽高写死了,在不同设备上显示不同还好,但是我特么明明要的是正方形的Button,你特么给我的确实长方形的。是不是有点说不过去了?就暂且当他是系统bug把,后面会说明为什么会出现这个原因。
第二种方式 root 不为null , attach 为false
val container = findViewById<RelativeLayout>(R.id.ll)
var rootNotNull = LayoutInflater.from(this).inflate(R.layout.item, container, false)
container.addView(rootNotNull)
rootNotNull.png
是不是感觉这次很完美,尺寸对了,形状也对了,是的这次很完美,但是有一点不好的就是,竟然让我们写了三行代码猜得到想要的结果,鉴于“能坐着绝不站着,能躺着绝不坐着”,这次只能中评。
第三种方式 root 为null, attach true
val container = findViewById<RelativeLayout>(R.id.ll)
var rootNullNotAttach = LayoutInflater.from(this).inflate(R.layout.item, null, true)
container.addView(rootNullNotAttach)
rootNullAttachTrue.png
是的你没看错,我保证我绝对没有贴错图片,效果就是和第一种是一样的。
第四种 root 不为空, attach 为true
val container = findViewById<RelativeLayout>(R.id.ll)
var rootNotNullAttach = LayoutInflater.from(this).inflate(R.layout.item, container, true)
container.addView(rootNotNullAttach)
很遗憾这里,不是效果图,因为这样写会导致崩溃
crash.png
从奔溃日志上可以看到崩溃原因是 你的子View 已经有parent的,谷歌就不让你再给他找一个parent了,言外之意就是你已经把这个View 添加到一个ViewGroup里面了,不能再把他添加到其他ViewGroup里面。一共就三行代码,哪来的添加两次的逻辑?行吧,听你的都听你的,你说我重复添加了,但是我不添加了我把我addView 的代码删掉试试?
val container = findViewById<RelativeLayout>(R.id.ll)
var rootNotNullAttach = LayoutInflater.from(this).inflate(R.layout.item, container, true)
rootNotNullAttach.png
去掉之后就完美运行了。惊不惊喜意不意外? 其实起到影响的就是LayoutInflater.from(this).inflate()的第三个参数,true代表,我要把解析出来的view add到root 里面去, false 代表,解析出来的view 我暂时不打算add 到root 里面。
好了现在LayoutInflater.from(this).inflate()三个参数,我们已经知道其中两个参数的作用了,第一个参数指定要被解析的xml布局,第三个参数attach 表示是否要把被解析出来的View add 到第二个参数root 里面去。 那么问题来了!! 第二个参数root 是干嘛的,我该怎么传入,不传行不行?
我们android 现有的几大ViewGroup : LinearLayout、RelativeLayout、FrameLayout他们都有自己特有的布局属性比如:
LinearLayout : android:layout_weight=""
RelativeLayout: android:layout_above="" 等等,
如果你的父布局是LinearLayout,你在子布局中设置了android:layout_above,那就是没有效果的,同样的如果你父布局是RelativeLayout,但是你给子布局设置了 android:layout_weight,同样也是没效果的, 那我们是不是可以大胆的猜想一下,子View的layout属性都是交给他的parent(父布局)来解析的呢?如果是的就能走通上面四种解析方式效果出乎我们意料的问题了。
现在假设我们上面的猜想是对的,我们来总结一下
- LayoutInflater.from(this).inflate 的第一个参数是来指定被解析的xml布局
- LayoutInflater.from(this).inflate 的第二个参数是用来解析 待解析的xml布局根视图的layout_ 属性
- LayoutInflater.from(this).inflate 的第三个参数是用来设置是否要把解析出来的View 添加到root View 中。
按照上面三个结论我们来反推上面四张效果图是否正确
第一种 root 为null ,attach 为false
root 为null ,也就是不解析xml布局 根视图的layout_属性,自然而然的 待解析布局中设置android:layout_width="200dp"和android:layout_height="200dp" 是无效的, 又因为第三个参数是false 代表不自动把解析出来的view add 到viewGroup里面,所以我们需用第三行代码把解析出来的view add到VuewGroup里面
推理完美的与表象结合了有木有
第二种 root 不为 null ,attach 为 false
按照上面的推理来说就是, 要解析 待解析xml布局根视图的layout_属性, 但是不自动添加在root 中。
有一次完美了有木有?尺寸正确,形状正确
第三种 root 为null, attach 为true
按照上面的结论来说就是 ,不解析 待解析xml布局跟视图的layout_属性,但是我要把解析出来的view add 到root 中(很不合理有木有,root都为null了,你还要往null里面add 东西? 所以 roor 为 null 的时候 ,第三个参数没有意义, 与false 效果一致)
再次与上面的表象一致了
第四种 root不为null, attach 为true
意思就是: 我既要解析 待解析xml布局跟视图的layout_ 属性,我又要自动把解析出来的view add 到 root 中
很好,完美的与上面四种表象都吻合了
要不看看源码
当然,我知道如果这样就想说服你们相信我我说的是对的,肯定是不可能的, 因为这就像,一个人和你说 凡是喝了白开水的人最后都死了,劝你不要喝白开水一样(白开水是真的香)
我们先来看看LayouInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 这个函数的代码
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
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) {
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 (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
==========敲黑板了 第一处 第一处的解析在下面========
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 {
======敲黑板了 第二处 第二处, 解析看下面======
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = 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);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// 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;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(inflaterContext, attrs)
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
这段代码很长,虽然我很想删除一些不必要的代码,但是里面几乎每行代码都很有用
-
第一处:这一处代码重要不重要?和今天的主体没有关系,但是和面试很有关系有没有? 只要面试面试个view 有关百分之七十都会问 你能给我解释一下<merge >这个标签吗?
道理我都懂:merge 只能是根视图,并且LayoutInflater.infale()的第三个参数必须是true,因为代码上写的很清楚,你要是不符合就直接扔一个Exception给你(喜欢吗?) -
第二处: 首先我们从英文注释里面可以看到
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
这行代码的作用是从xml中找到根布局
上面的代码先截取一段
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = 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);
}
}
这段代码是不是很清晰? 首先从xml 布局中找到跟视图, 然后创建一个null 的ViewGroup.LayoutParams, 如果root不为空(我们在调用LayoutInfalter.inflate()第二个参数root 不为空),他就干嘛了?干嘛了?干嘛了?
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
他去生成LayoutParams了,他去生成LayoutParams了,他去生成LayoutParams了!!!(是不是和我们上面的猜想吻合了)然后呢?然后呢?然后呢?
然后他竟然去判断attachToRoot 是否是false 了, 如果是false,他就会把解析出来的layoutParams 设置给rootView(只是设置layoutParams,只是设置,不是add啊!注意了没有add),如果不是false 呢?如果不是false!!!
if (root != null && attachToRoot) {
root.addView(temp, params);
}
如果不是false而且root 是null,他就解析出来的view add 到root 里面了。
总结一下
通过源码的查看我们发现我们的猜想完全正确有没有?
LayoutInflater.from(this).inflate(R.layout.item, null, true)
第一个参数 指定 要解析那个xml 布局
第二个参数 root 来确定要不要解析 待解析xml布局跟视图的layout_ 属性
第三个参数 来确定是否要把解析出来的View add 到root 中。
好吧其实上面文章已经结束了从中午12点一直敲到下午4点,没吃饭优点饿,就像结束文章了。但是文章里有一个瑕疵就是有一个地方我没解释,由于了半个小时,我决定先爬起来花半个小时把这个瑕疵给补上, 但是犒劳自己去吃顿肯德基。 晚上再去跑会步,惩罚自己一天没下床。
瑕疵就是,文章开篇我们的猜想中有说过,第二个参数的作用是用来 确定是否要解析 待解析的xml布局中 layout-属性, 喜欢追根究底的朋友在这里肯定优点不舒服, “layout-属性”是什么意思,从没在别人那里停过这个词,别想用高大上的名字忽悠群众。 好的这里解释一下,layout-属性 就是只每个ViewGroup 里特有“layout_”开头的属性,在LinearLayout里面有 android:layout_weight="" 权重属性, 在RelativeLayout中有android:layout_above=""等等
因为待解析xml布局的跟视图的layoutParams的生成是通过
params = root.generateLayoutParams(attrs);
这个,root 就是你传进来第二个参数。
RelativeLayout.java
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new RelativeLayout.LayoutParams(getContext(), attrs);
}
LinearLayout.java
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
}
可以看到这个函数虽然每个ViewGroup都有,但是没有具体的实现类返回的结果是不一样的, 其中具体实现也是不一样的。
问题
-
开篇说了五种解析方式: 但是我只说了4种,第五种View.inflate() 大家还是自己看吧, 内部其实调用还是LayoutInflater.infalte()
-
在使用RecyclerView 重写Adapter的onCreateViewHolder 函数时,到底应该使用哪种方式解析view。如果看懂了这篇文章就能知道为什么在创建ViewHolder 的时候不能使用View.infalte()创建而必须使用LayoutInflater.infalte()来解析了
网友评论