LayoutInflater技术广泛应用于需要动态添加View的时候,比如在ScrollView和ListView中,经常都可以看到LayoutInflater的身影。
一 获取LayoutInflater实例 LayoutInflater的基本用法:
首先需要获取到LayoutInflater的实例,有两种方法可以获取到,第一种写法如下:
1 LayoutInflater layoutInflater = LayoutInflater.from(context);
另外一种写法:
1 LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
第三种写法:通过activity中的getLayoutInflater()方法:
1 LayoutInflater inflater = getLayoutInflater();
这三种方式本质都是调用Context.getSystemService()。
getLayoutInflater()源码分析
Activity 的 getLayoutInflater() 方法是调用 PhoneWindow 的getLayoutInflater()方法,看一下该源代码:
1 2 3 4 5 public PhoneWindow(Context context) { super(context); //可以看出它其实是调用 LayoutInflater.from(context)。 mLayoutInflater = LayoutInflater.from(context); }
LayoutInflater.from(context)源码分析
1 2 3 4 5 6 7 public static LayoutInflater from(Context context) { LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (LayoutInflater == null) { throw new AssertionError("LayoutInflater not found."); } return LayoutInflater; }
LayoutInflater.from(context)内部是调用 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)获得布局加载器.
二 inflate方法 所有inflate方法: 得到了LayoutInflater的实例之后就可以调用它的inflate()方法来加载布局了,LayoutInflater类提供的下面四种inflate方法:
public View inflate (int resource, ViewGroup root)
public View inflate (int resource, ViewGroup root, boolean attachToRoot)
public View inflate (XmlPullParser parser, ViewGroup root)
public View inflate (XmlPullParser parser, ViewGroup root, boolean attachToRoot)
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public View inflate(int resource, ViewGroup root) { //如果root不为null,attachToRoot为true,否则attachToRoot为false return inflate(resource, root, root != null); } public View inflate(int resource, ViewGroup root, boolean attachToRoot) { if (DEBUG) System.out.println("INFLATING from resource: " + resource); XmlResourceParser parser = getContext().getResources().getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
inflate的调用关系:
1 layoutInflater.inflate(resourceId, root);
inflate()方法一般接收两个参数,第一个参数就是要加载的布局id,第二个参数是指给该布局的外部再嵌套一层父布局 ,如果不需要就直接传null。这样就成功成功创建了一个布局的实例,之后再将它添加到指定的位置就可以显示出来了。
使用inflate的示例代码: activity_main.xml
1 2 3 4 5 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="match_parent" > </LinearLayout>
button_layout.xml
1 2 3 4 5 6 <Button xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" > </Button>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class MainActivity extends Activity { private LinearLayout mainLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mainLayout = (LinearLayout) findViewById(R.id.main_layout); LayoutInflater layoutInflater = LayoutInflater.from(this); View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null); mainLayout.addView(buttonLayout); } }
结果如下图所示:
inflate()方法源码分析: 不管你是使用的哪个inflate()方法的重载,最终都会辗转调用到LayoutInflater的如下代码中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); mConstructorArgs[0] = mContext; View result = root; try { int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); 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, attrs); } else { View temp = createViewFromTag(name, attrs); ViewGroup.LayoutParams params = null; if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { temp.setLayoutParams(params); } } rInflate(parser, temp, attrs); if (root != null && attachToRoot) { root.addView(temp, params); } if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { InflateException ex = new InflateException(e.getMessage()); ex.initCause(e); throw ex; } catch (IOException e) { InflateException ex = new InflateException( parser.getPositionDescription() + ": " + e.getMessage()); ex.initCause(e); throw ex; } return result; } }
LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。第23行,调用了createViewFromTag()这个方法,并把节点名和参数传了进去。它是用于根据节点名来创建View对象的。在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。
这只是创建出了一个根布局的实例而已,接下来会在第31行调用rInflate()方法来循环遍历这个根布局下的子元素,代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(name, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs); viewGroup.addView(view, params); } } parent.onFinishInflate(); }
第21行同样是createViewFromTag()方法来创建View的实例,然后还会在第24行递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。
这样的话,把整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。
分析inflate(int resource, ViewGroup root, boolean attachToRoot)第三个参数作用: inflate()方法还有个接收三个参数的方法重载,结构如下:
1 inflate(int resource, ViewGroup root, boolean attachToRoot)
接下来分析第三个参数attachToRoot作用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // 这里加载我们设置的resource,临时标记为temp final View temp = createViewFromTag(root, name, attrs, false); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // 如果root不是null,会根据resource最外面的layout创建layout params来匹配root(如果需要) params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 如果attachToRoot为false,为temp设置布局 temp.setLayoutParams(params); } ...... //如果root != null && attachToRoot,把temp添加到root中,并设置params if (root != null && attachToRoot) { root.addView(temp, params); } //如果root == null || !attachToRoot,直接返回temp。 if (root == null || !attachToRoot) { result = temp; } ...... return result;
代码分析:
第1段代码:动态载入resource:
1 final View temp = createViewFromTag(root, name, attrs, false)
第2段代码:创建ViewGroup的LayoutParams
1 2 ViewGroup.LayoutParams params = null; params = root.generateLayoutParams(attrs);
第3段代码:当root不为空,attachToRoot为false时,为temp设置layout属性,当该view以后被添加到父view当中时,这些layout属性会自动生效
1 2 3 4 5 6 if (root != null) { params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 如果attachToRoot为false,为temp设置布局 temp.setLayoutParams(params); }
第4段代码:
1 2 3 4 5 6 7 8 9 10 11 // 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; }
只有当root != null && attachToRoot)时,root会把temp添加到root中,给加载的布局文件的指定一个父布局,即root。否则temp不会添加到root,即root == null || !attachToRoot时,最后直接return temp。
第三个参数attachToRoot作用:
如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。
如果root不为null,attachToRoot设为true,root会把temp添加到root中,此时在temp布局文件中的根view的layout属性会生效。
如果root不为null,attachToRoot设为false,此时view并没有添加到root,但是view的layout属性被保存了下来,以后如果调用addView(View child),layout属性会自动生效。可以见下面源码:ViewGroup
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void addView(View child, int index) { if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } //这里便是从child中获取LayoutParams LayoutParams params = child.getLayoutParams(); if (params == null) { params = generateDefaultLayoutParams(); if (params == null) { throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null"); } } //这段代码是不是和刚才inflate中代码很相似,对,这里就是把view和layout属性添加到parent中。 addView(child, index, params); }
注1:View类也提供了两个静态方法,作用一样
1 2 View.inflate(int resource, ViewGroup root); View.inflate(int resource, ViewGroup root, boolean attachToRoot);
三 inflate的布局文件的layout属性失效情景 在应用中碰到过这样的场景吗?看看下面的代码,为Listview简单地载入一个布局文件: R.layout.item_row
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="?android:attr/listPreferredItemHeight" android:gravity="center_vertical" android:orientation="horizontal"> <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingRight="15dp" android:text="Text1"/> <TextView android:id="@+id/text2" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="Text2"/> </LinearLayout>
这里我们想把高度设置为固定高度,上面把它设为当前主题下的推荐高度……看似很合理。 但是,当我们这样载入布局文件的时候,就不对了:
1 2 3 4 5 6 7 publicView getView(intposition, View convertView, ViewGroup parent) { if(convertView == null) { convertView = inflate(R.layout.item_row, null); } returnconvertView; }
然后结果就变成这样了:
为什么会出现这种情况,通过前面的分析,已经很清楚了。 而如果这样载入布局的话就没有问题:
1 2 3 4 5 6 7 publicView getView(intposition, View convertView, ViewGroup parent) { if(convertView == null) { convertView = inflate(R.layout.item_row, parent, false); } returnconvertView; }
这样我们就得到了想要的结果:
注:本文是参考以下文章所做的总结性的学习的文章,主要供自己学习所用,如果转载的话请联系以下博客的作者,在此对相关作者表示感谢,附录相关参考文章的链接:
Android LayoutInflater原理分析,带你一步步深入了解View(一)
LayoutInflater详解
Layout Inflation不能这么用
ViewGroup generateLayoutParams() 方法的作用
上一篇:布局优化
下一篇:Thread、AsycTask、HandlerThread、IntentService的使用场景与特点