LayoutInflater 源码分析与应用
KindraBryso
7年前
<h2>一、简述</h2> <p>LayoutInflater直译为 布局填充器,它是用来创建布局视图的,常用inflate()将一个xml布局文件转换成一个View,下面先介绍下获取LayoutInflater的三种方式 和 创建View的两种方式。</p> <h2>1、获取LayoutInflater的三种方式</h2> <ol> <li>LayoutInflater inflater = getLayoutInflater(); //调用Activity的getLayoutInflater()</li> <li>LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);</li> <li>LayoutInflater inflater = LayoutInflater.from(context);</li> </ol> <p>其实不管是哪种方式,最后都是通过方式2获取到LayoutInflater的,如:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/695f5986d8cd205a1a44dbe72c5c005a.png"></p> <h2>2、创建View的两种方式</h2> <ol> <li>View.inflate();</li> <li>LayoutInflater.from(context).inflate();</li> </ol> <h2>二、源码分析</h2> <p>上面两种创建View的方式都是开发中常用的,那两者有什么关系吗?下面对View.inflate()进行方法调用分析:</p> <h2>1、View.inflate()最终调用方法探究</h2> <h3>1)按住Ctrl+鼠标左键查看View.inflate()方法</h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/8b1ae07293d5c6be7b14a57c83a6d839.png"></p> <p>可以看到View.inflate()就是调用了LayoutInflater.from(context).inflate()。</p> <p>好,到这一步要明确,不管我们研究哪种方式,实际上都研究方式2,即LayoutInflater.from(context).inflate()。</p> <h3>2)按住Ctrl+鼠标左键查看LayoutInflater.from(context).inflate(resource, root)方法。</h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/b2f92fb1fa4411e361a007973f3dd6f9.png"></p> <p>嗯?LayoutInflater.from(context).inflate(resource, root)再调用了自己的重载inflate(resource, root, root != null)。</p> <p>3)按住Ctrl+鼠标左键查看LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null)方法。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/be9686bb077a3a665a515ec05ea3ebaf.png"></p> <p>嗯??LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null)再再调用了自己的重载inflate(parser, root, attachToRoot)。</p> <p>4)按住Ctrl+鼠标左键查看LayoutInflater.from(context).inflate(resource, root).inflate(resource, root, root != null).inflate(parser, root, attachToRoot)方法。</p> <p>这下总算是到头了,不过代码太长,这里就截了一半的图(这不是重点)。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/751562d0140769088fd195f170d0dcdb.png"></p> <p>好,重点来了,到这步我们可以明白一点,View.inflate()整个方法调用链如下:</p> <pre> <code class="language-java">View.inflate() = LayoutInflater.from(context) .inflate(resource, root) .inflate(resource, root, root != null) .inflate(parser, root, attachToRoot)</code></pre> <h2>2、LayoutInflater的inflate(parser, root, attachToRoot)做了什么?</h2> <p>由于代码太长,不方便截图,下面贴出代码中的重点代码:</p> <pre> <code class="language-java">public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { ... 省略代码~ ... 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); } } ... 省略代码~ ... // 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; } ... 省略代码~ ... return result; } }</code></pre> <p>该inflate方法中有以下四步操作:</p> <ol> <li>通过使用XmlPullParser parser将xml布局文件转换成视图temp。</li> <li>判断ViewGroup root对象是否为null,来决定要不要给temp设置LayoutParams。</li> <li>判断boolean attachToRoot是否为true,来决定是否要把temp顺便加到ViewGroup root中。</li> <li>最后返回视图temp。</li> </ol> <p>到这里就把创建视图的流程分析完了,接下来是比较 View.inflate()和 LayoutInflater.from(context).inflate()的区别。</p> <h2>3、View.inflate()和 LayoutInflater.from(context).inflate()的区别</h2> <h3>1)View.inflate()第三个参数的解析:</h3> <p>开发中常常会对第三个参数(ViewGroup root)传入null吧,通过上面对最终inflate方法的分析,可以知道,如果ViewGroup root取值为null,则得到的视图temp不会被设置LayoutParams。下面做个试验:</p> <pre> <code class="language-java">View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null); ViewGroup.LayoutParams params = itemView.getLayoutParams(); Log.e("CSDN_LQR", "params == null : " + (params == null));</code></pre> <p>打印结果如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7d3cf4cfb7ab6a332d92ffe22d711190.png"></p> <p>同理,将第三个参数传入一个确实存在的ViewGroup时,结果就是视图temp能获取到LayoutParams,有兴趣的可以自己试试。</p> <h3>2)LayoutInflater.from(context).inflate()的优势:</h3> <p>*下面的场景分析将体现出LayoutInflater.from(context).inflate()的灵活性。</p> <p>如果是在RecyclerView或ListView中使用View.inflate()创建布局视图,又想对创建出来的布局视图进行高度等参数设置时,会有什么瓶颈呢?</p> <p>下面贴出我之前写过的一段用于瀑布流适配器的代码:</p> <pre> <code class="language-java">public class MyStaggeredAdapter extends RecyclerView.Adapter<MyStaggeredAdapter.MyViewHolder> { private List<String> mData; private Random mRandom = new Random(); public MyStaggeredAdapter(List<String> data) { mData = data; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //这里使用的是安卓自带的文本控件布局 View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, null); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { //为实现瀑布流效果,需要对条目高度进行设置(让各个条目的高度不同) ViewGroup.LayoutParams params = holder.mTv.getLayoutParams(); params.height = mRandom.nextInt(200) + 200; holder.mTv.setLayoutParams(params); holder.mTv.setBackgroundColor(Color.argb(255, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30, 180 + mRandom.nextInt(60) + 30)); holder.mTv.setText(mData.get(position)); } @Override public int getItemCount() { return mData.size(); } class MyViewHolder extends RecyclerView.ViewHolder { TextView mTv; public MyViewHolder(View itemView) { super(itemView); mTv = (TextView) itemView.findViewById(android.R.id.text1); } } }</code></pre> <p>经过上面对View.inflate()的第三个参数解析之后,这代码的问题一眼就能看出来了吧,没错,就是ViewGroup.LayoutParams params = holder.mTv.getLayoutParams();这行代码获取到的LayoutParams为空,不信?走一个。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/4bb7853d1b58660b39787caac60dd6d0.png"></p> <p>接下来理所当然的要让得到的LayoutParams不为空啦,所以将onCreateViewHolder()的代码修改如下:</p> <pre> <code class="language-java">@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //这里使用的是安卓自带的文本控件布局 View itemView = View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent); return new MyViewHolder(itemView); }</code></pre> <p>传入的ViewGroup parent不为null,所以肯定获取的LayoutParams不为空,但是又有一个问题,看报错。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3f6c03d6a7b3177d463694c717474848.png"></p> <p>为什么会报这样的错呢?回看最终inflate()的四个步骤:</p> <ol> <li>通过使用XmlPullParser parser将xml布局文件转换成视图temp。</li> <li>判断ViewGroup root对象是否为null,来决定要不要给temp设置LayoutParams。</li> <li>判断boolean attachToRoot是否为true,来决定是否要把temp顺便加到ViewGroup root中。</li> <li>最后返回视图temp。</li> </ol> <p>步骤2让条目获取的LayoutParams不为空没错,但是步骤3出问题了,当使用View.inflate(parent.getContext(), android.R.layout.simple_list_item_1, parent)传入parent后,boolean attachToRoot的取值就是为true,所以创建出来的条目会顺便添加到ViewGroup中(这里的ViewGroup就是RecyclerView),而RecyclerView本身就会自动将条目添加到自身,这样就添加了两次,故报错。那为什么attachToRoot的取值是true呢?再看View.inflate()的整个方法调用链:</p> <pre> <code class="language-java">View.inflate() = LayoutInflater.from(context) .inflate(resource, root) .inflate(resource, root, root != null) .inflate(parser, root, attachToRoot)</code></pre> <p>boolean attachToRoot的取值取决于root(也就是parent)是否为空,这就是View.inflate()的瓶颈,它没法灵活的指定boolean attachToRoot的取值。这里我就是只是想让创建出来的视图能得到LayoutParams,但不添加到ViewGroup中,这样的要求可以通过LayoutInflater.from(context).inflate()来实现。所以下面将onCreateViewHolder()的代码修改如下:</p> <pre> <code class="language-java">@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false); ViewGroup.LayoutParams params = itemView.getLayoutParams(); Log.e("CSDN_LQR", "params == null : " + (params == null)); return new MyViewHolder(itemView); }</code></pre> <p>代码中LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1,parent,false)传入了parent(即ViewGroup不为null),所以创建出来的视图可以得到LayoutParams,同时又指定attachToRoot的取值为false,即不添加到ViewGroup中。到这里,上面重覆添加子控件的问题就解决了,总结一下吧:</p> <ul> <li>View.inflate()第三个参数若不为null,则创建出来的视图一定能获得LayoutParams,反之,不一定。(下面会解释)</li> <li>LayoutInflater.from(context).inflate()可以灵活的指定传入的ViewGroup是否为空来决定创建出来的视图能否获得LayoutParams,同时又可以指定attachToRoot的取值来决定创建出来的视图是否要添加到ViewGroup中。</li> </ul> <h2>三、小细节</h2> <p>*上面已经将LayoutInflater的源码分析完毕,现在还有一个小问题,其实跟本文主题没多大关系,当作拓展来看吧。</p> <p>前面说到,View.inflate()第三个参数若不为null,则创建出来的视图一定能获得LayoutParams,反之,不一定。这话怎么理解?</p> <p>也就是说,即使View.inflate()第三个参数为null,创建出来的视图也有可能获得LayoutParams咯?是的,说到底,这个LayoutParams的有无,实际取决于条目本身是否有父控件,且看上面用到的simple_list_item_1布局:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6a4408962f2888ea9e483b75d13852e0.png"></p> <p>发现了吧,就一个TextView,没有父控件,那如果我给它加个父控件,同时使用最开始的方式也能顺利得到LayoutParams呢?代码如下:</p> <pre> <code class="language-java">@Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = View.inflate(parent.getContext(), R.layout.item_layout, null); return new MyViewHolder(itemView); } @Override public void onBindViewHolder(MyViewHolder holder, int position) { ViewGroup.LayoutParams params = holder.mTv.getLayoutParams(); Log.e("CSDN_LQR", "params == null : " + (params == null)); ... 控件设置 ... }</code></pre> <p>item_layout的布局代码如下:</p> <pre> <code class="language-java"><?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:minHeight="?android:attr/listPreferredItemHeightSmall" android:textAppearance="?android:attr/textAppearanceListItemSmall"/> </LinearLayout></code></pre> <p>运行,果然可以获得LayoutParams,打印结果如下:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/0b7628269ebfa420ea75527e0d5521a2.png"></p> <h2>四、最后</h2> <p>本人也是头次写类分析型的文章,如描述有误,请不吝赐教,同时还请各位看客多担待,指出后本人会尽快修改,谢谢。</p> <p> </p> <p>来自:https://juejin.im/post/58fc3a0bac502e0063a83a31</p> <p> </p>