关于LayoutInflater的错误用法

转自:http://www.cnblogs.com/kobe8/p/3859708.html

Layout inflation is the term used within the context of Android to indicate when an XML layout resource is parsed and converted into a hierarchy of View objects.

It’s common practice in the Android SDK, but you may be surprised to find that there is a wrong way to use LayoutInflater, and your application might be one of the offenders. If you’ve ever written something like the following code using LayoutInflater in your Android application:

inflater.inflate(R.layout.my_layout,  null );

PLEASE read on, because you’re doing it wrong and I want to explain to you why.

Get to Know LayoutInflater

Let’s first take a look at how LayoutInflater works. There are two usable versions of the inflate() method for a standard application:

inflate( int  resource, ViewGroup root)
inflate( int  resource, ViewGroup root,  boolean  attachToRoot)

The first parameter points to the layout resource you want to inflate. The second parameter is the root view of the hierarchy you are inflating the resource to attach to. When the third parameter is present, it governs whether or not the inflated view is attached to the supplied root after inflation.

It is these last two parameters that can cause a bit of confusion. With the two parameter version of this method, LayoutInflater will automatically attempt to attach the inflated view to the supplied root. However, the framework has a check in place that if you pass null for the root it bypasses this attempt to avoid an application crash.

Many developers take this behavior to mean that the proper way to disable attachment on inflation is by passing null as root; in many cases not even realizing that the three parameter version of inflate() exists. By doing things this way, we also disable another very important function the root view has…but I’m getting ahead of myself.

Examples from the Framework

Let’s examine some situations in Android where the framework expects you as a developer to interactively inflate portions of the view.

Adapters are the most common case for using LayoutInflater is custom ListView adapters overriding getView(), which has the following method signature:

getView( int  position, View convertView, ViewGroup parent)

Fragments also use inflation often when creating views via onCreateView(); notice its method signature:

onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

Have you noticed that every time the framework wants you to inflate a layout, they also pass you the parent ViewGroup it will eventually be attached to? Notice also that in most cases (including the above two examples), it will throw an Exception later on if LayoutInflater is allowed to automatically attach the inflated view to the root.

So why do you suppose we are given this ViewGroup if we are not supposed to attach to it? It turns out the parent view is a very important part of the inflation process because it is necessary in order to evaluate the LayoutParams declared in the root element of the XML being inflated. Passing nothing here is akin to telling the framework “I don’t know what parent this view will be attached to, sorry.”

The problem with this is android:layout_xxx attributes are always be evaluated in the context of the parent view. As a result, without any known parent, all LayoutParams you declared on the root element of your XML tree will just get thrown away, and then you’ll be left asking “why is the framework ignoring the layout customizations I defined? I’d better check SO and then file a bug.”

Without LayoutParams, the ViewGroup that eventually hosts the inflated layout is left to generate a default set for you. If you are lucky (and in many cases you are) these default parameters are the same as what you had in XML…masking the fact that something is amiss.

Application Example

So you claim you’ve never seen this happen in an application? Take the following simple layout that we want to inflate for a ListView row:

R.layout.item_row

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
< LinearLayout  xmlns: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 >

We want to set the height of our row to be a fixed height, in this case the preferred item height for the current theme…seems reasonable.

However, when we inflate this layout the wrong way

1
2
3
4
5
6
7
public  View getView( int  position, View convertView, ViewGroup parent) {
     if  (convertView ==  null ) {
         convertView = inflate(R.layout.item_row,  null );
     }
 
     return  convertView;
}

we end up with a result that looks like this

What happened to the fixed height we set?? This is usually where you end up setting the fixed height on all your child views, switching the root elements height to wrap_content, and move on without really knowing why it broke (you may have even cursed at Google in the process).

If we instead inflate the same layout this way

1
2
3
4
5
6
7
public  View getView( int  position, View convertView, ViewGroup parent) {
     if  (convertView ==  null ) {
         convertView = inflate(R.layout.item_row, parent,  false );
     }
 
     return  convertView;
}

we end up with what we expected in the first place.

Hooray!

Every Rule Has An Exception

There are of course instances where you can truly justify a null parent during inflation, but they are few. One such instance occurs when you are inflating a custom layout to be attached to an AlertDialog. Consider the following example where we want to use our same XML layout but set it as the dialog view:

1
2
3
4
5
6
7
AlertDialog.Builder builder =  new  AlertDialog.Builder(context);
View content = LayoutInflater.from(context).inflate(R.layout.item_row,  null );
 
builder.setTitle( "My Dialog" );
builder.setView(content);
builder.setPositiveButton( "OK" null );
builder.show();

The issue here is that AlertDialog.Builder supports a custom view, but does not provide an implementation of setView() that takes a layout resource; so you must inflate the XML manually. However, because the result will go into the dialog, which does not expose its root view (in fact, it doesn’t exist yet), we do not have access to the eventual parent of the layout, so we cannot use it for inflation. It turns out, this is irrelevant, because AlertDialog will erase any LayoutParams on the layout anyway and replace them with match_parent.

So the next time your fingers are tempted to just type null into inflate(), you should stop and ask yourself “do I really not know where this view will end up?”

Bottom line, you should think of the two parameter version of inflate() as a convenience shortcut to omit true as the third paramter. You should not think of passing null as a convenience shortcut to omit false.


<think>我们正在讨论LayoutInflater.from(context)方法的作用和使用示例。 根据引用[2]和引用[3],LayoutInflater.from(context)是获取LayoutInflater实例的三种方式之一。 引用[4]展示了在自定义View中使用LayoutInflater.from(context).inflate()的示例。 用户的问题:我想理解或获取关于 `LayoutInflater.from(mContext)` 的使用方法或作用 回答步骤: 1. 解释LayoutInflater.from(context)的作用:它是一个静态方法,用于获取LayoutInflater的实例。 2. 说明参数:需要传入一个Context对象(如Activity、Application等)。 3. 与其他获取方式对比:引用[2]提到三种方式,其中LayoutInflater.from(context)是最简洁的方式。 4. 使用示例:展示如何使用获取到的LayoutInflater实例来加载布局。 注意:引用[4]中详细展示了inflate方法的使用,特别是第二个参数(root)和第三个参数(attachToRoot)的不同效果。 我们还可以结合用户之前的问题(关于convertView的inflate)来进一步说明在适配器中的使用。 因此,我们可以这样组织回答: - 作用 - 获取方式对比 - 使用示例(包括在自定义View和适配器中的使用) - 注意事项(如attachToRoot参数) 同时,根据用户之前的对话,用户可能是在适配器的getView方法中使用,所以可以结合适配器的场景说明。 最后,生成相关问题。</think>### `LayoutInflater.from(mContext)` 详解 `LayoutInflater.from(mContext)` 是 Android 中获取 `LayoutInflater` 实例的标准方式,用于将 XML 布局文件转换为实际的 `View` 对象。以下是核心要点: #### 一、作用与原理 1. **作用** 通过 `Context` 创建 `LayoutInflater` 实例,用于动态加载 XML 布局资源。 2. **内部实现** 等价于: ```java LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); ``` 本质是通过系统服务获取布局解析器实例[^2][^3]。 3. **优势** - **简洁性**:相比 `getSystemService()` 更简洁 - **通用性**:可在任何拥有 `Context` 的地方使用(Activity/Fragment/Adapter等) - **安全性**:避免直接操作系统服务 #### 二、使用示例 ##### 1. 基础用法(加载单个视图) ```java // 创建布局解析器 LayoutInflater inflater = LayoutInflater.from(mContext); // 加载布局(不立即附加到父视图) View customView = inflater.inflate( R.layout.custom_layout, // 布局资源ID parentView, // 父容器(用于继承布局参数) false // 不立即附加 ); // 手动添加到父容器 parentView.addView(customView); ``` ##### 2. 在适配器中的用法(复用视图) ```java @Override public View getView(int position, View convertView, ViewGroup parent) { // 获取 LayoutInflater LayoutInflater inflater = LayoutInflater.from(parent.getContext()); if (convertView == null) { // 首次创建视图 convertView = inflater.inflate( R.layout.list_item, parent, false // 必须为false,由适配器管理附加 ); // 初始化ViewHolder... } // 更新视图数据 return convertView; } ``` #### 三、关键参数解析 | 参数 | 说明 | 推荐场景 | |------|------|----------| | `resource` | XML 布局资源 ID | 必填 | | `root` | 父容器 (`ViewGroup`) | **必填** (确保布局参数生效) | | `attachToRoot` | 是否立即附加 | `false` (适配器/动态添加)<br/>`true` (自定义View组件)[^4] | #### 四、注意事项 1. **必须传递 `root` 参数** 未传递时布局的 `layout_width/layout_height` 等参数会失效: ```java // 错误!布局参数可能异常 inflater.inflate(R.layout.item, null); // 正确 inflater.inflate(R.layout.item, parent, false); ``` 2. **上下文选择** 优先使用 `parent.getContext()` 而非 `Activity` 上下文,避免内存泄漏: ```java // 在适配器中推荐 LayoutInflater.from(parent.getContext()); ``` 3. **性能优化** 在 `RecyclerView/ListView` 中务必复用 `convertView` + `ViewHolder` 模式,避免频繁调用 `inflate()`。 > 总结:`LayoutInflater.from(context)` 是获取布局解析器的标准方式,核心价值在于将静态 XML 转换为可操作的 `View` 对象,同时确保布局参数正确继承[^1][^4]。 --- ### 相关问题 1. **为什么 `inflate()` 的 `attachToRoot` 参数在适配器中必须设为 `false`?** [答案:适配器需自主管理视图生命周期,设为 `true` 会导致重复添加视图引发异常[^4]。] 2. **`LayoutInflater.from()` 和 `Activity.getLayoutInflater()` 有何区别?** [答案:前者基于 `Context` 更通用,后者是 `Activity` 的快捷方法,本质相同[^2]。] 3. **如何在自定义 View 组件中正确使用 `LayoutInflater`?** [答案:构造函数中调用 `inflate(R.layout.xxx, this, true)` 实现自动附加[^4]。] 4. **为什么通过 `LayoutInflater` 加载的视图有时布局参数失效?** [答案:未传递有效的 `root` 参数导致无法解析 `LayoutParams`[^4]。]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值