1.4. 布局与样式

Flutter 的布局系统基于 组件树 结构,每个 Widget 负责自己的布局约束。

1.4.1. 布局模型

1.4.1.1. Flutter 布局三步骤

  1. 传递约束 (父 -> 子):父 Widget 告诉子 Widget 最大/最小宽高

  2. 确定尺寸 (子 -> 父):子 Widget 根据约束确定自己的尺寸

  3. 摆放位置 (父):父 Widget 在自身区域摆放子 Widget

父 Widget
   │   "你的最大宽度是 300,最小是 0"
   ▼
子 Widget  ──→  "我选宽度 200,高度 100"
                  │
                  ▼
             父将子摆放在 x=50, y=10 位置

1.4.2. 核心布局 Widget

1.4.2.1. Row(水平布局)

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,  // 主轴(水平)对齐
  crossAxisAlignment: CrossAxisAlignment.center,     // 交叉轴(垂直)对齐
  children: [
    Icon(Icons.star, size: 48),
    Icon(Icons.favorite, size: 48),
    Icon(Icons.thumb_up, size: 48),
  ],
)

MainAxisAlignment

效果

start

从主轴起点开始排列

center

主轴居中

end

主轴末尾

spaceBetween

两端对齐,中间间距均匀

spaceAround

每个元素左右间距相等

spaceEvenly

所有间距(含两端)相等

1.4.2.2. Column(垂直布局)

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  crossAxisAlignment: CrossAxisAlignment.stretch,  // 子元素撑满交叉轴
  children: [
    Text('第一行'),
    Text('第二行'),
    Text('第三行'),
  ],
)

1.4.3. 布局与定位

1.4.3.1. Stack(层叠布局)

类似 CSS 的绝对定位:

Stack(
  children: [
    // 底部:背景图片
    Image.network('https://example.com/bg.jpg'),
    // 顶部:居中文字
    const Positioned(
      bottom: 20,
      left: 0,
      right: 0,
      child: Text(
        '图片底部文字',
        textAlign: TextAlign.center,
        style: TextStyle(color: Colors.white, fontSize: 18),
      ),
    ),
  ],
)

1.4.3.2. Align(对齐)

Align(
  alignment: Alignment.topRight,
  child: const FlutterLogo(size: 60),
)

// 分数定位(0 到 1 之间)
Align(
  alignment: Alignment(0.5, 0.5),     // 正中心
  child: const Text('居中'),
)

1.4.4. Flexible 与 Expanded

在 Row 或 Column 中,用 Expanded 让子元素拉伸填充剩余空间:

Row(
  children: [
    // 固定宽度
    Container(width: 50, color: Colors.red),
    // 占据剩余空间的 2/3(flex: 2 / (2+1))
    Expanded(
      flex: 2,
      child: Container(color: Colors.green),
    ),
    // 占据剩余空间的 1/3
    Expanded(
      flex: 1,
      child: Container(color: Colors.blue),
    ),
  ],
)
// Flexible 允许子元素小于分配空间
Flexible(
  flex: 1,
  child: Text('这是一段可能很长的文本,如果空间不够会换行'),
)

小技巧

  • Expanded = Flexible(fit: FlexFit.tight)——强制填充分配空间

  • Flexible = Flexible(fit: FlexFit.loose)——允许子元素更小

1.4.5. 弹性与滚动

1.4.5.1. SingleChildScrollView(单子滚动)

SingleChildScrollView(
  padding: const EdgeInsets.all(16),
  child: Column(
    children: [
      // 内容超出屏幕时自动可滚动
      for (int i = 0; i < 50; i++)
        ListTile(title: Text('Item $i')),
    ],
  ),
)

1.4.5.2. ListView(列表)

// 简单列表
ListView(
  children: const [
    ListTile(title: Text('Item 1')),
    ListTile(title: Text('Item 2')),
    Divider(),
  ],
)

// 高性能长列表(按需构建)
ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(child: Text('$index')),
      title: Text('Item $index'),
    );
  },
)

1.4.5.3. GridView(网格)

// 固定交叉轴数量
GridView.count(
  crossAxisCount: 2,
  children: [
    for (int i = 0; i < 20; i++)
      Card(child: Center(child: Text('Card $i'))),
  ],
)

// 自适应列宽
GridView.extent(
  maxCrossAxisExtent: 200,    // 每列最大宽度
  children: [
    for (int i = 0; i < 20; i++)
      Card(child: Center(child: Text('Card $i'))),
  ],
)

1.4.6. 自适应布局

1.4.6.1. LayoutBuilder(获取父约束)

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return _buildWideLayout();
    } else {
      return _buildNarrowLayout();
    }
  },
)

1.4.6.2. MediaQuery(屏幕信息)

// 获取屏幕宽度
final screenWidth = MediaQuery.of(context).size.width;

// 响应式判断
final isSmall = screenWidth < 360;
final isTablet = screenWidth > 768;

// 安全区域(刘海屏、圆角)
final padding = MediaQuery.of(context).padding;

1.4.7. 尺寸与间距速查

Widget

用途

SizedBox(width: x, height: y)

固定尺寸的空白或约束子元素

ConstrainedBox

施加额外的最大/最小约束

FractionallySizedBox

按父容器的比例确定尺寸

AspectRatio

强制宽高比 (如 16:9)

LimitedBox

仅在无约束时施加约束