首页 > 代码库 > LayoutInflater inflate LayoutParams 简介

LayoutInflater inflate LayoutParams 简介

LayoutInflater简介

LayoutInflater就是布局填充器,作用是将xml布局文件转化为View对象。
可以通过以下两种方式获取LayoutInflater,其实他们是完全一样的
  • LayoutInflater layoutInflater = LayoutInflater.from(context);
  • LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)
public static LayoutInflater from(Context context) {
return (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
然后就可以使用layoutInflater的inflate方法来将布局转化为View了,主要有如下几个重载方法:
技术分享
其实他们最终调用的也都是同一个方法
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, ViewGroup root) {
return inflate(parser, root, root != null);
}
可能有的同学使用
View的inflate静态方法获取View的,其实最终还是通过LayoutInflater来填充布局的。
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}

inflate方法分析

下面分析inflate的执行过程,源码如下:
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 {
// Look for the root node.查找根节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {//没有找到根节点
throw new InflateException(parser.getPositionDescription()+": No start tag found!");
}
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 {
// Temp is the root view that was found in the xml 创建根节点的View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied 如果提供了LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {//即inflate(resId , parent,false)时的逻辑
// Set the layout params for temp if we are not attaching. (If we are, we use addView, below)
temp.setLayoutParams(params); // 为xml中的root设置LayoutParams
}
}
// 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) {//即inflate(resId , parent,true)时的逻辑
root.addView(temp, params); //将temp按照params添加到root中
}
// Decide whether to return the root that was passed in or the top view found in xml.
if (root == null || !attachToRoot) { //即inflate(resId , null)时的逻辑
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(parser.getPositionDescription()+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don‘t retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
从上面的分析已经可以看出:
  • 对于 inflate(resId , null),只创建temp,并返回temp
  • 对于 inflate(resId , parent, false),创建temp,然后执行temp.setLayoutParams(params),并返回temp
  • 对于 inflate(resId , parent, true),创建temp,然后执行root.addView(temp, params),返回包含temp的root
由上面已经能够解释:
  • inflate(resId , null)不能正确处理宽和高是因为:layout_width,layout_height是相对了父级设置的,必须与父级的LayoutParams一致。而此temp的getLayoutParams为null
  • inflate(resId , parent,false) 可以正确处理,因为temp.setLayoutParams(params);这个params正是root.generateLayoutParams(attrs);得到的。
  • inflate(resId , parent,true)不仅能够正确的处理,而且已经把resId这个view加入到了parent,并且返回的是parent。前面例子中,MyAdapter里面的getView报错是因为源码中调用了root.addView(temp, params),而此时的root是我们的ListView,ListView为AdapterView的子类,我们来看AdapterView的源码:
public void addView(View child) {
throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
}
可以看出来,这里报错的原因仅仅是因为ListView不支持调用addView方法。我估计是因为,你addView后ListView就无法使用原来的缓存机制了,而addFooterView和addHeaderView后不影响。

inflate方法总结

下面我们来总结下inflate(int resourceId, ViewGroup root, boolean attachToRoot)方法中这三个参数的作用
首先,resourceId是布局Id,root是这个布局所依附的父布局attachToRoot就是是否依附在这个父布局上,下面分情况讨论:
  • 如果 root 为null,此时 attachToRoot 的值将不起作用,resource的最外层的控件的宽高是【没有】效果的,并且会添加到父控件中
  • 如果 root 不为null,attachToRoot 为 false:resource的最外层的控件的宽高是【有】效果的,但不会添加到父控件中
  • 如果 root 不为null,attachToRoot 为 true: resource的最外层的控件的宽高是【有】效果的,并且会添加到父控件中

LayoutParams简介

上面的过程中涉及到了LayoutParams类,简单来说,LayoutParams相当于一个Layout信息包,它封装了与Layout相关的位置、高、宽等信息,作用是用于child向parent传达自己对布局要求的信息
下面看下官方文档的说明:
/**
* Set the layout parameters associated管理 with this view. These supply提供
* parameters to the <i>parent父布局</i> of this view specifying指定 how it should be
* arranged安排. There are many subclasses子类 of ViewGroup.LayoutParams, and these
* correspond to the different subclasses of ViewGroup that are responsible承担
* for arranging their children.
*
* @param params The layout parameters for this view, cannot be null
*/
public void setLayoutParams(ViewGroup.LayoutParams params) {
if (params == null) {
throw new NullPointerException("Layout parameters cannot be null");
}
mLayoutParams = params;
resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
}
requestLayout();
}

由此我想到了另一个问题,即:在我们需要动态修改View的宽高时,为什么不是直接设置View的大小,而是通过View的LayoutParams去设置呢?
这是因为我们在设置一个View的 layout_width、layout_height 等值的时候,其实这些属性都是作用在父布局中的,并不是作用于View上的,这也是为什么叫layout_width而不是width的原因。

需要注意的是,LayoutParams是ViewGroup的内部类,不同的Layout有不同LayoutParams实现,他们都是ViewGroup.LayoutParams的直接或间接子类,对于不同的Layout要使用相应的LayoutParams才行

其中,AbsListView(ListView的父类)是ViewGroup.LayoutParams的直接子类
而三大布局FrameLayout(包括其子类ScrollView)、LinearLayout、RelativeLayout却是ViewGroup.MarginLayoutParams的子类
public static class LayoutParams extends ViewGroup.MarginLayoutParams
public static class MarginLayoutParams extends ViewGroup.LayoutParams

ViewGroup.LayoutParams

技术分享
技术分享
通过源码可知,对于ViewGroup.LayoutParams来说,只能传达width、height信息

ViewGroup.MarginLayoutParams

技术分享
通过源码可知,对于ViewGroup.MarginLayoutParams来说,除了能传达width、height信息外,还可以传达各种margin信息

FrameLayout.LayoutParams

技术分享
技术分享
对于FrameLayout.LayoutParams来说,除width、height、margin外,还可以设置gravity

LinearLayout.LayoutParams

技术分享
技术分享
对于LinearLayout.LayoutParams来说,width、height、margin外,还可以设置gravity和float weight权重

RelativeLayout.LayoutParams

技术分享 技术分享
RelativeLayout.LayoutParams可以设置的参数就多了,那些以layout开头的属性基本都可以通过addRule方法进行设置

AbsListView.LayoutParams

技术分享
技术分享
主要是多了一个int viewType;//View type for this view, as returned by android.widget.Adapter#getItemViewType(int)

LayoutInflater inflate LayoutParams 简介