第4章:布局类组件 —— 4.7 对齐与相对定位(Align)

49 阅读7分钟

4.7 对齐与相对定位(Align)

📚 章节概览

如果只想简单地调整一个子组件在父组件中的位置,使用 Align 组件会比 Stack/Positioned 更简单。本章节将学习:

  • Align - 对齐组件
  • alignment - 对齐方式
  • Alignment - 中心坐标系
  • FractionalOffset - 左上角坐标系
  • widthFactor/heightFactor - 尺寸因子
  • Center - 居中组件
  • Align vs Stack - 两种定位方式的对比

🎯 核心知识点

Align vs Stack/Positioned

特性AlignStack/Positioned
子组件数量1个多个
定位参考系alignment计算四个顶点
是否堆叠❌ 否✅ 是
使用场景简单对齐复杂层叠
易用性⭐⭐⭐⭐⭐⭐⭐⭐

1️⃣ Align(对齐组件)

1.1 构造函数

Align({
  Key? key,
  AlignmentGeometry alignment = Alignment.center,
  double? widthFactor,
  double? heightFactor,
  Widget? child,
})

1.2 主要属性

属性类型默认值说明
alignmentAlignmentGeometrycenter对齐方式
widthFactordouble?null宽度因子
heightFactordouble?null高度因子
childWidget?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

特性AlignmentFractionalOffset
坐标原点中心左上角
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: 设置 widthFactorheightFactor 为 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:实现一个带标签的头像

目标: 头像右上角显示"在线"标签

步骤:

  1. 使用 Stack 或 Align
  2. 头像为主体
  3. 标签叠加在右上角
💡 查看答案(方案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)

目标: 在页面右下角放置一个圆形按钮

步骤:

  1. 使用 Align
  2. alignment设置为右下角
  3. 添加适当的间距
💡 查看答案
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常用值

位置AlignmentFractionalOffset
左上(-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)

记忆技巧

  1. Alignment:中心为原点,类似数学坐标系
  2. FractionalOffset:左上为原点,类似CSS布局
  3. Center:就是 Align 的快捷方式
  4. Factor为1:紧包裹子组件
  5. Factor为null:占满父容器

🔗 相关资源