一、为什么要做布局优化?
Android 布局性能直接决定了应用的首屏加载速度与流畅度。
复杂的布局层级会导致:
measure/layout/draw次数增加界面绘制变慢、掉帧卡顿
View 创建与内存占用上升
所以我们要通过:
✅ include
✅ merge
✅ ViewStub
实现布局结构优化。
二、三大优化手段关系图
下图展示了三者的区别和作用:
┌───────────────────────────────┐ │ 布局优化手段 │ ├──────────┬───────────┬──────────┤ │ include │ merge │ ViewStub │ ├──────────┼───────────┼──────────┤ │ 重用布局 │ 减少层级 │ 延迟加载 │ │ Header、Footer │ RecyclerView Item │ 空页、错误页 │ └──────────┴───────────┴──────────┘
📈 性能影响从小到大:
include < merge < ViewStub(延迟加载最优)
三、<include> ------ 复用布局的利器
🧱 典型结构图
activity_main.xml │ ├── include -> header_layout.xml │ ├── ImageView (返回键) │ └── TextView (标题) │ └── 内容区域
✅ 使用示例
XML
<!-- header_layout.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@color/black">
<ImageView
android:id="@+id/iv_back"
android:src="@drawable/ic_back"
android:layout_width="24dp"
android:layout_height="24dp"/>
<TextView
android:id="@+id/tv_title"
android:text="标题"
android:textColor="@color/white"
android:textSize="18sp"
android:layout_marginStart="16dp"/>
</LinearLayout>
XML
<!-- activity_main.xml -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/header_layout"
android:id="@+id/include_header"/>
<TextView
android:text="内容区域"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
⚠️ 注意事项
include的id仅作用于引用处,子布局id不会被替代。外层可重新定义
layout_width、layout_height。
四、<merge> ------ 减少多余布局层级
🧩 对比结构图
错误示例:
LinearLayout (外层) └── include -> LinearLayout (item_user.xml) ├── ImageView └── TextView
正确示例:
LinearLayout (外层) ├── ImageView └── TextView
✅ 实现示例
XML
<!-- item_user.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:id="@+id/avatar"
android:layout_width="40dp"
android:layout_height="40dp"/>
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"/>
</merge>
XML
<!-- 使用 -->
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/item_user"/>
</LinearLayout>
⚠️ 注意事项
<merge>不能独立使用(必须 include 或动态 inflate)。无法直接访问根布局(它被合并进父布局)。
五、<ViewStub> ------ 按需延迟加载隐藏布局
🕓 原理示意图
初始状态: FrameLayout ├── MainContent └── ViewStub (占位,不占资源) 当触发 inflate(): FrameLayout ├── MainContent └── ErrorLayout (由 ViewStub 替换生成)
✅ 示例代码
XML
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/layout_content" />
<ViewStub
android:id="@+id/view_stub_error"
android:inflatedId="@+id/layout_error"
android:layout="@layout/layout_error"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Kotlin
val viewStub = findViewById<ViewStub>(R.id.view_stub_error)
val errorView = viewStub.inflate()
errorView.findViewById<TextView>(R.id.tv_error).text = "加载失败,请重试"
⚠️ 注意事项
inflate()只能调用一次。绑定
ViewBinding或DataBinding时需在inflate()之后执行。适合错误页、空数据页、引导页等不常显示的布局。
六、三者对比表
| 特性 | include | merge | ViewStub |
|---|---|---|---|
| 作用 | 复用布局 | 减少嵌套 | 延迟加载 |
| 是否创建 View | 是 | 合并入父布局 | 延迟加载时创建 |
| 性能优化级别 | 中 | 高 | 最高 |
| 常见场景 | Header/Footer | RecyclerView item | 空页、错误页、引导页 |
| 注意事项 | 属性覆盖 | 无根节点 | inflate 一次性 |
七、性能可视化示意图
下图表示布局层级优化前后的渲染层次:
未优化: FrameLayout └── LinearLayout └── LinearLayout └── TextView 优化后 (merge): FrameLayout └── LinearLayout └── TextView
结果:
measure/layout 次数减少约 20--30%
渲染时间缩短,首屏更快
八、实战:错误页延迟加载优化
XML
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/layout_content" />
<ViewStub
android:id="@+id/stub_error"
android:layout="@layout/layout_error"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
Kotlin
class MainActivity : AppCompatActivity() {
private var errorView: View? = null
fun showError() {
if (errorView == null) {
errorView = findViewById<ViewStub>(R.id.stub_error).inflate()
}
errorView?.visibility = View.VISIBLE
}
fun hideError() {
errorView?.visibility = View.GONE
}
}
九、总结:何时用哪一个?
| 目的 | 推荐方式 | 原因 |
|---|---|---|
| 复用相同布局 | include | 简单可靠 |
| 避免重复父布局 | merge | 减少层级 |
| 延迟加载不常用布局 | ViewStub | 节省内存与渲染时间 |
🔚 写在最后
布局优化不是炫技,而是「让 UI 加载更快」的基础功。
掌握
include、merge、ViewStub的组合拳,你可以让应用启动速度提升、UI 更流畅。