在RecyclerView适配器中的onCreateViewHolder函数中一直使用如下方式生成item view,但从没考虑过inflate的具体流程是什么样的,其各个参数具体如何起作用,今天来具体分析一下inflate的代码流程和参数的具体作用,以明确在使用RecyclerView过程中的注意事项。
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.example, root, false);
先上inflate源码(代码有删减):
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);(1)
try {
return inflate(parser, root, attachToRoot);(2)
} finally {
parser.close();
}
}
注意参数root实际上就是RecyclerView自身,因为RecyclerView本身即是ViewGroup的子类,其传递方法如下:
holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type);
从代码中可以看出有两个关键的步骤:位置(1)处先生成一个资源文件resource的解析器,然后在位置(2)调用另一个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; //默认返回的View为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(); //获得布局文件的根标签,这里假设是LinearLayout
if (TAG_MERGE.equals(name)) { //如果根标签是merger
//如果root为空或attachToRoot=false,则抛出异常,在实际使用中attachToRoot一般设置为false,因此布局文件的根标签建议不要使用merge
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);//根据根标签创建View,这里的标签name假设为LinearLayout (1)
ViewGroup.LayoutParams params = null;
if (root != null) { //root不为空的情况下
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs); //由root根据布局文件生成LayoutParams (2)
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params); //设置生成View的LayoutParams (3)
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true); //解析temp View的子控件
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; //设置返回的View为解析出来的布局文件
}
}
} 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(parser.getPositionDescription()
+ ": " + 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;
}
}
代码中比较关键的函数调用解释如下:
(1):createViewFromTag(root, name, inflaterContext, attrs)根据根标签创建View,这里的标签name假设为LinearLayout
在createViewFromTag中最终是调用LayoutInflater的public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException创建View。在createView中通过反射最终调用到LinearLayout的如下构造函数:
public LinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
可以看出如果自定义一个ViewGroup,含有两个参数的构造函数是必须要自己实现的。
(2):params = root.generateLayoutParams(attrs)由root根据布局文件生成LayoutParams
前面说过root实际上就是RecyclerView自已,因此这里调用的是RecyclerView中覆写的generateLayoutParams函数,其代码如下:
public android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
if (this.mLayout == null) { //mLayout为空的话抛出异常,可见必须设置RecyclerView的LayoutManager
throw new IllegalStateException("RecyclerView has no LayoutManager" + this.exceptionLabel());
} else {
return this.mLayout.generateLayoutParams(this.getContext(), attrs); //由LayoutManager根据布局文件生成LayoutParams
}
}
(3):temp.setLayoutParams(params)设置解析出来布局文件的LayoutParams,这也是我们在布局文件中设置的布局参数真正起作用的地方。可以看出如果root为空且attachToRoot为真,我们在布局中设置的参数是不会起 作用的。
以上就是inflate的执行的关键流程,通过代码分析可以明确RecylerView在其适配器中向inflate传递参数时需要注意以下几点:
- root不能为空,否则布局文件中设置的布局参数不会起作用
- attach不能设置为true,否则inflate返回的布局为RecylerView,而不是我们希望返回的自定义布局对象
- 必须为RecylerView指定LayoutManager,否则会抛出异常