前言:
既然在平淡的一天,也要坚持给自己找点乐趣。
在android开发中,根据布局文件生成View的情况咋们开发中是十分常见的,现在咋们分析下这这两个方法的区别
LayoutInflater.inflate() ?
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot); ----------------发现调用了这个重载的方法
} finally {
parser.close();
}
}
三个参数分别为 1 ->你要生成的View 的XML文件,2 -> 父类布局 3 ->是否将xml生成的View添加到 父布局中
下面先讲解上面的方法
final Resources res = getContext().getResources(); -------------获取Resources 对象
final XmlResourceParser parser = res.getLayout(resource); ------------获取XML的解析器(在LayoutInflater.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 (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
//这个temp 就是根据你传入的XML文件生成的View
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
// 为传入的布局生成一套匹配root的 LayoutParams (源码的翻译)
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
// 如果没有 attachToRoot,那为根布局设置 layoutparams
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.
//如果root 不等于null并且attachToRoot == true的时候,给你传入的View添加到root里面,返回给你
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.
//如果root == null 或者 attachToRoot = false 的时候 直接把XML生成的View返回
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(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;
}
}
现在看看两个参数的方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
在上面的两个参数的最后发现还是调用inflate()三个参数的,只是默认给
attachToRoot 传入值 为 root != null
如果 root == null,那么attachToRoot = false 返回你传入的View,并且不设置params
如果 root !=null , 那么attachToRoot = true 返回rootview ,设置了params
View.inflate();
最后看下View的inflate()
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
发现最后还是LayoutInflater.inflate(resource, root)两个参数的方法
总结:
- View.inflate() 只是一个android 提供的简易的包装方法 ,实际还是调用了LayoutInflater.inflate(resource, root)
-
LayoutInflater#inflate 由于可以自己选择 root 和 attachToRoot 的搭配(后面有解释),使用起来更加灵活;
-
当 root 传空时,会直接返回要加载的 layoutId,返回的 View 没有父布局且LayoutParam是不可能有效的,因为像,layout_with ,margin等着这个是有父类才能生效的;
问题?
假如我需要生成View,而且需要添加params,怎么办?
1.列如
RecycleView # onCreatViewHolder
在为 RecyclerView 创建 ViewHolder 时,由于 View 复用的问题,是 RecyclerView 来决定什么时候展示它的子View,这个完全不由我们决定,这种情况下,attachToRoot 必须为 false:
View.inflate() 这个方法肯定是不能的,这个这个方法的局限性
所以只能用LayoutInflater#inflate
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View view = inflater.inflate(R.layout.item, parent, false);
return new ViewHolder(view);
}
2.列如
-
Fragment#onCreateView()
由于 Fragment 需要依赖于 Activity 展示,一般在 Activity 中也会有容器布局来盛放 Fragment:
Fragment fragment = new Fragment();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.root_container, fragment)
.commit();
上述代码中的 R.id.root_container 便为容器,这个 View 会作为参数传递给 Fragment#onCreateView() :
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);
}
它也是你在 inflate() 方法中传入的 ViewGroup,FragmentManager 会将 Fragment 的 View 添加到 ViewGroup 中,言外之意就是,Fragment 对应的布局展示或者说添加进 ViewGroup 时也不是我们来控制的,而是 FragmentManager 来控制的。
如果return inflater.inflate(R.layout.fragment_layout, parentViewGroup, true );
则会报一个错误
child already has parent
在这里在引申一个问题,在代码生成的Fragment的生命周期,在没有添加到Activity 的时候只会走空参构造方法,其他的方法只有add()后才会走
这里有一个很好的解释因为
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Log.e("++", "onCreateView");
return super.onCreateView(inflater, container, savedInstanceState);
}
中的container是在FragmentMange中的add()方法传入的