4.7 对齐与相对定位(Align)
📚 章节概览
如果只想简单地调整一个子组件在父组件中的位置,使用 Align 组件会比 Stack/Positioned 更简单。本章节将学习:
- Align - 对齐组件
- alignment - 对齐方式
- Alignment - 中心坐标系
- FractionalOffset - 左上角坐标系
- widthFactor/heightFactor - 尺寸因子
- Center - 居中组件
- Align vs Stack - 两种定位方式的对比
🎯 核心知识点
Align vs Stack/Positioned
| 特性 | Align | Stack/Positioned |
|---|---|---|
| 子组件数量 | 1个 | 多个 |
| 定位参考系 | alignment计算 | 四个顶点 |
| 是否堆叠 | ❌ 否 | ✅ 是 |
| 使用场景 | 简单对齐 | 复杂层叠 |
| 易用性 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
1️⃣ Align(对齐组件)
1.1 构造函数
Align({
Key? key,
AlignmentGeometry alignment = Alignment.center,
double? widthFactor,
double? heightFactor,
Widget? child,
})
1.2 主要属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
alignment | AlignmentGeometry | center | 对齐方式 |
widthFactor | double? | null | 宽度因子 |
heightFactor | double? | null | 高度因子 |
child | Widget? | null | 子组件 |
1.3 基础用法
Container(
height: 120,
color: Colors.blue[50],
child: Align(
alignment: Alignment.topRight,
child: FlutterLogo(size: 60),
),
)
效果: FlutterLogo 在容器右上角
2️⃣ Alignment(中心坐标系)
2.1 坐标系统
(-1, -1) (0, -1) (1, -1)
●----------●----------●
| |
(-1, 0) ● | (0, 0) | ● (1, 0)
| ● |
| |
●----------●----------●
(-1, 1) (0, 1) (1, 1)
特点:
- 坐标原点:矩形中心
- X轴范围:[-1, 1](左→右)
- Y轴范围:[-1, 1](上→下)
2.2 坐标转换公式
实际偏移x = Alignment.x * (父宽度 - 子宽度) / 2 + (父宽度 - 子宽度) / 2
实际偏移y = Alignment.y * (父高度 - 子高度) / 2 + (父高度 - 子高度) / 2
简化公式:
实际偏移x = Alignment.x * (父宽度 - 子宽度) / 2
实际偏移y = Alignment.y * (父高度 - 子高度) / 2
2.3 预定义常量
// 9个预定义对齐位置
Alignment.topLeft // (-1, -1)
Alignment.topCenter // (0, -1)
Alignment.topRight // (1, -1)
Alignment.centerLeft // (-1, 0)
Alignment.center // (0, 0)
Alignment.centerRight // (1, 0)
Alignment.bottomLeft // (-1, 1)
Alignment.bottomCenter // (0, 1)
Alignment.bottomRight // (1, 1)
2.4 自定义坐标
Container(
width: 120,
height: 120,
child: Align(
alignment: Alignment(2.0, 0.0),
child: FlutterLogo(size: 60),
),
)
计算:
- 父宽度 = 120,子宽度 = 60
- 实际偏移x = 2.0 * (120 - 60) / 2 = 60
- 实际偏移y = 0.0 * (120 - 60) / 2 = 0
效果: FlutterLogo 向右偏移60像素
2.5 坐标计算示例
示例1:Alignment(-1, -1)
// 假设父容器 200×200,子组件 60×60
Alignment(-1, -1)
// 计算偏移
x = -1 * (200 - 60) / 2 = -70
y = -1 * (200 - 60) / 2 = -70
// 结果:左上角(相对中心偏移-70, -70)
示例2:Alignment(0.5, 0.5)
// 假设父容器 200×200,子组件 60×60
Alignment(0.5, 0.5)
// 计算偏移
x = 0.5 * (200 - 60) / 2 = 35
y = 0.5 * (200 - 60) / 2 = 35
// 结果:中心偏右下(相对中心偏移35, 35)
3️⃣ widthFactor 和 heightFactor
3.1 Factor规则
| Factor值 | 说明 | Align尺寸 |
|---|---|---|
| null | 占用尽可能多的空间 | 填满父容器 |
| 1.0 | 等于子组件尺寸 | 子组件尺寸 |
| 2.0 | 子组件尺寸的2倍 | 子组件尺寸 × 2 |
计算公式:
Align宽度 = child宽度 * widthFactor
Align高度 = child高度 * heightFactor
3.2 示例对比
无factor(占满父容器)
Container(
width: 200,
height: 100,
color: Colors.grey[200],
child: Align(
alignment: Alignment.topRight,
child: FlutterLogo(size: 60),
),
)
效果: Align尺寸 = 200×100(父容器尺寸)
widthFactor: 2, heightFactor: 2
Container(
color: Colors.grey[200],
child: Align(
widthFactor: 2,
heightFactor: 2,
alignment: Alignment.topRight,
child: FlutterLogo(size: 60),
),
)
效果: Align尺寸 = 120×120(60 × 2)
widthFactor: 1, heightFactor: 1
Align(
widthFactor: 1,
heightFactor: 1,
alignment: Alignment.center,
child: FlutterLogo(size: 60),
)
效果: Align尺寸 = 60×60(紧包裹子组件)
4️⃣ FractionalOffset(左上角坐标系)
4.1 坐标系统
(0, 0) ●----------●----------● (1, 0)
| |
| |
● ● ●
| |
| |
●----------●----------●
(0, 1) (1, 1)
特点:
- 坐标原点:矩形左上角
- X轴范围:[0, 1](左→右)
- Y轴范围:[0, 1](上→下)
- 与布局系统一致
4.2 坐标转换公式
实际偏移x = FractionalOffset.x * (父宽度 - 子宽度)
实际偏移y = FractionalOffset.y * (父高度 - 子高度)
4.3 示例
Container(
width: 120,
height: 120,
color: Colors.blue[50],
child: Align(
alignment: FractionalOffset(0.2, 0.6),
child: FlutterLogo(size: 60),
),
)
计算:
- 父宽度 = 120,子宽度 = 60
- 实际偏移x = 0.2 * (120 - 60) = 12
- 实际偏移y = 0.6 * (120 - 60) = 36
效果: FlutterLogo 距左上角 (12, 36)
4.4 Alignment vs FractionalOffset
| 特性 | Alignment | FractionalOffset |
|---|---|---|
| 坐标原点 | 中心 | 左上角 |
| X范围 | [-1, 1] | [0, 1] |
| Y范围 | [-1, 1] | [0, 1] |
| 左上角 | (-1, -1) | (0, 0) |
| 中心 | (0, 0) | (0.5, 0.5) |
| 右下角 | (1, 1) | (1, 1) |
| 推荐度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
建议: 优先使用 FractionalOffset,更符合布局系统习惯
5️⃣ Center(居中组件)
5.1 定义
class Center extends Align {
const Center({
Key? key,
double? widthFactor,
double? heightFactor,
Widget? child,
}) : super(
key: key,
widthFactor: widthFactor,
heightFactor: heightFactor,
child: child,
);
}
本质: Center = Align(alignment: Alignment.center)
5.2 示例对比
无factor
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Center(
child: Text('Hello'),
),
)
效果: Center占满父容器,文本居中
widthFactor: 1, heightFactor: 1
DecoratedBox(
decoration: BoxDecoration(color: Colors.red),
child: Center(
widthFactor: 1,
heightFactor: 1,
child: Text('Hello'),
),
)
效果: Center紧包裹文本
5.3 等价写法
// 写法1:使用Center
Center(child: Text('Hello'))
// 写法2:使用Align
Align(
alignment: Alignment.center,
child: Text('Hello'),
)
// 完全等价!
6️⃣ Align vs Stack/Positioned
6.1 定位参考系对比
Stack/Positioned
Stack(
children: [
Positioned(
right: 10, // 距右边10
bottom: 10, // 距底部10
child: Widget(),
),
],
)
特点: 直接指定距四个边的距离
Align
Align(
alignment: Alignment.bottomRight, // 需要通过坐标计算
child: Padding(
padding: EdgeInsets.all(10),
child: Widget(),
),
)
特点: 通过 alignment 坐标转换公式计算位置
6.2 子组件数量对比
Stack - 多个子组件
Stack(
children: [
Container(color: Colors.red), // 底层
Container(color: Colors.green), // 中层
Container(color: Colors.blue), // 顶层
],
)
支持多个子组件堆叠
Align - 单个子组件
Align(
alignment: Alignment.center,
child: Container(color: Colors.blue), // 只能一个
)
只支持一个子组件
6.3 使用场景
| 场景 | 推荐 | 原因 |
|---|---|---|
| 简单对齐 | Align | 代码更简洁 |
| 多层堆叠 | Stack | 支持多个子组件 |
| 精确定位 | Stack | 直接指定边距 |
| 相对定位 | Align | 类似Web的relative |
🤔 常见问题(FAQ)
Q1: 如何实现右下角10像素间距?
A: 两种方法
方法1:Stack + Positioned
Stack(
children: [
Positioned(
right: 10,
bottom: 10,
child: FlutterLogo(size: 60),
),
],
)
方法2:Align + Padding
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.all(10),
child: FlutterLogo(size: 60),
),
)
Q2: Alignment超出[-1, 1]范围会怎样?
A: 可以超出范围,子组件会移出可见区域
Container(
width: 120,
height: 120,
clipBehavior: Clip.none, // 不裁剪
child: Align(
alignment: Alignment(3, 0), // 超出范围
child: FlutterLogo(size: 60),
),
)
效果: FlutterLogo 移出容器右侧
Q3: 如何让Align只占用子组件大小?
A: 设置 widthFactor 和 heightFactor 为 1
Align(
widthFactor: 1,
heightFactor: 1,
alignment: Alignment.topLeft,
child: FlutterLogo(size: 60),
)
效果: Align尺寸 = 60×60
Q4: Alignment(0, 0) 和 FractionalOffset(0.5, 0.5) 一样吗?
A: 是的,都表示中心位置
// Alignment(0, 0) - 坐标原点在中心
Alignment(0, 0)
→ 偏移 = 0 * (父宽 - 子宽) / 2 = 0
→ 结果:中心
// FractionalOffset(0.5, 0.5) - 坐标原点在左上角
FractionalOffset(0.5, 0.5)
→ 偏移 = 0.5 * (父宽 - 子宽)
→ 结果:中心(距左上角50%)
Q5: 如何实现类似Web的相对定位?
A: 使用 Align
<!-- Web CSS -->
<div style="position: relative; left: 20px; top: 10px;">
Content
</div>
// Flutter Align
Align(
alignment: FractionalOffset(0.2, 0.1),
child: Text('Content'),
)
🎯 跟着做练习
练习1:实现一个带标签的头像
目标: 头像右上角显示"在线"标签
步骤:
- 使用 Stack 或 Align
- 头像为主体
- 标签叠加在右上角
💡 查看答案(方案1:Stack)
class AvatarWithBadge extends StatelessWidget {
const AvatarWithBadge({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 80,
height: 80,
child: Stack(
children: [
// 头像
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'A',
style: TextStyle(color: Colors.white, fontSize: 32),
),
),
),
// 在线标签
Positioned(
right: 0,
top: 0,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(10),
),
child: Text(
'在线',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
),
],
),
);
}
}
💡 查看答案(方案2:Align)
class AvatarWithBadge extends StatelessWidget {
const AvatarWithBadge({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: 80,
height: 80,
child: Stack(
children: [
// 头像
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'A',
style: TextStyle(color: Colors.white, fontSize: 32),
),
),
),
// 在线标签(使用Align)
Align(
alignment: Alignment.topRight,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(10),
),
child: Text(
'在线',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
),
],
),
);
}
}
练习2:实现一个浮动操作按钮(FAB)
目标: 在页面右下角放置一个圆形按钮
步骤:
- 使用 Align
- alignment设置为右下角
- 添加适当的间距
💡 查看答案
class FloatingButton extends StatelessWidget {
const FloatingButton({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 300,
color: Colors.grey[200],
child: Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(16),
child: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
),
),
);
}
}
📋 小结
核心概念
| 组件 | 说明 | 使用场景 |
|---|---|---|
| Align | 对齐组件 | 简单对齐定位 |
| Alignment | 中心坐标系 | 通用对齐 |
| FractionalOffset | 左上角坐标系 | 精确定位(推荐) |
| Center | 居中组件 | 快速居中 |
Alignment常用值
| 位置 | Alignment | FractionalOffset |
|---|---|---|
| 左上 | (-1, -1) | (0, 0) |
| 顶部 | (0, -1) | (0.5, 0) |
| 右上 | (1, -1) | (1, 0) |
| 左边 | (-1, 0) | (0, 0.5) |
| 居中 | (0, 0) | (0.5, 0.5) |
| 右边 | (1, 0) | (1, 0.5) |
| 左下 | (-1, 1) | (0, 1) |
| 底部 | (0, 1) | (0.5, 1) |
| 右下 | (1, 1) | (1, 1) |
记忆技巧
- Alignment:中心为原点,类似数学坐标系
- FractionalOffset:左上为原点,类似CSS布局
- Center:就是 Align 的快捷方式
- Factor为1:紧包裹子组件
- Factor为null:占满父容器