Flutter流式布局 #

一、Wrap #

1.1 基本用法 #

Wrap是一个可以自动换行的布局容器,当空间不足时会自动换行。

dart
Wrap({
  Key? key,
  Axis direction = Axis.horizontal,
  WrapAlignment alignment = WrapAlignment.start,
  double spacing = 0.0,
  WrapAlignment runAlignment = WrapAlignment.start,
  double runSpacing = 0.0,
  WrapCrossAlignment crossAxisAlignment = WrapCrossAlignment.start,
  TextDirection? textDirection,
  VerticalDirection verticalDirection = VerticalDirection.down,
  Clip clipBehavior = Clip.none,
  List<Widget> children = const <Widget>[],
})

Wrap(
  children: [
    Chip(label: Text('Tag 1')),
    Chip(label: Text('Tag 2')),
    Chip(label: Text('Tag 3')),
    Chip(label: Text('Tag 4')),
    Chip(label: Text('Tag 5')),
  ],
)

1.2 direction属性 #

控制主轴方向:

dart
Wrap(
  direction: Axis.horizontal,
  children: [...],
)

Wrap(
  direction: Axis.vertical,
  children: [...],
)

1.3 spacing属性 #

主轴方向子Widget之间的间距:

dart
Wrap(
  spacing: 8,
  children: [
    Chip(label: Text('Tag 1')),
    Chip(label: Text('Tag 2')),
    Chip(label: Text('Tag 3')),
  ],
)

1.4 runSpacing属性 #

交叉轴方向行/列之间的间距:

dart
Wrap(
  spacing: 8,
  runSpacing: 16,
  children: [
    Chip(label: Text('Tag 1')),
    Chip(label: Text('Tag 2')),
    Chip(label: Text('Tag 3')),
    Chip(label: Text('Tag 4')),
    Chip(label: Text('Tag 5')),
  ],
)

1.5 alignment属性 #

主轴方向对齐方式:

dart
Wrap(
  alignment: WrapAlignment.center,
  children: [...],
)

WrapAlignment.start
WrapAlignment.end
WrapAlignment.center
WrapAlignment.spaceBetween
WrapAlignment.spaceAround
WrapAlignment.spaceEvenly

1.6 runAlignment属性 #

交叉轴方向对齐方式:

dart
Wrap(
  runAlignment: WrapAlignment.center,
  children: [...],
)

1.7 crossAxisAlignment属性 #

交叉轴对齐方式:

dart
Wrap(
  crossAxisAlignment: WrapCrossAlignment.center,
  children: [
    Container(width: 50, height: 50, color: Colors.red),
    Container(width: 50, height: 100, color: Colors.green),
    Container(width: 50, height: 75, color: Colors.blue),
  ],
)

WrapCrossAlignment.start
WrapCrossAlignment.end
WrapCrossAlignment.center

二、Wrap实战示例 #

2.1 标签云 #

dart
Wrap(
  spacing: 8,
  runSpacing: 8,
  children: [
    'Flutter',
    'Dart',
    'Android',
    'iOS',
    'Web',
    'Desktop',
    'Mobile',
    'Cross-platform',
  ].map((tag) => Chip(
    label: Text(tag),
    backgroundColor: Colors.blue.shade100,
  )).toList(),
)

2.2 可删除标签 #

dart
class TagList extends StatefulWidget {
  @override
  State<TagList> createState() => _TagListState();
}

class _TagListState extends State<TagList> {
  final List<String> _tags = ['Flutter', 'Dart', 'Android', 'iOS'];
  
  void _removeTag(String tag) {
    setState(() {
      _tags.remove(tag);
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 8,
      runSpacing: 8,
      children: _tags.map((tag) => Chip(
        label: Text(tag),
        deleteIcon: Icon(Icons.close, size: 16),
        onDeleted: () => _removeTag(tag),
      )).toList(),
    );
  }
}

2.3 图片网格 #

dart
Wrap(
  spacing: 4,
  runSpacing: 4,
  children: List.generate(9, (index) {
    return Container(
      width: (MediaQuery.of(context).size.width - 16) / 3 - 4,
      height: (MediaQuery.of(context).size.width - 16) / 3 - 4,
      color: Colors.primaries[index % Colors.primaries.length],
      child: Center(child: Text('${index + 1}')),
    );
  }),
)

2.4 搜索历史 #

dart
Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text('搜索历史', style: TextStyle(fontWeight: FontWeight.bold)),
        TextButton(
          onPressed: () {},
          child: Text('清空'),
        ),
      ],
    ),
    SizedBox(height: 8),
    Wrap(
      spacing: 8,
      runSpacing: 8,
      children: [
        'Flutter教程',
        'Dart语言',
        'Widget详解',
        '状态管理',
      ].map((keyword) => ActionChip(
        label: Text(keyword),
        onPressed: () {
          print('Search: $keyword');
        },
      )).toList(),
    ),
  ],
)

三、Flow #

3.1 基本用法 #

Flow是一个更底层的流式布局,需要自定义布局逻辑。

dart
Flow({
  Key? key,
  required FlowDelegate delegate,
  List<Widget> children = const <Widget>[],
  Clip clipBehavior = Clip.hardEdge,
})

class MyFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    double x = 0;
    double y = 0;
    
    for (int i = 0; i < context.childCount; i++) {
      var child = context.getChildSize(i);
      if (x + child!.width > context.size.width) {
        x = 0;
        y += child.height;
      }
      context.paintChild(i, transform: Matrix4.translationValues(x, y, 0));
      x += child.width;
    }
  }
  
  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) {
    return false;
  }
}

Flow(
  delegate: MyFlowDelegate(),
  children: [
    Container(width: 80, height: 80, color: Colors.red),
    Container(width: 100, height: 60, color: Colors.green),
    Container(width: 60, height: 100, color: Colors.blue),
  ],
)

3.2 自定义布局示例 #

dart
class CircleFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    final centerX = context.size.width / 2;
    final centerY = context.size.height / 2;
    final radius = 100.0;
    
    for (int i = 0; i < context.childCount; i++) {
      final angle = (2 * 3.14159 * i) / context.childCount;
      final x = centerX + radius * cos(angle) - context.getChildSize(i)!.width / 2;
      final y = centerY + radius * sin(angle) - context.getChildSize(i)!.height / 2;
      
      context.paintChild(i, transform: Matrix4.translationValues(x, y, 0));
    }
  }
  
  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) => false;
}

Flow(
  delegate: CircleFlowDelegate(),
  children: List.generate(6, (index) => 
    Container(
      width: 50,
      height: 50,
      color: Colors.primaries[index % Colors.primaries.length],
      child: Center(child: Text('${index + 1}')),
    ),
  ),
)

四、Wrap vs Flow #

特性 Wrap Flow
易用性 简单 复杂
灵活性 一般
性能 较好 更好
自定义 有限 完全自定义

五、总结 #

5.1 核心属性 #

属性 说明
direction 主轴方向
spacing 主轴间距
runSpacing 交叉轴间距
alignment 主轴对齐
runAlignment 交叉轴对齐

5.2 下一步 #

掌握了流式布局后,让我们学习 层叠布局

最后更新:2026-03-28