一、场景:
当我们在RecyclerView 或者 动态给View添加视图时会使用 LayoutInflater
将视图布局解析成View,然后添加到根布局中。LayoutInflater
inflate方法使用不当就会导致视图宽度有问题,比如inflate的布局中给定了宽高,但是最终展示还是充斥整个布局。或者出现java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
二、问题
1、RecyclerView item视图 、动态添加View时视图宽度问题
2、java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
三、解决
使用LayoutInflater.from(parent.context).inflate(R.layout.adapter_navigation, parent,false)
同时解决上面两个问题。
四、分析
LayoutInflater inflate
最终调用的都是此方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
1、源码片段
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);
}
}
如果root != null
,创建LayoutParams,params = root.generateLayoutParams(attrs)
这就是问题的所在。
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();
}
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {
width = a.getLayoutDimension(widthAttr, "layout_width");
height = a.getLayoutDimension(heightAttr, "layout_height");
}
2、源码片段
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;
}
注意 root.addView(temp, params);
方法,我们在看ViewGroup的addView方法调用的
private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout)
里面的片段
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
这便是问题根源,那为什么child
的parent
会有值呢?看下面
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
/*
* Caller is responsible for calling requestLayout if necessary.
* (This allows addViewInLayout to not request a new layout.)
*/
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
/**
* The parent this view is attached to.
* {@hide}
*
* @see #getParent()
*/
protected ViewParent mParent;
所以当需要连续重复使用同一个布局时 inflate中的第三个参数 boolean attachToRoot 必须设置为false