源码分析初级《篇一》 为何建议使用LayoutInflater.from而不用View.inflate
来源:互联网 发布:php 会员下载系统源码 编辑:程序博客网 时间:2024/06/06 15:45
转载文章请注明出处:道龙的博客
我们不管是在ListView、RecyclerView、甚至自定义布局的时候,都会通过View.inflate(......);方法加载布局,其实这是偷懒的方式,有些时候,通过这种偷懒的方式反而带来意想不到的bug。比如空指针异常,非法状态异常。接下来就通过源码角度,分析为何不建议使用这种方式。
伪代码示例:
public class MyStaggedRecyclerAdapter extends RecyclerView.Adapter<MyStaggedRecyclerAdapter.MyViewHolder> { private List<String> list; private List<Integer> heights; public MyStaggedRecyclerAdapter(List<String> list) { // TODO Auto-generated constructor stub this.list = list; heights = new ArrayList<Integer>(); for (int i = 0; i < list.size(); i++) { heights.add((int) (200 + Math.random() * 50)); } } class MyViewHolder extends RecyclerView.ViewHolder { TextView tv; public MyViewHolder(View view) { super(view); tv = (TextView) view.findViewById(android.R.id.text1); } } @Override public int getItemCount() { // TODO Auto-generated method stub return list.size(); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { //绑定数据 LayoutParams params = holder.tv.getLayoutParams(); params.height = heights.get(position); holder.tv.setBackgroundColor(Color.rgb(100, (int) (Math.random() * 255), (int) (Math.random() * 255))); holder.tv.setLayoutParams(params); holder.tv.setText(list.get(position)); } @Override public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) { // 创建ViewHolder MyViewHolder holder = new MyViewHolder(View.inflate(viewGroup.getContext(), android.R.layout.simle_list_item_1, null)); //MyViewHolder holder = new MyViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(android.R.layout.simple_list_item_1, viewGroup, false)); return holder; }}
这里的代码非常简单,是为了给RecycleView设置流式布局的适配器类。流式布局需要给孩子设置宽高,这里通过动态随机给item孩子设置高度的方式,这样展示流式布局显得更加高大上一点~ 首先,在onCreateViewHolder方法中,先通过以往偷懒的方式加载item孩子布局的。运行程序,我们会发现报错:
NullPointException
经过log日志,或者debug,锁定到LayoutParams为空,接下来设置宽高,也就没法执行了。看看为空情况,眼见为实:
这里就很头疼了,我按照标准写法写的,为什么为空呢?这里,其实存在着不小的坑。对于这个坑,需要通过源码角度理解。进入源码:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) { LayoutInflater factory = LayoutInflater.from(context); return factory.inflate(resource, root);}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null);}我们发现,通过调用View.inflate本质上调用的是LayoutInflater的inflate(resource, root, root != null);方法。由于传入的根布局root为nulll,因此这里本质上为inflate(resource, null, false);。继续跟进源码
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(); }}这里就有意思了,inflate三个参数含义前两个很明显,第三个看参数名称也很明显,含义是:是否绑定传入的RootView?我们本质上传入的是false,所以这个值以后都为fallse了。方法里面是通过xml序列化器,对自定义传入的res布局文件预进行解析。具体的解析,继续跟进源码:
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 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 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); } } 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. 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; } } } 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; }}
在这个方法里面,就是对传入的item布局进行全面的解析了。看一下红色位置标注处,params = root.generateLayoutParams(attrs);哈哈,终于把你揪出来来了。我们传入的root为null,更不可能去生成该item的params了,因此,刚开始获取params为null很明显了了。
我们先不直接使用LayoutInflater.from,我们先到,既然root为空,那么我传入一个root让其不是空,不就解决了这个bug吗?做如下修改:
MyViewHolder holder = new MyViewHolder(View.inflate(viewGroup.getContext(), android.R.layout.simple_list_item_1, viewGroup));
还是报错:
java.lang.IllegalStateException:
The specified child already has a parent. You must call removeView() on the child's parent first.
错误情况也很明朗,因为,我们指定的孩子view已经有了父容器了,我们传入的root不能直接成为父亲。你必须先让原来孩子的父亲把孩子去掉,才能再找一个父亲。这尼玛真实一言不合就换父亲啊~~
我们还是要看看为何报这个错误:
首先:由于我们传入了root,也就不为空,最初默认调用还是默认还是调用的:
inflate(resource, root, root != null);
inflate(resource, root, true);
接着定位最终调用源码绿色位置:
看源码就知道:多做了一个事情就是
if (root != null && attachToRoot) {
root.addView(temp, params);
}
由于RecyclerView/ListView会自动将child添加到它里面去成为父亲,并最终一起添加到viewGroup(成为爷爷)。孩子自己就想添加到viewGroup,想直接把爷爷当做父亲,这伦理上也说不过去啊,难免自作多情!这个时候,就会报非法异常了。
那么最后看解决办法:
解决办法1:
MyViewHolder holder = new MyViewHolder(View.inflate(viewGroup.getContext(), R.layout.listview_item, null));
布局使用自己定义的布局:
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:gravity="center" android:textSize="20sp" android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="match_parent"/></LinearLayout>这样的,我们给TextView制定了父布局,通过最初的方式就能完成任务。
解决方式2:
通过建议的方式,使用
MyViewHolder holder = new MyViewHolder(LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.listview_item, viewGroup, false));
使用这种方式,无论加载什么布局,都能完成View的加载,当然,这也是本文所说的结论,如果想要加载布局,不想带来可能存在的“危机”,就使用这种方式加载布局吧!
其他解决方式:
可以通过ViewTreeObserver.addGlob..Listener()方法,设置页面布局绘制完毕监听器,在里面进行获取组件的宽高一定不会报错。至于使用方式,自定百度~
对于这一块的更具体、详细的源码,等笔者能力提高了,再进入两万行的代码里带大家遨游一番吧~
觉得有作用,就点个赞价加个关注呗~~
- 源码分析初级《篇一》 为何建议使用LayoutInflater.from而不用View.inflate
- LayoutInflater.from(this) View.inflate()
- LayoutInflater.from(this) View.inflate()
- View.Inflate)与LayoutInflater.from().inflate();
- LayoutInflater源码分析之Inflate
- View.inflate() 和 LayoutInflater.from(context).inflate()区别
- Android LayoutInflater.inflate的使用及源码分析
- LayoutInflater.inflate源码详解
- android中LayoutInflater.from(context).inflate的分析
- android中LayoutInflater.from(context).inflate的分析
- android中LayoutInflater.from(context).inflate的分析
- android中LayoutInflater.from(context).inflate的分析
- 关于LayoutInflater.from(context).inflate()的使用的问题
- 关于LayoutInflater.from(context).inflate()的使用的问题
- 关于LayoutInflater.from(context).inflate()的使用的问题
- LayoutInflater.from(this)、inflate 详解
- View.inflate()与LayoutInflater.inflate()的区别?
- LayoutInflater.inflate方法加载View
- JavaScript 中深拷贝浅拷贝问题
- 程序开发资源网站收藏
- 451. Sort Characters By Frequency
- 多线程之线程池的应用
- 阈值处理
- 源码分析初级《篇一》 为何建议使用LayoutInflater.from而不用View.inflate
- 论找朋友与工作的重要性
- redis.properties文件的配置及说明
- shortWeekdaySymbols,NSLocale,NSDateComponents
- hello world 的简单输入输出
- 在Java中连接字符串时是使用+号还是使用StringBuilder
- 旧的 DxgkDdiSubmitBuffer / DxgkDdiRender 函数已被弃用)。这个函数相当复杂,并且还接受来自用户模式驱动程序提交的每一个由厂商特定的数据。我在这里找到了一个bug。
- 495. Teemo Attacking
- jQuery.extend 函数使用详解