Appearance
02_常用布局详解
一、Android 布局体系概览
1.1 布局的重要性
在 Android 开发中,布局(Layout)是构建用户界面的基石。一个优秀的布局设计不仅能够提升应用的视觉效果,还能显著提高应用的性能。面试中,布局相关的问题几乎是必考点,从基础的使用到深层次的原理和优化,都需要深入理解。
1.2 布局分类
Android 提供了丰富的布局类型,主要包括:
| 布局类型 | 特点 | 适用场景 |
|---|---|---|
| FrameLayout | 最简单,所有子视图堆叠 | 单视图、图片叠加 |
| LinearLayout | 线性排列 | 简单的一行或一列布局 |
| RelativeLayout | 相对定位 | 中等复杂度的布局 |
| ConstraintLayout | 约束布局 | 复杂布局、Jetpack 推荐 |
| GridLayout | 网格布局 | 表格、表单 |
| TableLayout | 表格布局 | 类似 HTML 表格 |
| ScrollView/HorizontalScrollView | 滚动容器 | 内容超出屏幕时 |
1.3 布局性能金字塔
性能最优
↓
FrameLayout (单层)
↓
LinearLayout (浅层)
↓
ConstraintLayout
↓
RelativeLayout
↓
嵌套 LinearLayout
↓
性能最差二、LinearLayout 线性布局
2.1 基本使用
LinearLayout 是最常用的布局之一,它将子视图按照水平或垂直方向线性排列。
xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮 1"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮 2"
android:layout_weight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮 3"
android:layout_weight="1"/>
</LinearLayout>2.2 核心属性详解
2.2.1 orientation 方向
xml
android:orientation="horizontal" <!-- 水平排列 -->
android:orientation="vertical" <!-- 垂直排列 -->2.2.2 layout_weight 权重
layout_weight 是 LinearLayout 的灵魂属性,用于分配剩余空间。
xml
<!-- 示例:三个 TextView 均分屏幕高度 -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="33.3%"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="33.3%"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="33.3%"/>
</LinearLayout>关键点: 使用 layout_weight 时,对应的 layout_height 应设置为 0dp,这样可以避免两次测量,提升性能。
2.2.3 权重分配原理
总权重 = sum(layout_weight)
每个子视图获得的额外空间 = (layout_weight / 总权重) * 剩余空间xml
<!-- 示例:权重比例 1:2:1 -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="25%"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:text="50%"/>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="25%"/>
</LinearLayout>2.3 布局优化技巧
2.3.1 使用 0dp + weight
xml
<!-- ❌ 性能较差:会先测量内容高度,再分配权重 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="文本"/>
<!-- ✅ 性能最优:直接根据权重分配高度 -->
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="文本"/>2.3.2 避免过度嵌套
xml
<!-- ❌ 嵌套过多 -->
<LinearLayout
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal">
<LinearLayout
android:orientation="vertical">
<!-- 更多嵌套... -->
</LinearLayout>
</LinearLayout>
</LinearLayout>
<!-- ✅ 使用 ConstraintLayout 替代 -->
<androidx.constraintlayout.widget.ConstraintLayout>
<!-- 通过约束实现相同布局 -->
</androidx.constraintlayout.widget.ConstraintLayout>2.4 测量流程解析
LinearLayout 的测量过程分为两次:
- 第一次测量: 测量所有
layout_weight=0的子视图 - 第二次测量: 计算剩余空间,根据权重分配
java
// 简化版的 LinearLayout 测量逻辑
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = getChildCount();
int totalWeight = 0;
// 第一次测量:固定高度的子视图
for (int i = 0; i < size; i++) {
View child = getChildAt(i);
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
if (child.getLayoutParams() instanceof LinearLayout.LayoutParams) {
totalWeight += ((LinearLayout.LayoutParams) child.getLayoutParams()).weight;
}
}
// 计算剩余空间
int remainingSpace = totalSpace - usedSpace;
// 第二次测量:根据权重分配剩余空间
if (totalWeight > 0) {
for (int i = 0; i < size; i++) {
View child = getChildAt(i);
float weight = getChildWeight(i);
int extraSpace = (int) (remainingSpace * weight / totalWeight);
measureChildWithMargins(child, widthMeasureSpec, 0,
heightMeasureSpec, extraSpace);
}
}
}三、RelativeLayout 相对布局
3.1 基本使用
RelativeLayout 允许子视图相对于父容器或其他子视图进行定位。
xml
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 相对于父容器顶部 -->
<TextView
android:id="@+id/top_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="顶部文本"/>
<!-- 相对于其他视图 -->
<Button
android:id="@+id/center_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/top_text"
android:layout_centerHorizontal="true"
android:text="居中按钮"/>
<!-- 同时相对于父容器底部 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="底部文本"/>
</RelativeLayout>3.2 核心定位属性
3.2.1 相对于父容器
xml
android:layout_alignParentTop="true" <!-- 对齐父容器顶部 -->
android:layout_alignParentBottom="true" <!-- 对齐父容器底部 -->
android:layout_alignParentLeft="true" <!-- 对齐父容器左侧 -->
android:layout_alignParentRight="true" <!-- 对齐父容器右侧 -->
android:layout_centerHorizontal="true" <!-- 水平居中 -->
android:layout_centerVertical="true" <!-- 垂直居中 -->
android:layout_centerInParent="true" <!-- 完全居中 -->3.2.2 相对于其他视图
xml
android:layout_above="@id/viewId" <!-- 在 viewId 上方 -->
android:layout_below="@id/viewId" <!-- 在 viewId 下方 -->
android:layout_toLeftOf="@id/viewId" <!-- 在 viewId 左侧 -->
android:layout_toRightOf="@id/viewId" <!-- 在 viewId 右侧 -->
android:layout_alignTopWith="@id/viewId" <!-- 顶部与 viewId 对齐 -->
android:layout_alignBottomWith="@id/viewId" <!-- 底部与 viewId 对齐 -->
android:layout_alignLeftWith="@id/viewId" <!-- 左侧与 viewId 对齐 -->
android:layout_alignRightWith="@id/viewId" <!-- 右侧与 viewId 对齐 -->3.3 复杂布局示例
3.3.1 导航栏布局
xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp">
<!-- 左侧返回按钮 -->
<ImageButton
android:id="@+id/iv_back"
android:layout_width="56dp"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:src="@drawable/ic_back"
android:background="?attr/selectableItemBackground"/>
<!-- 标题文本,居中 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="标题"
android:textSize="20sp"
android:textStyle="bold"/>
<!-- 右侧菜单按钮 -->
<ImageButton
android:id="@+id/iv_more"
android:layout_width="56dp"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:src="@drawable/ic_more"
android:background="?attr/selectableItemBackground"/>
</RelativeLayout>3.3.2 居中带边距布局
xml
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<ImageView
android:id="@+id/iv_header"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_centerHorizontal="true"
android:src="@drawable/avatar"/>
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/iv_header"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dp"
android:text="用户名"
android:textSize="18sp"/>
<TextView
android:id="@+id/tv_bio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tv_name"
android:layout_marginTop="8dp"
android:layout_marginHorizontal="32dp"
android:gravity="center"
android:text="个人简介"
android:textSize="14sp"/>
</RelativeLayout>3.4 性能问题
3.4.1 链式依赖问题
xml
<!-- ❌ 链式依赖:A→B→C→D,每个视图都需要等待前一个测量完成 -->
<TextView
android:id="@+id/textA"
android:layout_alignParentTop="true"/>
<TextView
android:id="@+id/textB"
android:layout_below="@id/textA"/>
<TextView
android:id="@+id/textC"
android:layout_below="@id/textB"/>
<TextView
android:id="@+id/textD"
android:layout_below="@id/textC"/>
<!-- ✅ 优化:所有视图都相对于父容器或同一个视图 -->
<TextView
android:id="@+id/textA"
android:layout_alignParentTop="true"
android:layout_marginTop="0dp"/>
<TextView
android:id="@+id/textB"
android:layout_alignParentTop="true"
android:layout_marginTop="50dp"/>
<TextView
android:id="@+id/textC"
android:layout_alignParentTop="true"
android:layout_marginTop="100dp"/>
<TextView
android:id="@+id/textD"
android:layout_alignParentTop="true"
android:layout_marginTop="150dp"/>3.4.2 测量次数
RelativeLayout 需要多次测量才能确定所有子视图的位置:
测量次数 = 视图数量 × 依赖深度对于深度为 n 的链式依赖,最坏情况下需要 n² 次测量。
四、ConstraintLayout 约束布局
4.1 简介
ConstraintLayout 是 Google 官方推荐的高性能布局,2017 年成为年度最佳布局。它允许所有视图平铺在同一层级,通过约束关系确定位置。
4.2 基本使用
xml
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标题"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/content"/>
<TextView
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="内容"
app:layout_constraintTop_toBottomOf="@id/title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>4.3 核心约束属性
4.3.1 方向约束
xml
app:layout_constraintTop_toTopOf="parent" <!-- 顶部对齐 -->
app:layout_constraintBottom_toBottomOf="parent" <!-- 底部对齐 -->
app:layout_constraintStart_toStartOf="parent" <!-- 起始边对齐(左) -->
app:layout_constraintEnd_toEndOf="parent" <!-- 结束边对齐(右) -->
app:layout_constraintLeft_toLeftOf="parent" <!-- 左边对齐 -->
app:layout_constraintRight_toRightOf="parent" <!-- 右边对齐 -->4.3.2 居中约束
xml
app:layout_constraintHorizontal_chainStyle="spread" <!-- 水平链样式 -->
app:layout_constraintVertical_chainStyle="spread" <!-- 垂直链样式 -->4.4 链(Chains)
链是 ConstraintLayout 的重要特性,用于在多个视图之间分配空间。
xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 水平链:三个按钮均分空间 -->
<Button
android:id="@+id/btn_left"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="左"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/btn_center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<Button
android:id="@+id/btn_center"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="中"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/btn_left"
app:layout_constraintEnd_toStartOf="@id/btn_right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<Button
android:id="@+id/btn_right"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="右"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@id/btn_center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>4.4.1 链的三种模式
spread(默认):均匀分布
spread_inside:两端对齐,内部均匀
packed:紧凑,中间的元素不受影响4.5 比例约束
4.5.1 固定宽高比
xml
<ImageView
android:id="@+id/image"
android:layout_width="0dp"
android:layout_height="0dp"
android:src="@drawable/photo"
app:layout_constraintWidth_height="16:9" <!-- 固定比例 -->
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>4.5.2 根据宽度计算高度
xml
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="W,16:9" <!-- 宽度决定高度 -->
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/image"/>4.6 Guideline 辅助线
xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 水平辅助线:距离顶部 30% 的位置 -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:guidePercent="30%"
app:orientation="horizontal"/>
<!-- 垂直辅助线:距离左侧 80dp -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintGuide_begin="80dp"
app:orientation="vertical"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="对齐到辅助线"
app:layout_constraintTop_toTopOf="@id/guideline_horizontal"
app:layout_constraintStart_toStartOf="@id/guideline_vertical"/>
</androidx.constraintlayout.widget.ConstraintLayout>4.7 Barrier 屏障
Barrier 允许一组视图共享一个边界,比 Guideline 更智能。
xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="短"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个较长的文本"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/text1"/>
<TextView
android:id="@+id/text3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="中等长度"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@id/text2"/>
<!-- 屏障:对齐到最右侧视图 -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="text1,text2,text3"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="提交"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@id/barrier_right"/>
</androidx.constraintlayout.widget.ConstraintLayout>4.8 Group 组
xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 定义一个组 -->
<androidx.constraintlayout.widget.Group
android:id="@+id/visibility_group"
app:constraint_referenced_ids="text1,text2,button1"/>
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文本 1"/>
<TextView
android:id="@+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文本 2"/>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="按钮"/>
</androidx.constraintlayout.widget.ConstraintLayout>使用:
java
// 批量设置可见性
setVisibility(groupId, View.GONE);4.9 Flow 流布局
xml
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Flow
android:id="@+id/flow"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:flow_horizontalStyle="packed"
app:flow_verticalStyle="packed"
app:flow_horizontalBias="0.5"
app:flow_verticalBias="0.5"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标签 1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标签 2"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标签 3"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标签 4"/>
</androidx.constraintlayout.widget.Flow>
</androidx.constraintlayout.widget.ConstraintLayout>4.10 性能优化
4.10.1 优化布局层级
xml
<!-- ❌ 嵌套布局 -->
<LinearLayout
android:orientation="vertical">
<LinearLayout
android:orientation="horizontal">
<TextView android:text="1"/>
<TextView android:text="2"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal">
<TextView android:text="3"/>
<TextView android:text="4"/>
</LinearLayout>
</LinearLayout>
<!-- ✅ 使用 ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/t1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/t2"
app:layout_constraintTop_toTopOf="@id/t1"
app:layout_constraintStart_toEndOf="@id/t1"/>
<TextView
android:id="@+id/t3"
app:layout_constraintTop_toBottomOf="@id/t1"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="@+id/t4"
app:layout_constraintTop_toTopOf="@id/t3"
app:layout_constraintStart_toEndOf="@id/t3"/>
</androidx.constraintlayout.widget.ConstraintLayout>4.10.2 使用 merge 标签
xml
<!-- 定义可重用的布局 -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="标签"/>
<EditText
android:id="@+id/input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/label"/>
</merge>4.10.3 使用 ViewStub
xml
<!-- 延迟加载布局 -->
<ViewStub
android:id="@+id/view_stub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/deferred_content"/>java
ViewStub viewStub = findViewById(R.id.view_stub);
View view = viewStub.inflate(); // 需要时才加载五、其他布局
5.1 FrameLayout 帧布局
xml
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<!-- 背景图片 -->
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/background"/>
<!-- 居中的内容 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="居中文本"/>
<!-- 右上角的图标 -->
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end|top"
android:layout_margin="8dp"
android:src="@drawable/ic_close"/>
</FrameLayout>5.2 ScrollView 滚动布局
xml
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView android:text="内容 1"/>
<TextView android:text="内容 2"/>
<TextView android:text="内容 3"/>
<!-- 更多内容... -->
</LinearLayout>
</ScrollView>关键点: ScrollView 只能有一个直接子视图。
5.3 NestedScrollView 嵌套滚动
xml
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/header"/>
<include layout="@layout/content"/>
<include layout="@layout/footer"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>5.4 CoordinatorLayout 协调布局
xml
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/header_image"
app:layout_collapseMode="parallax"/>
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"/>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="滚动内容..."/>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>六、布局性能对比
6.1 测量次数对比
| 布局类型 | 简单布局 | 中等复杂度 | 复杂布局 |
|---|---|---|---|
| FrameLayout | 1 次 | 1 次 | 1 次 |
| LinearLayout | 2 次 | 2 次 | 4 次 |
| ConstraintLayout | 2 次 | 2 次 | 2 次 |
| RelativeLayout | 2 次 | 4 次 | 8 次 |
6.2 实际测试数据
测试场景:10 个 TextView 的列表项
FrameLayout (单层): 5ms
LinearLayout (浅层): 8ms
ConstraintLayout: 7ms
RelativeLayout: 12ms
嵌套 LinearLayout: 25ms6.3 内存占用对比
ConstraintLayout: 约 20KB (需要构建约束图)
RelativeLayout: 约 15KB
LinearLayout: 约 10KB
FrameLayout: 约 8KB6.4 性能测试工具
6.4.1 使用 Hierarchy Viewer
bash
# 连接设备后查看布局层级
hierarchy-viewer6.4.2 使用 Layout Inspector
在 Android Studio 中:
- Tools → Layout Inspector
- 查看布局树和性能指标
6.4.3 代码统计
java
public int getViewHierarchyDepth(View view) {
int depth = 0;
if (view instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) view;
for (int i = 0; i < vg.getChildCount(); i++) {
int childDepth = getViewHierarchyDepth(vg.getChildAt(i));
depth = Math.max(depth, childDepth);
}
depth++;
}
return depth;
}七、布局优化最佳实践
7.1 减少布局层级
7.1.1 使用 ConstraintLayout
xml
<!-- 目标:将层级从 5 层优化到 2 层 -->7.1.2 使用 merge
xml
<!-- 减少不必要的中间层 -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 子视图 -->
</merge>7.1.3 使用 include 谨慎
xml
<!-- include 会增加一层布局 -->
<include layout="@layout/common_header"/>7.2 使用可选的布局属性
xml
<!-- clipToPadding: 防止子视图在 padding 区域内绘制 -->
<View
android:clipToPadding="true"/>
<!-- clipChildren: 防止子视图在父视图外绘制 -->
<View
android:clipChildren="true"/>7.3 避免过度测量
xml
<!-- ❌ 避免嵌套 ScrollView -->
<ScrollView>
<LinearLayout>
<ScrollView>
<!-- 嵌套滚动容器 -->
</ScrollView>
</LinearLayout>
</ScrollView>
<!-- ✅ 使用 NestedScrollView + RecyclerView -->
<NestedScrollView>
<LinearLayout>
<TextView/>
<androidx.recyclerview.widget.RecyclerView
app:layout_manager="LinearLayoutManager"/>
</LinearLayout>
</NestedScrollView>7.4 使用硬件加速
xml
<!-- Manifest 中启用硬件加速 -->
<application
android:hardwareAccelerated="true">
</application>
<!-- 或者针对特定视图 -->
<View
android:layerType="hardware"/>7.5 使用布局模板
xml
<!-- 定义可复用的布局 -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="com.example.Item"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{item.name}"/>
</LinearLayout>
</layout>八、面试考点总结
8.1 基础知识点
Q1: LinearLayout 的 weight 属性如何优化性能?
A: 使用 layout_height="0dp" + layout_weight 的组合,避免两次测量。
xml
<!-- ✅ 正确写法 -->
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>Q2: ConstraintLayout 为什么性能好?
A:
- 所有视图平铺在同一层级,没有嵌套
- 测量次数固定为 2 次,与视图数量无关
- 使用约束图算法一次性计算所有位置
Q3: RelativeLayout 的链式依赖问题是什么?
A: 当一个视图依赖于前一个视图时,会产生多次测量,导致性能下降。
8.2 进阶知识点
Q4: ConstraintLayout 的 Chain 是什么?
A: Chain 是一系列通过约束连接的视图,用于在它们之间分配空间。三种模式:spread、spread_inside、packed。
Q5: 如何减少布局层级?
A:
- 使用 ConstraintLayout 替代嵌套布局
- 使用 merge 标签
- 使用 include 时注意会增加一层
- 使用 ViewStub 延迟加载
Q6: 硬件加速对布局性能有什么影响?
A: 硬件加速可以将绘制操作从 CPU 转移到 GPU,提高动画和复杂布局的性能。
8.3 实战题目
Q7: 设计一个响应式的登录页面
考察点: ConstraintLayout、Guideline、Barrier、资源限定符
Q8: 实现一个可滚动的头部固定布局
考察点: CoordinatorLayout、AppBarLayout、NestedScrollView
8.4 常见面试题
| 问题 | 考察点 | 难度 |
|---|---|---|
| LinearLayout weight 优化 | 性能优化 | ⭐⭐ |
| ConstraintLayout 原理 | 布局原理 | ⭐⭐⭐ |
| RelativeLayout 链式依赖 | 性能问题 | ⭐⭐ |
| 布局层级优化方法 | 最佳实践 | ⭐⭐⭐ |
| View 测量流程 | 底层原理 | ⭐⭐⭐⭐ |
| 硬件加速原理 | 性能优化 | ⭐⭐⭐ |
九、实战案例分析
9.1 电商商品列表优化
9.1.1 原始布局(12 层)
xml
<LinearLayout>
<LinearLayout>
<ImageView/>
<LinearLayout>
<TextView/>
<TextView/>
<LinearLayout>
<TextView/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>9.1.2 优化后(2 层)
xml
<androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
app:layout_constraintStart_toStartOf="parent"/>
<TextView
app:layout_constraintStart_toEndOf="@id/image"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
app:layout_constraintStart_toEndOf="@id/image"
app:layout_constraintTop_toBottomOf="@id/name"/>
<TextView
app:layout_constraintStart_toEndOf="@id/image"
app:layout_constraintTop_toBottomOf="@id/desc"/>
</androidx.constraintlayout.widget.ConstraintLayout>9.2 新闻详情页优化
9.2.1 使用 NestedScrollView + ConstraintLayout
xml
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/news_header"/>
<TextView
android:id="@+id/content"
app:layout_constraintTop_toBottomOf="@id/header"/>
<include layout="@layout/news_footer"
app:layout_constraintTop_toBottomOf="@id/content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>十、总结
10.1 布局选择指南
| 场景 | 推荐布局 |
|---|---|
| 单个视图 | FrameLayout |
| 简单线性排列 | LinearLayout |
| 相对定位 | ConstraintLayout |
| 复杂布局 | ConstraintLayout |
| 可滚动内容 | NestedScrollView |
| 需要行为协调 | CoordinatorLayout |
10.2 性能优化清单
- [ ] 布局层级控制在 10 层以内
- [ ] 优先使用 ConstraintLayout
- [ ] LinearLayout 使用 0dp + weight
- [ ] 避免 RelativeLayout 链式依赖
- [ ] 使用 merge 和 ViewStub
- [ ] 启用硬件加速
- [ ] 使用 Hierarchy Viewer 检测
10.3 学习资源
本文档持续更新,欢迎补充和完善。