Android LayoutInflater深度解析

本文详细解析了LayoutInflater的工作原理,包括其基本用法、inflate方法的不同参数组合及其应用场景,如动态添加View和ListView的Item布局。

本篇文章转自鸿洋原文地址和郭霖原文地址两位大神的文章,做了一些重新的排版。

相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用于加载布局的。在Activity中的setContentView()方法内部也是使用LayoutInflater来加载布局的,只不过这部分源码是internal的,不太容易查看到。那么今天我们就来把LayoutInflater的工作流程仔细地剖析一遍,也许还能解决掉某些困扰你心头多年的疑惑。

先来看一下LayoutInflater的基本用法吧,它的用法非常简单,首先需要获取到LayoutInflater的实例,有两种方法可以获取到,第一种写法如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. LayoutInflater layoutInflater = LayoutInflater.from(context);  
当然,还有另外一种写法也可以完成同样的效果:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. LayoutInflater layoutInflater = (LayoutInflater) context  
  2.         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);  
其实第一种就是第二种的简单写法,只是Android给我们做了一下封装而已。得到了LayoutInflater的实例之后就可以调用它的inflate()方法来加载布局了,如下所示:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. layoutInflater.inflate(resourceId, root);  
inflate()方法一般接收两个参数,第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局,如果不需要就直接传null。这样就成功成功创建了一个布局的实例,之后再将它添加到指定的位置就可以显示出来了。

下面我们就通过一个非常简单的小例子,来更加直观地看一下LayoutInflater的用法。比如说当前有一个项目,其中MainActivity对应的布局文件叫做activity_main.xml,代码如下所示:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:id="@+id/main_layout"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" >  
  5.   
  6. </LinearLayout>  
这个布局文件的内容非常简单,只有一个空的LinearLayout,里面什么控件都没有,因此界面上应该不会显示任何东西。

那么接下来我们再定义一个布局文件,给它取名为button_layout.xml,代码如下所示:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <Button xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="wrap_content"  
  3.     android:layout_height="wrap_content"  
  4.     android:text="Button" >  
  5.   
  6. </Button>  
这个布局文件也非常简单,只有一个Button按钮而已。现在我们要想办法,如何通过LayoutInflater来将button_layout这个布局添加到主布局文件的LinearLayout中。根据刚刚介绍的用法,修改MainActivity中的代码,如下所示:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class MainActivity extends Activity {  
  2.   
  3.     private LinearLayout mainLayout;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         mainLayout = (LinearLayout) findViewById(R.id.main_layout);  
  10.         LayoutInflater layoutInflater = LayoutInflater.from(this);  
  11.         View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);  
  12.         mainLayout.addView(buttonLayout);  
  13.     }  
  14.   
  15. }  
可以看到,这里先是获取到了LayoutInflater的实例,然后调用它的inflate()方法来加载button_layout这个布局,最后调用LinearLayout的addView()方法将它添加到LinearLayout中。

现在可以运行一下程序,结果如下图所示:

                                             

Button在界面上显示出来了!说明我们确实是借助LayoutInflater成功将button_layout这个布局添加到LinearLayout中了。LayoutInflater技术广泛应用于需要动态添加View的时候,比如在ScrollView和ListView中,经常都可以看到LayoutInflater的身影。

当然,仅仅只是介绍了如何使用LayoutInflater显然是远远无法满足大家的求知欲的,知其然也要知其所以然,接下来我们就从源码的角度上看一看LayoutInflater到底是如何工作的。

不管你是使用的哪个inflate()方法的重载,最终都会辗转调用到LayoutInflater的如下代码中:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
  2.     synchronized (mConstructorArgs) {  
  3.         final AttributeSet attrs = Xml.asAttributeSet(parser);  
  4.         mConstructorArgs[0] = mContext;  
  5.         View result = root;  
  6.         try {  
  7.             int type;  
  8.             while ((type = parser.next()) != XmlPullParser.START_TAG &&  
  9.                     type != XmlPullParser.END_DOCUMENT) {  
  10.             }  
  11.             if (type != XmlPullParser.START_TAG) {  
  12.                 throw new InflateException(parser.getPositionDescription()  
  13.                         + ": No start tag found!");  
  14.             }  
  15.             final String name = parser.getName();  
  16.             if (TAG_MERGE.equals(name)) {  
  17.                 if (root == null || !attachToRoot) {  
  18.                     throw new InflateException("merge can be used only with a valid "  
  19.                             + "ViewGroup root and attachToRoot=true");  
  20.                 }  
  21.                 rInflate(parser, root, attrs);  
  22.             } else {  
  23.                 View temp = createViewFromTag(name, attrs);  
  24.                 ViewGroup.LayoutParams params = null;  
  25.                 if (root != null) {  
  26.                     params = root.generateLayoutParams(attrs);  
  27.                     if (!attachToRoot) {  
  28.                         temp.setLayoutParams(params);  
  29.                     }  
  30.                 }  
  31.                 rInflate(parser, temp, attrs);  
  32.                 if (root != null && attachToRoot) {  
  33.                     root.addView(temp, params);  
  34.                 }  
  35.                 if (root == null || !attachToRoot) {  
  36.                     result = temp;  
  37.                 }  
  38.             }  
  39.         } catch (XmlPullParserException e) {  
  40.             InflateException ex = new InflateException(e.getMessage());  
  41.             ex.initCause(e);  
  42.             throw ex;  
  43.         } catch (IOException e) {  
  44.             InflateException ex = new InflateException(  
  45.                     parser.getPositionDescription()  
  46.                     + ": " + e.getMessage());  
  47.             ex.initCause(e);  
  48.             throw ex;  
  49.         }  
  50.         return result;  
  51.     }  
  52. }  
从这里我们就可以清楚地看出,LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。不熟悉pull解析方式的朋友可以网上搜一下,教程很多,我就不细讲了,这里我们注意看下第23行,调用了createViewFromTag()这个方法,并把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。确实如此,在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。

当然,这里只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,代码如下所示:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)  
  2.         throws XmlPullParserException, IOException {  
  3.     final int depth = parser.getDepth();  
  4.     int type;  
  5.     while (((type = parser.next()) != XmlPullParser.END_TAG ||  
  6.             parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {  
  7.         if (type != XmlPullParser.START_TAG) {  
  8.             continue;  
  9.         }  
  10.         final String name = parser.getName();  
  11.         if (TAG_REQUEST_FOCUS.equals(name)) {  
  12.             parseRequestFocus(parser, parent);  
  13.         } else if (TAG_INCLUDE.equals(name)) {  
  14.             if (parser.getDepth() == 0) {  
  15.                 throw new InflateException("<include /> cannot be the root element");  
  16.             }  
  17.             parseInclude(parser, parent, attrs);  
  18.         } else if (TAG_MERGE.equals(name)) {  
  19.             throw new InflateException("<merge /> must be the root element");  
  20.         } else {  
  21.             final View view = createViewFromTag(name, attrs);  
  22.             final ViewGroup viewGroup = (ViewGroup) parent;  
  23.             final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);  
  24.             rInflate(parser, view, attrs);  
  25.             viewGroup.addView(view, params);  
  26.         }  
  27.     }  
  28.     parent.onFinishInflate();  
  29. }  
可以看到,在第21行同样是createViewFromTag()方法来创建View的实例,然后还会在第24行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。

这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。

比较细心的朋友也许会注意到,inflate()方法还有个接收三个参数的方法重载,结构如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. inflate(int resource, ViewGroup root, boolean attachToRoot)  
那么这第三个参数attachToRoot又是什么意思呢?其实如果你仔细去阅读上面的源码应该可以自己分析出答案,这里我先将结论说一下吧,感兴趣的朋友可以再阅读一下源码,校验我的结论是否正确。

1. 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。

2. 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root。

3. 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。

4. 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。

好了,现在对LayoutInflater的工作原理和流程也搞清楚了,你该满足了吧。额。。。。还嫌这个例子中的按钮看起来有点小,想要调大一些?那简单的呀,修改button_layout.xml中的代码,如下所示:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <Button xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="300dp"  
  3.     android:layout_height="80dp"  
  4.     android:text="Button" >  
  5.   
  6. </Button>  
这里我们将按钮的宽度改成300dp,高度改成80dp,这样够大了吧?现在重新运行一下程序来观察效果。咦?怎么按钮还是原来的大小,没有任何变化!是不是按钮仍然不够大,再改大一点呢?还是没有用!

其实这里不管你将Button的layout_width和layout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height

再来看一下我们的button_layout.xml吧,很明显Button这个控件目前不存在于任何布局当中,所以layout_width和layout_height这两个属性理所当然没有任何作用。那么怎样修改才能让按钮的大小改变呢?解决方法其实有很多种,最简单的方式就是在Button的外面再嵌套一层布局,如下所示:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     android:layout_width="match_parent"  
  3.     android:layout_height="match_parent" >  
  4.   
  5.     <Button  
  6.         android:layout_width="300dp"  
  7.         android:layout_height="80dp"  
  8.         android:text="Button" >  
  9.     </Button>  
  10.   
  11. </RelativeLayout>  
可以看到,这里我们又加入了一个RelativeLayout,此时的Button存在与RelativeLayout之中,layout_width和layout_height属性也就有作用了。当然,处于最外层的RelativeLayout,它的layout_width和layout_height则会失去作用。现在重新运行一下程序,结果如下图所示:

                      

OK!按钮的终于可以变大了,这下总算是满足大家的要求了吧。


所以,再回过头去看一下三个参数的意义:

Inflate(resId , null )只创建temp ,返回temp

Inflate(resId , parent, false )创建temp,然后执行temp.setLayoutParams(params);返回temp

Inflate(resId , parent, true )创建temp,然后执行root.addView(temp,params);最后返回root

Inflate(resId , null )不能正确处理宽和高是因为:layout_width,layout_height是相对父级设置的,而此时tempgetLayoutParamsnull

Inflate(resId , parent,false )可以正确处理,因为temp.setLayoutParams(params);这个params正是root.generateLayoutParams(attrs);得到的。

Inflate(resId , parent,true )不仅能够正确的处理,而且已经resId这个view加入到了parent,并且返回的是parent,第三种方式和以上两者返回值有绝对的区别。这个区别将会在为ListView膨胀Item时产生重大影响。

一个特别简单的ListView,每个Item中放一个按钮:

Activity的布局文件:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <ListView xmlns:android="http://schemas.android.com/apk/res/android"  
  2. xmlns:tools="http://schemas.android.com/tools"  
  3. android:id="@+id/id_listview"  
  4. android:layout_width="fill_parent"  
  5. android:layout_height="wrap_content" >  
  6. </ListView>  
ListView的Item的布局文件:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <Button xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/id_btn"  
  4.     android:layout_width="120dp"  
  5.     android:layout_height="120dp" >  
  6.   
  7. </Button>  

ListView的适配器:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package com.example.zhy_layoutinflater;  
  2.   
  3. import java.util.List;  
  4.   
  5. import android.content.Context;  
  6. import android.view.LayoutInflater;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9. import android.widget.BaseAdapter;  
  10. import android.widget.Button;  
  11.   
  12. public class MyAdapter extends BaseAdapter  
  13. {  
  14.   
  15.     private LayoutInflater mInflater;  
  16.     private List<String> mDatas;  
  17.   
  18.     public MyAdapter(Context context, List<String> datas)  
  19.     {  
  20.         mInflater = LayoutInflater.from(context);  
  21.         mDatas = datas;  
  22.     }  
  23.   
  24.     @Override  
  25.     public int getCount()  
  26.     {  
  27.         return mDatas.size();  
  28.     }  
  29.   
  30.     @Override  
  31.     public Object getItem(int position)  
  32.     {  
  33.         return mDatas.get(position);  
  34.     }  
  35.   
  36.     @Override  
  37.     public long getItemId(int position)  
  38.     {  
  39.         return position;  
  40.     }  
  41.   
  42.     @Override  
  43.     public View getView(int position, View convertView, ViewGroup parent)  
  44.     {  
  45.   
  46.         ViewHolder holder = null;  
  47.         if (convertView == null)  
  48.         {  
  49.             holder = new ViewHolder();  
  50.             convertView = mInflater.inflate(R.layout.item, null);  
  51. //          convertView = mInflater.inflate(R.layout.item, parent ,false);  
  52. //          convertView = mInflater.inflate(R.layout.item, parent ,true);  
  53.             holder.mBtn = (Button) convertView.findViewById(R.id.id_btn);  
  54.             convertView.setTag(holder);  
  55.         } else  
  56.         {  
  57.             holder = (ViewHolder) convertView.getTag();  
  58.         }  
  59.   
  60.         holder.mBtn.setText(mDatas.get(position));  
  61.   
  62.         return convertView;  
  63.     }  
  64.   
  65.     private final class ViewHolder  
  66.     {  
  67.         Button mBtn;  
  68.     }  
  69. }  

主Activity:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package com.example.zhy_layoutinflater;  
  2.   
  3. import java.util.Arrays;  
  4. import java.util.List;  
  5.   
  6. import android.app.Activity;  
  7. import android.os.Bundle;  
  8. import android.widget.ListView;  
  9.   
  10. public class MainActivity extends Activity  
  11. {  
  12.   
  13.     private ListView mListView;  
  14.     private MyAdapter mAdapter;  
  15.     private List<String> mDatas = Arrays.asList("Hello""Java""Android");  
  16.   
  17.     @Override  
  18.     protected void onCreate(Bundle savedInstanceState)  
  19.     {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.   
  23.         mListView = (ListView) findViewById(R.id.id_listview);  
  24.         mAdapter = new MyAdapter(this, mDatas);  
  25.         mListView.setAdapter(mAdapter);  
  26.   
  27.   
  28.     }  
  29.   
  30. }  
好了,相信大家对这个例子都再熟悉不过了,没啥好说的,我们主要关注getView里面的inflate那行代码:下面我依次把getView里的写成:
1、convertView = mInflater.inflate(R.layout.item, null);
2、convertView = mInflater.inflate(R.layout.item, parent ,false);
3、convertView = mInflater.inflate(R.layout.item, parent ,true);
分别看效果图:

图1:


图2:


图3:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. FATAL EXCEPTION: main  
  2. java.lang.UnsupportedOperationException:   
  3. addView(View, LayoutParams) is not supported in AdapterView  

嗯,没错没有图3,第三种写法会报错。

由上面三行代码的变化,产生3个不同的结果,可以看到

inflater(resId, null )的确不能正确处理宽高的值,但是inflater(resId,parent,false)并非和inflater(resId, null )效果一致,它可以看出完美的显示了宽和高。

而inflater(resId,parent,true)报错了

产生错误的原因是,第三种写法会将Item作为子View直接添加到作为rootListView中。ListViewAdapterView的子类,而AdapterView的源码中有如下内容:



[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2.   public void addView(View child) {  
  3.         throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");  
  4.   }  

可以看到这个错误为啥产生了。


平时在Activity中指定布局文件的时候,最外层的那个布局是可以指定大小的呀,layout_width和layout_height都是有作用的。确实,这主要是因为,在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果。那么我们来证实一下吧,修改MainActivity中的代码,如下所示:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class MainActivity extends Activity {  
  2.   
  3.     private LinearLayout mainLayout;  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         mainLayout = (LinearLayout) findViewById(R.id.main_layout);  
  10.         ViewParent viewParent = mainLayout.getParent();  
  11.         Log.d("TAG""the parent of mainLayout is " + viewParent);  
  12.     }  
  13.   
  14. }  
可以看到,这里通过findViewById()方法,拿到了activity_main布局中最外层的LinearLayout对象,然后调用它的getParent()方法获取它的父布局,再通过Log打印出来。现在重新运行一下程序,结果如下图所示:

 

非常正确!LinearLayout的父布局确实是一个FrameLayout,而这个FrameLayout就是由系统自动帮我们添加上的。

说到这里,虽然setContentView()方法大家都会用,但实际上Android界面显示的原理要比我们所看到的东西复杂得多。任何一个Activity中显示的界面其实主要都由两部分组成,标题栏和内容布局。标题栏就是在很多界面顶部显示的那部分内容,比如刚刚我们的那个例子当中就有标题栏,可以在代码中控制让它是否显示。而内容布局就是一个FrameLayout,这个布局的id叫作content,我们调用setContentView()方法时所传入的布局其实就是放到这个FrameLayout中的,这也是为什么这个方法名叫作setContentView(),而不是叫setView()。

最后再附上一张Activity窗口的组成图吧,以便于大家更加直观地理解:

            

上面我根据源码得出的结论可能大家还是有一丝的迷惑,我再写个例子论证我们上面得出的结论:

主布局文件:
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <Button xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:id="@+id/id_btn"  
  4.     android:layout_width="120dp"  
  5.     android:layout_height="120dp"  
  6.     android:text="Button" >  
  7. </Button>  

主Activity:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package com.example.zhy_layoutinflater;  
  2.   
  3. import android.app.ListActivity;  
  4. import android.os.Bundle;  
  5. import android.util.Log;  
  6. import android.view.LayoutInflater;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9.   
  10. public class MainActivity extends ListActivity  
  11. {  
  12.   
  13.   
  14.     private LayoutInflater mInflater;  
  15.   
  16.     @Override  
  17.     protected void onCreate(Bundle savedInstanceState)  
  18.     {  
  19.         super.onCreate(savedInstanceState);  
  20.   
  21.         mInflater = LayoutInflater.from(this);  
  22.   
  23.         View view1 = mInflater.inflate(R.layout.activity_main, null);  
  24.         View view2 = mInflater.inflate(R.layout.activity_main,  
  25.                 (ViewGroup)findViewById(android.R.id.content), false);  
  26.         View view3 = mInflater.inflate(R.layout.activity_main,  
  27.                 (ViewGroup)findViewById(android.R.id.content), true);  
  28.   
  29.         Log.e("TAG""view1 = " + view1  +" , view1.layoutParams = " + view1.getLayoutParams());  
  30.         Log.e("TAG""view2 = " + view2  +" , view2.layoutParams = " + view2.getLayoutParams());  
  31.         Log.e("TAG""view3 = " + view3  );  
  32.   
  33.     }  
  34.   
  35. }  

可以看到我们的主Activity并没有执行setContentView,仅仅执行了LayoutInflater的3个方法。
注:parent我们用的是Activity的内容区域:即android.R.id.content,是一个FrameLayout,我们在setContentView(resId)时,其实系统会自动为了包上一层FrameLayout(id=content)。

按照我们上面的说法:

view1的layoutParams 应该为null
view2的layoutParams 应该不为null,且为FrameLayout.LayoutParams
view3为FrameLayout,且将这个button添加到Activity的内容区域了(因为R.id.content代表Actvity内容区域)

下面看一下输出结果,和Activity的展示:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. 07-27 14:17:36.703: E/TAG(2911): view1 = android.widget.Button@429d1660 , view1.layoutParams = null  
  2. 07-27 14:17:36.703: E/TAG(2911): view2 = android.widget.Button@42a0e120 , view2.layoutParams = android.widget.FrameLayout$LayoutParams@42a0e9a0  
  3. 07-27 14:17:36.703: E/TAG(2911): view3 = android.widget.FrameLayout@42a0a240  

效果图:

可见,虽然我们没有执行setContentView,但是依然可以看到绘制的控件,是因为

View view3 = mInflater.inflate(R.layout.activity_main,(ViewGroup)findViewById(android.R.id.content), true);

这个方法内部已经执行了root.addView(temp , params); 上面已经解析过了。

也可以看出:和我们的推测完全一致,到此已经完全说明了inflate3个重载的方法的区别。相信大家以后在使用时也能选择出最好的方式。不过下面准备从ViewGroup和View的角度来说一下,为啥layoutParams为null,就不能这确的处理。

从ViewGroup和View的角度来解析

如果大家对自定义ViewGroup和自定义View有一定的掌握,肯定不会对onMeasure方法陌生:
ViewGroup的onMeasure方法所做的是:

为childView设置测量模式和测量出来的值。

如何设置呢?就是根据LayoutParams。
如果childView的宽为:LayoutParams. MATCH_PARENT,则设置模式为MeasureSpec.EXACTLY,且为childView计算宽度。
如果childView的宽为:固定值(即大于0),则设置模式为MeasureSpec.EXACTLY,且将lp.width直接作为childView的宽度。
如果childView的宽为:LayoutParams. WRAP_CONTENT,则设置模式为:MeasureSpec.AT_MOST
高度与宽度类似。

View的onMeasure方法:
主要做的就是根据ViewGroup传入的测量模式和测量值,计算自己应该的宽和高:
一般是这样的流程:
如果宽的模式是AT_MOST:则自己计算宽的值。
如果宽的模式是EXACTLY:则直接使用MeasureSpec.getSize(widthMeasureSpec);

对于最后一块,如果不清楚,不要紧,以后我会在自定义ViewGroup和自定义View时详细讲解的。

大概就是这样的流程,真正的绘制过程肯定比这个要复杂,这里就是为了说明如果View的宽和高如果设置为准确值,还一定要依赖于LayoutParams才可以将我们设置的值真正的应用到View上,所以我们的inflate(resId,null)中,被膨胀的视图的LayoutParams如果为null,是不能正确处理宽和高的。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值