一、基础布局类型
LinearLayout
- 线性排列子 View,支持垂直 / 水平方向
- 关键属性:
android:orientation
、layout_weight
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="按钮1"/> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="2" android:text="按钮2"/> </LinearLayout>
RelativeLayout
- 通过相对位置规则放置子 View
- 关键属性:
layout_above
、layout_toRightOf
、layout_centerInParent
<RelativeLayout 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="顶部文本"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/text1" android:layout_alignParentRight="true" android:text="右侧按钮"/> </RelativeLayout>
FrameLayout
- 所有子 View 堆叠在左上角
- 适合实现图层效果或单元素展示
<FrameLayout android:layout_width="match_parent" android:layout_height="200dp"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image_bg" android:scaleType="centerCrop"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="图片标题" android:textColor="#FFFFFF"/> </FrameLayout>
ConstraintLayout
- 基于约束关系定位子 View,性能优化
- 关键特性:链 (Chains)、屏障 (Barrier)、引导线 (GuideLine)
<androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:id="@+id/input" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/button" app:layout_constraintTop_toTopOf="parent"/> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="@id/input"/> </androidx.constraintlayout.widget.ConstraintLayout>
二、布局性能优化
减少布局层级
- 使用 ConstraintLayout 替代嵌套 LinearLayout
- 避免超过 3 层布局嵌套
布局复用技术
<include>
标签:复用布局文件
<include layout="@layout/common_toolbar"/>
<merge>
标签:减少布局层级
<merge xmlns:android="http://schemas.android.com/apk/res/android"> <TextView android:layout_width="match_parent" .../> <Button android:layout_width="wrap_content" .../> </merge>
延迟加载
- ViewStub:需要时再加载布
<ViewStub android:id="@+id/stub" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout="@layout/progress_view"/>
java
ViewStub stub = findViewById(R.id.stub); View inflated = stub.inflate(); // 延迟加载
三、尺寸单位
单位 | 说明 | 适用场景 |
---|---|---|
px | 像素 | 不推荐使用 |
dp/dip | 密度无关像素 | 大多数场景 |
sp | 缩放像素 | 文本大小 |
pt | 点 (1/72 英寸) | 很少使用 |
% | 百分比 | ConstraintLayout 中使用 |
四、常见面试问题
LinearLayout 中 layout_weight 的工作原理
- 剩余空间按权重比例分配
- 建议将 width/height 设为 0dp 优化性能
RelativeLayout 与 LinearLayout 性能对比
- RelativeLayout:单次测量,但布局算法复杂
- LinearLayout:可能需要多次测量 (有 weight 时)
- ConstraintLayout 性能最优
如何实现流式布局 (FlowLayout)
- 自定义 View 继承 ViewGroup
- 重写 onMeasure 和 onLayout 方法
- 或使用第三方库:HFlowLayout
谈谈 ConstraintLayout 的优势
- 扁平化布局减少层级
- 支持复杂约束关系
- 性能优于 RelativeLayout
- 可视化编辑器友好
五、自定义布局
继承 ViewGroup 并重写:
- onMeasure ():测量子 View 尺寸
- onLayout ():放置子 View 位置
java
public class CustomLayout extends ViewGroup { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 测量所有子View measureChildren(widthMeasureSpec, heightMeasureSpec); // 计算自身尺寸 setMeasuredDimension(width, height); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // 放置每个子View的位置 child.layout(left, top, right, bottom); } }
自定义属性:
xml
<declare-styleable name="CustomLayout"> <attr name="item_space" format="dimension"/> </declare-styleable>
java
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomLayout); int itemSpace = a.getDimensionPixelSize(R.styleable.CustomLayout_item_space, 0); a.recycle();
六、最佳实践
使用 ConstraintLayout 作为主要布局
使用 VectorDrawable 替代多分辨率图片
为不同屏幕尺寸创建布局别名
xml
<!-- values-sw600dp/dimens.xml --> <dimen name="card_padding">24dp</dimen> <!-- values/dimens.xml --> <dimen name="card_padding">16dp</dimen>
使用 Data Binding 减少 findViewById
xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.example.User"/> </data> <TextView android:text="@{user.name}" .../> </layout>
七、补充面试问题
1. View 的绘制流程是怎样的?
- 核心流程:
measure()
→layout()
→draw()
- measure:确定 View 的测量宽高(通过父容器传递的 MeasureSpec 和自身需求计算)
- layout:确定 View 在父容器中的位置(left/top/right/bottom)
- draw:绘制 View 的内容(背景、文本、图形等)
2. MeasureSpec 的作用是什么?
- 定义:32 位 int 值,高 2 位表示模式,低 30 位表示尺寸
- 三种模式:
- EXACTLY:父容器已确定精确尺寸(如
match_parent
或固定值) - AT_MOST:父容器指定最大尺寸(如
wrap_content
) - UNSPECIFIED:父容器不对 View 尺寸做限制(如 ScrollView)
- EXACTLY:父容器已确定精确尺寸(如
3. 如何优化自定义 View 的性能?
- 关键优化点:
- 避免在
onDraw()
中创建对象(如 Paint 应在初始化时创建) - 使用
setWillNotDraw(false)
明确需要绘制 - 实现
onSaveInstanceState()
保存状态 - 使用
Canvas.clipRect()
减少重绘区域 - 考虑硬件加速兼容性
- 避免在
4. FrameLayout、LinearLayout、RelativeLayout 的使用场景?
- FrameLayout:简单堆叠场景(如加载框、图片叠加)
- LinearLayout:单行 / 单列排列(避免过多 weight 嵌套)
- RelativeLayout:复杂相对位置关系(已逐渐被 ConstraintLayout 替代)
5. 谈谈 ConstraintLayout 的链 (Chain) 机制
- 作用:水平 / 垂直方向上的权重分配
- 类型:
- Spread(默认):均匀分布元素
- Spread Inside:两端元素贴近边界,中间均分
- Packed:元素紧凑排列,可通过
chainStyle
设置权重
6. 什么情况下会导致 View 的过度绘制?如何检测和修复?
- 过度绘制:同一像素被多次绘制(如多层重叠背景)
- 检测工具:开发者选项 → 显示过度绘制区域
- 修复方法:
- 移除不必要的背景
- 使用
<merge>
标签减少层级 - 按需显示隐藏视图(如使用 ViewStub)
- 优化 Drawable 资源(避免多层 Alpha 叠加)
7. 如何实现一个自适应屏幕方向的布局?
- 方案:
- 创建
layout-land
和layout-port
目录存放不同方向布局 - 使用
ConstraintLayout
的Barrier
和Guideline
动态调整 - 代码中监听屏幕旋转事件:
java
@Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 重新布局或加载对应资源 }
- 创建
8. 对比match_parent
和wrap_content
在 MeasureSpec 中的区别
属性 | 父容器为 EXACTLY | 父容器为 AT_MOST |
---|---|---|
match_parent |
EXACTLY (父尺寸) | EXACTLY (父尺寸) |
wrap_content |
AT_MOST (父尺寸) | AT_MOST (父尺寸) |
9. 自定义 View 如何支持 padding 和 margin?
- Padding:在
onDraw()
中处理(如canvas.translate(paddingLeft, paddingTop)
) - Margin:需在父容器(自定义 ViewGroup)中解析 LayoutParams 并应用
10. 如何实现布局的懒加载?
- 方案:
- 使用
ViewStub
(仅加载一次) - 动态 addView(如 Fragment 的
setUserVisibleHint()
) - 配合 Jetpack 的
LazyColumn
/LazyRow
(Compose)
- 使用
八、代码实战问题
1. 手写一个正方形 ImageView
java
public class SquareImageView extends ImageView {
public SquareImageView(Context context) {
super(context);
}
public SquareImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int size = Math.min(width, height);
setMeasuredDimension(size, size);
}
}
2. 优化以下布局(减少层级)
xml
<!-- 原始布局 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<!-- 优化后(ConstraintLayout) -->
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/image"/>
</androidx.constraintlayout.widget.ConstraintLayout>