LayoutInflaterlayoutInflater=LayoutInflater.from(context);
当然,还有另一种写法也能达到同样的效果:
LayoutInflaterlayoutInflater=(LayoutInflater) 上下文
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);其实第一种方法是第二种方法的简单写法,只不过Android已经帮我们封装好了。获得LayoutInflater的实例后,可以调用其inflate()方法来加载布局,如下所示:
layoutInflater.inflate(resourceId, root);第一个参数是加载的布局id,第二个参数是在布局外面嵌套一层父布局。如果不需要,直接传null。这样就成功创建了布局的实例,然后将其添加到指定位置以显示它。
我们通过一个非常简单的例子来更直观的了解一下LayoutInflater的用法。例如,目前有一个项目,其中MainActivity对应的布局文件名为activity_main.xml。代码如下:
?xml version="1.0"encoding="utf-8" ?这个布局文件的内容非常简单。只有一个空的LinearLayout,里面没有任何控件,所以界面上不应该显示任何内容。
然后我们将定义一个布局文件并将其命名为button_layout.xml。代码如下:
这个布局文件也很简单,只有一个Button按钮。现在我们要弄清楚如何通过LayoutInflater将button_layout布局添加到主布局文件的LinearLayout中。根据刚才介绍的用法,修改MainActivity中的代码如下:
公共类MainActivity 扩展Activity {
私有LinearLayout mainLayout;
@覆盖
protected void onCreate(Bundle savingInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainLayout=(LinearLayout) findViewById(R.id.main_layout);
LayoutInflaterlayoutInflater=LayoutInflater.from(this);
查看buttonLayout=layoutInflater.inflate(R.layout.button_layout, null);
mainLayout.addView(buttonLayout);
}
}可以看到,首先获取了LayoutInflater的实例,然后调用其inflate()方法加载button_layout布局,最后调用LinearLayout的addView()方法将其添加到LinearLayout中。
现在可以运行程序了,结果如下图:
HUAWEI-JKM-AL00b-2019-12-26-14-24-14.jpg 我们使用LayoutInflater将button_layout布局添加到LinearLayout中。当需要动态添加View时,LayoutInflater技术被广泛使用。例如,在ScrollView和ListView中,经常可以看到LayoutInflater。
当然,仅仅介绍如何使用LayoutInflater显然远远不能满足大家的求知欲。要知道它,你还必须知道为什么。接下来我们从源码的角度看一下LayoutInflater是如何工作的。
无论你使用哪种重载的inflate() 方法,最终都会调用到LayoutInflater 的以下代码:
公共视图膨胀(XmlPullParser解析器,ViewGroup根,布尔attachToRoot){
同步(mConstructorArgs){
最终AttributeSet attrs=Xml.asAttributeSet(parser);
mConstructorArgs[0]=mContext;
查看结果=根;
尝试{
int 类型;
while ((type=parser.next()) !=XmlPullParser.START_TAG
类型!=XmlPullParser.END_DOCUMENT) {
}
如果(类型!=XmlPullParser.START_TAG){
抛出新的InflateException(parser.getPositionDescription()
+ ": 未找到开始标记!");
}
最终字符串名称=parser.getName();
if (TAG_MERGE.equals(name)) {
if (root==null || !attachToRoot) {
throw new InflateException("merge 只能与有效的"
+ "ViewGroup 根和attachToRoot=true");
}
rInflate(解析器,根,属性);
} 别的{
视图temp=createViewFromTag(name, attrs);
ViewGroup.LayoutParams 参数=null;
如果(根!=空){
params=root.generateLayoutParams(attrs);
如果(!attachToRoot){
temp.setLayoutParams(params);
}
}
rInflate(解析器,临时,属性);
如果(根!=null AttachToRoot){
root.addView(temp, params);
}
if (root==null || !attachToRoot) {
结果=温度;
}
}
} catch (XmlPullParserException e) {
InflateException ex=new InflateException(e.getMessage());
例如initCause(e);
扔前;
} catch (IOException e) {
InflateException ex=新的InflateException(
parser.getPositionDescription()
+ ":" + e.getMessage());
例如initCause(e);
扔前;
}
返回结果;
}
}从这里我们可以清楚地看到,LayoutInflater实际上是使用Android提供的pull解析方法来解析布局文件的。不熟悉pull解析方法的朋友可以网上搜索一下。教程很多,我就不详细说了。这里注意第23行,这里调用了createViewFromTag()方法,并传入了节点名和参数。看到这个方法名,我们应该能猜到它是用来根据节点名创建View对象的。确实,createView()方法是在createViewFromTag()方法内部调用的,然后使用反射来创建View的实例并返回它。
当然,这里我们只是创建根布局的实例。接下来,在第31 行调用rInflate() 方法来循环访问此根布局的子元素。代码如下:
private void rInflate(XmlPullParser解析器,视图父级,最终AttributeSet attrs)
抛出XmlPullParserException,IOException {
最终int 深度=parser.getDepth();
int 类型;
while (((type=parser.next()) !=XmlPullParser.END_TAG ||
parser.getDepth() 深度) 类型!=XmlPullParser.END_DOCUMENT) {
如果(类型!=XmlPullParser.START_TAG){
继续;
}
最终字符串名称=parser.getName();
如果(TAG_REQUEST_FOCUS.equals(名称)){
parseRequestFocus(解析器,父级);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth()==0) {
throw new InflateException("不能是根元素");
}
parseIninclude(解析器,父级,属性);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("必须是根元素");
} 别的{
最终视图视图=createViewFromTag(name, attrs);
最终ViewGroup viewGroup=(ViewGroup) 父级;
最终ViewGroup.LayoutParams params=viewGroup.generateLayoutParams(attrs);
rInflate(解析器,视图,属性);
viewGroup.addView(视图, 参数);
}
}
onFinishInflate();
当然,这里我们只是创建根布局的实例。接下来,将在第31 行调用rInflate() 方法来循环访问此根布局的子元素。代码如下:
private void rInflate(XmlPullParser解析器,视图父级,最终AttributeSet attrs)
抛出XmlPullParserException,IOException {
最终int 深度=parser.getDepth();
int 类型;
while (((type=parser.next()) !=XmlPullParser.END_TAG ||
parser.getDepth() 深度) 类型!=XmlPullParser.END_DOCUMENT) {
如果(类型!=XmlPullParser.START_TAG){
继续;
}
最终字符串名称=parser.getName();
如果(TAG_REQUEST_FOCUS.equals(名称)){
parseRequestFocus(解析器,父级);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth()==0) {
throw new InflateException("不能是根元素");
}
parseIninclude(解析器,父级,属性);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("必须是根元素");
} 别的{
最终视图视图=createViewFromTag(name, attrs);
最终ViewGroup viewGroup=(ViewGroup) 父级;
最终ViewGroup.LayoutParams params=viewGroup.generateLayoutParams(attrs);
rInflate(解析器,视图,属性);
viewGroup.addView(视图, 参数);
}
}
onFinishInflate();
}可以看到,第21行也是使用了createViewFromTag()方法创建了View的实例,然后第24行递归调用了rInflate()方法来查找这个View下的子元素。每次递归完成后,此View都会添加到父布局中。
这样的话,整个布局文件解析完毕后,就会形成一个完整的DOM结构,最终会返回顶层根布局,inflate()过程也就结束了。
细心的朋友可能会注意到,inflate()方法还有一个接收三个参数的方法重载。结构如下:
inflate(int资源,ViewGroup根,布尔值attachToRoot)
那么第三个参数attachToRoot是什么意思呢?其实,如果你仔细阅读上面的源码,你应该能够自己分析出答案。这里我先说一下结论。有兴趣的朋友可以再次阅读源码来验证我的结论是否正确。
如果root 为null,attachToRoot 将不起作用,设置任何值都没有意义。
如果root不为null并且attachToRoot设置为true,则加载的布局文件将被分配一个父布局,即root。
如果root不为null并且attachToRoot设置为false,则布局文件最外层的所有布局属性都会被设置。当视图添加到父视图时,这些布局属性将自动生效。
在不设置attachToRoot参数的情况下,如果root不为null,则attachToRoot参数默认为true。
好了,现在你已经弄清楚了LayoutInflater的工作原理和流程,你应该满意了。前额。仍然觉得这个例子中的按钮看起来有点小,想把它们做得更大?很简单,修改button_layout.xml中的代码如下:
这里我们将按钮的宽度更改为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外面再嵌套一层布局,如下图:
正如你所看到的,这里我们添加了另一个RelativeLayout。此时Button就存在于RelativeLayout中,layout_width和layout_height属性也发挥了作用。当然,最外面的RelativeLayout的layout_width和layout_height就会失去作用。现在重新运行程序,结果如下图:
图片.png
好的!按钮终于可以做大了,终于满足了大家的要求。
看到这里,也许有些朋友心中会有一个巨大的疑惑。不对呀!平时在Activity中指定布局文件的时候,最外层的那个布局是可以指定大小的呀,layout_width和layout_height都是有作用的。确实,这主要是因为,在setContentView()方法中,Android会自动在布局文件的最外层再嵌套一个FrameLayout,所以layout_width和layout_height属性才会有效果。那么我们来证实一下吧,修改MainActivity中的代码,如下所示:公共类MainActivity 扩展Activity {
私有LinearLayout mainLayout;
@覆盖
protected void onCreate(Bundle savingInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mainLayout=(LinearLayout) findViewById(R.id.main_layout);
ViewParent viewParent=mainLayout.getParent();
Log.d("TAG", "mainLayout的父级是" + viewParent);
}
}可以看到,这里我们通过findViewById()方法获取了activity_main布局中最外层的LinearLayout对象,然后调用其getParent()方法获取其父布局,然后通过Log打印出来。现在重新运行程序,结果如下图:
【深入解析Android LayoutInflater机制(第一部分)】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
想学习一下 Android 开发,这个博文感觉很有帮助。
有8位网友表示赞同!
LayoutInflater 真是个常用的东西啊,还没深入了解过它的原理,这次正好来补补课。
有10位网友表示赞同!
代码优化和性能都是很重要的东西,分析 LayoutInflater 的原理能给我带来一些启发。
有16位网友表示赞同!
看这个标题感觉很有深度啊,应该能学到不少干货知识。
有5位网友表示赞同!
Android 开发里遇到各种布局问题,希望能从这个博文中找到解决方法。
有17位网友表示赞同!
我一直以为 LayoutInflater 就这么用就好了,没想到原来还有那么多原理在里面。
有12位网友表示赞同!
对 Android 开发比较感兴趣,想了解一下不同组件之间的工作机制。
有8位网友表示赞同!
学习安卓开发需要系统性的理解,这个博文应该能帮我一步步了解布局系统的核心。
有13位网友表示赞同!
之前写 Android 代码的时候没有过多考虑 LayoutInflater 的原理,看完这个博文 hopefully 我可以写得更合理、更高效一些。
有7位网友表示赞同!
很多时候不知道为什么代码会跑出这种错误,分析它的原理能够让我更好理解 Android 开发机制。
有20位网友表示赞同!
想要写更简洁优美的 Android 程序,深入了解这些基础知识是必不可少的。
有14位网友表示赞同!
这个标题很有吸引力,期待作者能用通俗易懂的语言讲解。
有14位网友表示赞同!
我想从这个博文中学习到如何高效地使用 LayoutInflater 提高代码性能。
有13位网友表示赞同!
最近在学习 Android 开发,这个帖子正好可以帮我巩固我对布局系统的理解。
有19位网友表示赞同!
希望作者能详细解释 LayoutInflater 的具体工作流程和应用场景。
有5位网友表示赞同!
阅读源码分析原理,确实是提升 Android 开发水平的有效方法。
有16位网友表示赞同!
Android 系统开发越来越复杂,分析每个关键组件的工作方式至关重要。
有13位网友表示赞同!
学习完这个博文,希望能把这个知识运用到我的项目实践中去。
有16位网友表示赞同!
感谢作者分享这份知识!希望更多 Android 开发者都能受益于此。
有5位网友表示赞同!