一个 Activity 一个 LayoutInflater 实例
每个 Activity 都有自己的 LayoutInflater 实例,每个 Activity 会在初始化时创建自己的 LayoutInflater 实例,存储在 Activity 的成员变量中。
获取 LayoutInflater 的方法
LayoutInflater 需要依赖上下文(Context)来加载资源(如布局文件、字符串等),LayoutInflater 提供 LayoutInflater.from(context: Context)
方法根据传入的 Context 对象获取 LayoutInflater 对象。
在当前 Activity 中获取 LayoutInflater 时,可以直接使用本类的成员变量 LayoutInflater,也可以根据当前 Activity 实例重新创建 LayoutInflater。虽然通过这两种方式获得的是两个不同的实例,但都关联当前 Activity 的上下文,功能完全相同。
// 在当前 Activity 中获取 LayoutInflater 的两种方式
val inflater1 = layoutInflater // Activity 自带的 inflater
val inflater2 = LayoutInflater.from(this) // 通过 Activity 的上下文创建
核心方法:inflate ()
inflate() 是加载布局的核心方法,常用重载形式:
inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean): View
参数说明:
- resource:XML 布局文件的资源 ID(如 R.layout.item_list)
- root:父容器(ViewGroup),用于确定子视图的布局参数(如 match_parent 的基准)
- attachToRoot:是否将加载的视图自动添加到 root 中
inflate() 方法的 attachToRoot 参数何时设置为 false?
在使用 inflate(resource: Int, root: ViewGroup?, attachToRoot: Boolean)
方法时,attachToRoot
参数设为 false
的核心场景是:需要加载视图但不希望它被自动添加到父容器中,而是由开发者手动控制添加时机或由系统组件(如列表控件)自行管理。
以下是具体场景和示例说明:
场景1:列表控件(ListView/GridView)的 Item 布局加载(最典型场景)
列表控件(如 ListView)会通过适配器的 getView()
方法获取每个 Item 视图,然后由列表自身负责将这些视图添加到容器中。此时若 attachToRoot
设为 true
,会导致 Item 视图被重复添加(inflate
时自动添加一次,列表自身又添加一次),引发布局错乱或崩溃。
示例(Kotlin):
// 自定义适配器加载 ListView 的 Item 布局
class MyAdapter(
private val context: Context,
private val itemLayoutId: Int,
private val data: List<String>
) : ArrayAdapter<String>(context, itemLayoutId, data) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val view = if (convertView == null) {
// 关键:attachToRoot 设为 false
// 原因:ListView 会自行管理 Item 视图的添加,避免重复添加
LayoutInflater.from(context).inflate(itemLayoutId, parent, false)
} else {
convertView // 复用旧视图
}
// 绑定数据
view.findViewById<TextView>(R.id.tvItem).text = data[position]
return view
}
}
解析:
parent
传入的是 ListView 本身,用于帮助 inflate
正确解析 Item 布局中的 match_parent
等参数(以 ListView 宽度为基准),但 attachToRoot=false
确保 Item 不会被立即添加到 ListView,而是由 ListView 在合适时机(如滚动时)自行添加。
场景2:动态添加视图前需要修改布局参数
若需要在加载视图后、添加到父容器前修改其布局参数(如宽高、margin 等),需将 attachToRoot
设为 false
,否则视图已被添加到父容器,修改参数可能不生效或引发异常。
示例(Kotlin):
// 在 Activity 中动态加载视图并修改参数后添加到容器
class DynamicActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dynamic)
val parentContainer = findViewById<LinearLayout>(R.id.parentContainer)
// 1. 加载视图:attachToRoot 设为 false(先不添加到父容器)
val customView = LayoutInflater.from(this)
.inflate(R.layout.view_custom, parentContainer, false)
// 2. 修改布局参数(必须在添加到父容器前进行)
val layoutParams = customView.layoutParams as LinearLayout.LayoutParams
layoutParams.topMargin = 20.dpToPx() // 自定义 margin
layoutParams.height = 150.dpToPx() // 自定义高度
customView.layoutParams = layoutParams
// 3. 手动添加到父容器
parentContainer.addView(customView)
}
// dp 转 px 的工具方法
private fun Int.dpToPx(): Int {
return (this * Resources.getSystem().displayMetrics.density).toInt()
}
}
解析:
若 attachToRoot
设为 true
,inflate
会直接将视图添加到 parentContainer
,后续修改布局参数可能无效(因视图已完成布局)。false
则允许我们先调整参数再手动添加。
场景3:加载对话框、弹窗等独立视图
对话框、弹窗等视图通常不需要立即关联到某个父容器,而是作为独立视图由对话框管理器控制显示。此时 root
可传入 null
,但即使传入父容器,attachToRoot
也必须设为 false
。
示例(Kotlin):
// 加载自定义对话框布局
class DialogActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dialog)
findViewById<Button>(R.id.btnShowDialog).setOnClickListener {
// 加载对话框布局:root 为 null,attachToRoot 设为 false
val dialogView = LayoutInflater.from(this)
.inflate(R.layout.dialog_custom, null, false)
// 创建对话框并设置视图
val dialog = AlertDialog.Builder(this)
.setView(dialogView)
.create()
// 绑定按钮事件
dialogView.findViewById<Button>(R.id.btnCancel).setOnClickListener {
dialog.dismiss()
}
dialog.show()
}
}
}
解析:
对话框视图的父容器由系统内部管理(如 Dialog
的窗口容器),开发者无需手动添加,因此 attachToRoot
必须为 false
。
场景4:Fragment 加载布局时
Fragment 的 onCreateView()
方法中加载布局时,attachToRoot
需设为 false
,因为 Fragment 的视图会由系统在合适时机自动添加到其所属的父容器(如 Activity 中的 FragmentContainerView
)。
示例(Kotlin):
class MyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// 加载 Fragment 布局:attachToRoot 设为 false
// 原因:Fragment 视图由系统自动添加到 container 中
return inflater.inflate(R.layout.fragment_my, container, false)
}
}
解析:
container
是 Fragment 所属的父容器(如 FrameLayout
),attachToRoot=false
确保布局仅被加载而不自动添加,由系统在 onViewCreated()
阶段完成添加,避免生命周期冲突。
总结:attachToRoot=false
的核心原则
当满足以下任一条件时,attachToRoot
必须设为 false
:
- 视图的添加由系统组件(如 ListView、Fragment)自动管理。
- 需要在添加到父容器前修改视图的布局参数。
- 视图是独立显示的(如对话框、弹窗),无需立即关联父容器。
设置为 false
的本质是将视图添加的控制权交给开发者或系统,避免提前添加导致的布局混乱。