既然在平淡的一天,也要坚持给自己找点乐趣。
在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("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()
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)两个参数的方法
LayoutInflater#inflate 由于可以自己选择 root 和 attachToRoot 的搭配(后面有解释),使用起来更加灵活;
当 root 传空时,会直接返回要加载的 layoutId,返回的 View 没有父布局且没有 LayoutParams;
假如我需要生成View,而且需要添加params,怎么办?
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);
}
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()方法传入的