Android布局加载之LayoutInflater示例详解
发布时间 - 2026-01-11 00:22:55 点击率:次前言

Activity 在界面创建时需要将 XML 布局文件中的内容加载进来,正如我们在 ListView 或者 RecyclerView 中需要将 Item 的布局加载进来一样,都是使用 LayoutInflater 来进行操作的。
LayoutInflater 实例的获取有多种方式,但最终是通过(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)来得到的,也就是说加载布局的 LayoutInflater 是来自于系统服务的。
由于 Android 系统源码中关于 Content 部分采用的是装饰模式,Context 的具体功能都是由 ContextImpl 来实现的。通过在 ContextImpl 中找到getSystemService的代码,一路跟进,得知最后返回的实例是PhoneLayoutInflater。
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
LayoutInflater 只是一个抽象类,而 PhoneLayoutInflater 才是具体的实现类。
inflate 方法加载 View
使用 LayoutInflater 时常用方法就是inflate方法了,将一个布局文件 ID 传入并最后解析成一个 View 。
LayoutInflater 加载布局的 inflate 方法也有多种重载形式:
View inflate(@LayoutRes int resource, @Nullable ViewGroup root) View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
而这两者的差别就在于是否要将 resource 布局文件加载到 root布局中去。
不过有点需要注意的地方,若 root为 null,则在 xml 布局中为 resource设置的属性会失效,只是单纯的加载布局。
// temp 是 xml 布局中的顶层 View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) { // root
// root 不为 null 才会生成 layoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 如果不添加到 root 中,则直接把布局参数设置给 temp
temp.setLayoutParams(params);
}
}
// 加载子 View
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
root.addView(temp, params);//添加到布局中,则布局参数用到 addView 中去
}
if (root == null || !attachToRoot) {
result = temp;
}
跟进createViewFromTag方法查看 View 是如何创建出来的。
View view; // 最后要返回的 View
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs); // 是否设置了 Factory2
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs); // 是否设置了 Factory
} else {
view = null;
}
if (view == null && mPrivateFactory != null) { // 是否设置了 PrivateFactory
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) { // 如果的 Factory 都没有设置过,最后在生成 View
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) { // 系统控件
view = onCreateView(parent, name, attrs);
} else { // 非系统控件,自定义的 View
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
如果设置过 Factory 接口,那么将由 Factory 中的 onCreateView 方法来生成 View 。
关于 LayoutInflater.Factory 的作用,就是用来在加载布局时可以自行去创建 View,抢在系统创建 View 之前去创建。
关于 LayoutInflater.Factory 的使用场景,现在比较多的就是应用的换肤了。
若没有设置过 Factory 接口,则是判断是否为自定义控件或者系统控件,不管是 onCreateView 方法还是 createView 方法,内部最终都是调用到了 createView 方法,通过它来生成 View 。
// 通过反射生成 View 的参数,分别是 Context 和 AttributeSet 类
static final Class<?>[] mConstructorSignature = new Class[] {
Context.class, AttributeSet.class};
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
if (constructor == null) { // 从缓存中得到 View 的构造器,没有则调用 getConstructor
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) { // 过滤,是否允许生成该 View
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs); // 不允许生成该 View
}
}
}
Object[] args = mConstructorArgs;
args[1] = attrs;
final View view = constructor.newInstance(args); // 通过反射生成 View
return view;
在 createView 方法内部,首先从 View 的构造器缓存中查找是否有对应的缓存,若没有则生成构造器并且放到缓存中去,若有构造器则看能否通过过滤,是否允许该 View 生成。
最后都满足条件的则是通过 View 的构造器反射生成了 View 。
在生成 View 时采用 Constructor.newInstance调用构造函数,而参数所需要的变量就是mConstructorSignature变量所定义的,分别是 Context 和 AttributeSet。可以看到,在最后生成 View 时也传入了对应的参数。
采用 Constructor.newInstance的形式反射生成 View ,是为了解耦,只需要有了类名,就可以加载出来。
由此可见,LayoutInflater 加载布局仍然是需要传递 Context的,不光是为了得到 LayoutInflater ,在反射生成 View 时同样会用到。
深度遍历加载布局
如果需要加载的布局只有一个控件,那么 LayoutInflater 返回那个 View 工作也就结束了。
若布局文件中有多个需要加载的 View ,则通过rInflateChildren方法继续加载顶层 View 下的 View ,最后通过rInflate方法来加载。
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
// 若 while 条件不成立,则加载结束了
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(); // 从 XmlPullParser 中得到 name 出来解析
if (TAG_REQUEST_FOCUS.equals(name)) { // name 各种情况下的解析
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true); // 继续遍历
viewGroup.addView(view, params); // 顶层 View 添加 子 View
}
}
if (finishInflate) { // 遍历解析
parent.onFinishInflate();
}
}
rInflate方法首先判断是否解析结束了,若没有,则从 XmlPullParser 中加载出下一个 View 进行处理,中间还会对不同的类型进行处理,比如TAG_REQUEST_FOCUS、TAG_TAG、TAG_INCLUDE、TAG_MERGE等等。
最后仍然还是通过createViewFromTag来生成 View ,并以这个生成的 View 为父节点,开始深度遍历,继续调用rInflateChildren方法加载布局,并把这个 View 加入到它的父 View 中去。
至于为什么生成 View 的方法名字createViewFromTag从字面上来看是来自于 Tag标签,想必是和 XmlPullParser解析布局生成的内容有关。
总结
以上就是这篇文章的全部内容了,希望本文的内容对各位Android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。
# layoutinflater用法
# layoutinflater的详解
# layoutinflater
# Android LayoutInflater.inflate源码分析
# Android 老生常谈LayoutInflater的新认知
# Android中使用LayoutInflater要注意的一些坑
# Android LayoutInflater加载布局详解及实例代码
# Android LayoutInflater深入分析及应用
# Android开发中LayoutInflater用法详解
# 基于Android LayoutInflater的使用介绍
# Android用于加载xml的LayoutInflater源码超详细分析
# 加载
# 遍历
# 中去
# 都是
# 则是
# 自定义
# 来自于
# 要将
# 结束了
# 方法来
# 的是
# 判断是否
# 也有
# 也就
# 多个
# 才是
# 是由
# 才会
# 还会
# 中有
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel怎么发送邮件_Laravel Mail类SMTP配置教程
如何在云虚拟主机上快速搭建个人网站?
Laravel如何获取当前登录用户信息_Laravel Auth门面使用与Session用户读取【技巧】
制作旅游网站html,怎样注册旅游网站?
Laravel如何创建和注册中间件_Laravel中间件编写与应用流程
Win11怎么查看显卡温度 Win11任务管理器查看GPU温度【技巧】
UC浏览器如何切换小说阅读源_UC浏览器阅读源切换【方法】
教你用AI润色文章,让你的文字表达更专业
如何快速生成凡客建站的专业级图册?
bing浏览器学术搜索入口_bing学术文献检索地址
java ZXing生成二维码及条码实例分享
Python文件异常处理策略_健壮性说明【指导】
*服务器网站为何频现安全漏洞?
如何用VPS主机快速搭建个人网站?
Laravel如何使用Sanctum进行API认证?(SPA实战)
详解CentOS6.5 安装 MySQL5.1.71的方法
如何在云指建站中生成FTP站点?
Laravel如何发送邮件和通知_Laravel邮件与通知系统发送步骤
php中::能调用final静态方法吗_final修饰静态方法调用规则【解答】
详解jQuery停止动画——stop()方法的使用
php读取心率传感器数据怎么弄_php获取max30100的心率值【指南】
网站建设要注意的标准 促进网站用户好感度!
如何用PHP快速搭建CMS系统?
教你用AI将一段旋律扩展成一首完整的曲子
Laravel怎么生成URL_Laravel路由命名与URL生成函数详解
实现点击下箭头变上箭头来回切换的两种方法【推荐】
如何在宝塔面板中修改默认建站目录?
简历没回改:利用AI润色让你的文字更专业
Laravel 419 page expired怎么解决_Laravel CSRF令牌过期处理
如何在阿里云购买域名并搭建网站?
laravel怎么为API路由添加签名中间件保护_laravel API路由签名中间件保护方法
利用python获取某年中每个月的第一天和最后一天
香港代理服务器配置指南:高匿IP选择、跨境加速与SEO优化技巧
Laravel怎么调用外部API_Laravel Http Client客户端使用
Laravel如何使用查询构建器?(Query Builder高级用法)
如何快速搭建自助建站会员专属系统?
如何在服务器上三步完成建站并提升流量?
如何基于PHP生成高效IDC网络公司建站源码?
如何自定义建站之星网站的导航菜单样式?
如何在香港免费服务器上快速搭建网站?
Swift中循环语句中的转移语句 break 和 continue
潮流网站制作头像软件下载,适合母子的网名有哪些?
香港服务器建站指南:外贸独立站搭建与跨境电商配置流程
大连网站制作公司哪家好一点,大连买房网站哪个好?
Laravel如何处理文件上传_Laravel Storage门面实现文件存储与管理
北京网站制作的公司有哪些,北京白云观官方网站?
公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?
Python自然语言搜索引擎项目教程_倒排索引查询优化案例
Win11怎么更改系统语言为中文_Windows11安装语言包并设为显示语言
Laravel事件监听器怎么写_Laravel Event和Listener使用教程

