Row和Column间距控制完全指南

在这里插入图片描述

完整示例:外卖订餐应用

下面是一个完整的外卖订餐应用示例,展示了多种间距控制技巧:

import 'package:flutter/material.dart';

class FoodDeliveryPage extends StatelessWidget {
  const FoodDeliveryPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[100],
      appBar: _buildAppBar(),
      body: Column(
        children: [
          _buildAddressBar(),
          _buildSearchBar(),
          Expanded(
            child: _buildRestaurantList(),
          ),
        ],
      ),
      bottomNavigationBar: _buildBottomBar(),
    );
  }

  AppBar _buildAppBar() {
    return AppBar(
      backgroundColor: Colors.orange,
      elevation: 0,
      leading: IconButton(
        icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
        onPressed: () {},
      ),
      title: const Text('外卖',
          style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
      actions: [
        IconButton(icon: const Icon(Icons.message, color: Colors.white),
            onPressed: () {}),
      ],
    );
  }

  // 地址栏 - 使用 SizedBox 控制间距
  Widget _buildAddressBar() {
    return Container(
      padding: const EdgeInsets.all(12),
      color: Colors.orange,
      child: Row(
        children: [
          const Icon(Icons.location_on, color: Colors.white, size: 20),
          const SizedBox(width: 8),
          const Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '北京市朝阳区',
                  style: TextStyle(
                      color: Colors.white,
                      fontSize: 16,
                      fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 2),
                Text(
                  '望京SOHO T3',
                  style: TextStyle(color: Colors.white70, fontSize: 12),
                ),
              ],
            ),
          ),
          const Icon(Icons.chevron_right, color: Colors.white),
          const SizedBox(width: 16),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
            decoration: BoxDecoration(
              color: Colors.white.withOpacity(0.2),
              borderRadius: BorderRadius.circular(16),
            ),
            child: const Row(
              children: [
                Icon(Icons.notifications, color: Colors.white, size: 16),
                SizedBox(width: 4),
                Text('3', style: TextStyle(color: Colors.white, fontSize: 12)),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // 搜索栏 - 使用 padding 内边距
  Widget _buildSearchBar() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
      color: Colors.orange,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        decoration: BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.circular(20),
        ),
        child: Row(
          children: [
            const Icon(Icons.search, color: Colors.grey, size: 20),
            const SizedBox(width: 8),
            const Expanded(
              child: Text(
                '搜索商家、商品名称',
                style: TextStyle(color: Colors.grey, fontSize: 14),
              ),
            ),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              decoration: BoxDecoration(
                color: Colors.orange[100],
                borderRadius: BorderRadius.circular(12),
              ),
              child: const Text('搜索',
                  style: TextStyle(color: Colors.orange, fontSize: 12)),
            ),
          ],
        ),
      ),
    );
  }

  // 商家列表 - 使用 margin 外边距
  Widget _buildRestaurantList() {
    return ListView(
      padding: const EdgeInsets.all(12),
      children: [
        _buildRestaurantCard(
          name: '麦当劳(望京店)',
          rating: 4.8,
          sales: '月售 5000+',
          deliveryTime: '30分钟',
          deliveryFee: '配送费 ¥3',
          distance: '1.2km',
          tags: ['满30减5', '满50减10'],
          isOpen: true,
        ),
        const SizedBox(height: 12),
        _buildRestaurantCard(
          name: '肯德基(望京店)',
          rating: 4.7,
          sales: '月售 4800+',
          deliveryTime: '25分钟',
          deliveryFee: '配送费 ¥0',
          distance: '0.8km',
          tags: ['新人专享', '优惠券'],
          isOpen: true,
        ),
      ],
    );
  }

  Widget _buildRestaurantCard({
    required String name,
    required double rating,
    required String sales,
    required String deliveryTime,
    required String deliveryFee,
    required String distance,
    required List<String> tags,
    required bool isOpen,
  }) {
    return Container(
      margin: const EdgeInsets.symmetric(vertical: 4),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 店铺图片
            Container(
              width: 100,
              height: 100,
              decoration: BoxDecoration(
                color: Colors.grey[200],
                borderRadius: BorderRadius.circular(6),
              ),
              child:
                  const Icon(Icons.store, size: 40, color: Colors.grey),
            ),
            const SizedBox(width: 12),
            // 店铺信息
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Row(
                    children: [
                      Expanded(
                        child: Text(
                          name,
                          style: const TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.bold,
                            color: Colors.black87,
                          ),
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                        ),
                      ),
                      if (!isOpen)
                        Container(
                          padding:
                              const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                          decoration: BoxDecoration(
                            color: Colors.grey[300],
                            borderRadius: BorderRadius.circular(4),
                          ),
                          child: Text(
                            '休息中',
                            style: TextStyle(color: Colors.grey[600], fontSize: 11),
                          ),
                        ),
                    ],
                  ),
                  const SizedBox(height: 6),
                  // 评分和销量 - 使用 Spacer 均匀分布
                  Row(
                    children: [
                      Row(
                        children: [
                          const Icon(Icons.star,
                              color: Colors.orange, size: 14),
                          const SizedBox(width: 2),
                          Text(
                            rating.toString(),
                            style: const TextStyle(
                              fontSize: 12,
                              color: Colors.orange,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(width: 12),
                      Text(sales,
                          style: TextStyle(color: Colors.grey[600], fontSize: 12)),
                      const Spacer(),
                      Row(
                        children: [
                          const Icon(Icons.access_time,
                              size: 14, color: Colors.grey),
                          const SizedBox(width: 4),
                          Text(deliveryTime,
                              style: TextStyle(color: Colors.grey[600], fontSize: 12)),
                          const SizedBox(width: 8),
                          Container(width: 1, height: 12, color: Colors.grey[300]),
                          const SizedBox(width: 8),
                          Text(deliveryFee,
                              style: TextStyle(color: Colors.grey[600], fontSize: 12)),
                        ],
                      ),
                    ],
                  ),
                  const SizedBox(height: 8),
                  // 优惠标签
                  Wrap(
                    spacing: 6,
                    runSpacing: 6,
                    children: tags.map((tag) {
                      return Container(
                        padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                        decoration: BoxDecoration(
                          color: Colors.red[50],
                          borderRadius: BorderRadius.circular(4),
                          border: Border.all(color: Colors.red[100]!),
                        ),
                        child: Text(
                          tag,
                          style: TextStyle(color: Colors.red[700], fontSize: 11),
                        ),
                      );
                    }).toList(),
                  ),
                  const SizedBox(height: 8),
                  // 距离和配送
                  Row(
                    children: [
                      const Icon(Icons.location_on_outlined,
                          size: 12, color: Colors.grey),
                      const SizedBox(width: 4),
                      Text(distance,
                          style: TextStyle(color: Colors.grey[500], fontSize: 11)),
                      const Spacer(),
                      Container(
                        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
                        decoration: BoxDecoration(
                          color: isOpen ? Colors.orange : Colors.grey[300],
                          borderRadius: BorderRadius.circular(16),
                        ),
                        child: Text(
                          '去点餐',
                          style: TextStyle(
                            color: isOpen ? Colors.white : Colors.grey[600],
                            fontSize: 12,
                          ),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildBottomBar() {
    return Container(
      height: 56,
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border(top: BorderSide(color: Colors.grey[200]!)),
      ),
      child: Flex(
        direction: Axis.horizontal,
        children: [
          // 购物车图标
          Expanded(
            flex: 2,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Stack(
                  children: [
                    const Icon(Icons.shopping_cart,
                        color: Colors.grey, size: 24),
                    Positioned(
                      right: -6,
                      top: -6,
                      child: Container(
                        padding: const EdgeInsets.all(4),
                        decoration: BoxDecoration(
                          color: Colors.red,
                          shape: BoxShape.circle,
                        ),
                        child: const Text('3',
                            style: TextStyle(color: Colors.white, fontSize: 10)),
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 2),
                Text('¥58',
                    style: TextStyle(
                        color: Colors.black87,
                        fontSize: 12,
                        fontWeight: FontWeight.bold)),
              ],
            ),
          ),
          // 配送信息
          Expanded(
            flex: 3,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('另需配送费 ¥3',
                    style: TextStyle(color: Colors.grey[600], fontSize: 11)),
                const SizedBox(height: 2),
                Text('满¥30减¥5',
                    style: TextStyle(color: Colors.red, fontSize: 11)),
              ],
            ),
          ),
          // 去结算按钮
          Expanded(
            flex: 2,
            child: Container(
              margin: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: Colors.orange,
                borderRadius: BorderRadius.circular(20),
              ),
              child: const Center(
                child: Text(
                  '去结算',
                  style: TextStyle(
                      color: Colors.white,
                      fontSize: 14,
                      fontWeight: FontWeight.bold),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(
    home: FoodDeliveryPage(),
    debugShowCheckedModeBanner: false,
  ));
}

关键点说明

  1. SizedBox 间距

    • 固定宽度/高度间距
    • 用于组件之间的固定间距
  2. Spacer 间距

    • 自动填充剩余空间
    • 用于均匀分布或推挤组件
  3. Padding 内边距

    • Container 的 padding 属性
    • EdgeInsets 的各种方法
  4. Margin 外边距

    • Container 的 margin 属性
    • 与 padding 配合使用
  5. Wrap 间距

    • spacing 控制同行间距
    • runSpacing 控制换行间距

Row和Column间距控制完全指南

在Flutter布局中,合理控制间距是构建美观界面的关键。Row和Column提供了多种间距控制方式,包括SizedBox、Spacer、padding等。本文将全面介绍这些间距控制技巧。

一、SizedBox间距控制

SizedBox是最常用的间距控制组件,可以创建指定尺寸的空白区域。

1.1 基本用法

// 水平间距(用于Row)
Row(
  children: [
    Container(width: 60, height: 40, color: Colors.red),
    SizedBox(width: 16),  // 水平间距
    Container(width: 60, height: 40, color: Colors.green),
  ],
)

// 垂直间距(用于Column)
Column(
  children: [
    Container(height: 40, color: Colors.red),
    SizedBox(height: 16),  // 垂直间距
    Container(height: 40, color: Colors.green),
  ],
)

1.2 不同尺寸的间距

// 小间距
SizedBox(width: 4)    // 或 height: 4

// 中间距
SizedBox(width: 8)    // 或 height: 8

// 大间距
SizedBox(width: 16)   // 或 height: 16

// 特大间距
SizedBox(width: 32)   // 或 height: 32

1.3 SizedBox实战案例:按钮组

class ButtonGroup extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        ElevatedButton(
          onPressed: () {},
          child: Text('主要'),
        ),
        SizedBox(width: 12),  // 间距
        OutlinedButton(
          onPressed: () {},
          child: Text('次要'),
        ),
        SizedBox(width: 12),  // 间距
        TextButton(
          onPressed: () {},
          child: Text('链接'),
        ),
      ],
    );
  }
}

二、Spacer弹性间距

Spacer会自动占据Row或Column中的剩余空间。

2.1 基本用法

// 水平Spacer(用于Row)
Row(
  children: [
    Container(width: 60, height: 40, color: Colors.red),
    Spacer(),  // 占据所有剩余空间
    Container(width: 60, height: 40, color: Colors.green),
  ],
)

// 垂直Spacer(用于Column)
Column(
  children: [
    Container(height: 40, color: Colors.red),
    Spacer(),  // 占据所有剩余空间
    Container(height: 40, color: Colors.green),
  ],
)

2.2 多个Spacer

Row(
  children: [
    Container(width: 60, height: 40, color: Colors.red),
    Spacer(flex: 1),  // 占据1份
    Container(width: 60, height: 40, color: Colors.green),
    Spacer(flex: 2),  // 占据2份
    Container(width: 60, height: 40, color: Colors.blue),
  ],
)

2.3 Spacer实战案例:导航栏

class AppBar extends StatelessWidget {
  final String title;

  const AppBar({
    required this.title,
  });

  
  Widget build(BuildContext context) {
    return Container(
      height: 56,
      padding: EdgeInsets.symmetric(horizontal: 16),
      decoration: BoxDecoration(
        color: Colors.blue,
      ),
      child: Row(
        children: [
          IconButton(
            icon: Icon(Icons.menu, color: Colors.white),
            onPressed: () {},
          ),
          Spacer(),  // 占据左侧空间
          Text(
            title,
            style: TextStyle(color: Colors.white, fontSize: 18),
          ),
          Spacer(),  // 占据右侧空间
          IconButton(
            icon: Icon(Icons.search, color: Colors.white),
            onPressed: () {},
          ),
        ],
      ),
    );
  }
}

三、padding内边距

padding可以通过Container的padding属性实现内边距。

3.1 基本用法

Container(
  padding: EdgeInsets.all(16),
  color: Colors.blue[50],
  child: Text('带内边距的容器'),
)

Container(
  padding: EdgeInsets.symmetric(
    horizontal: 16,
    vertical: 12,
  ),
  color: Colors.green[50],
  child: Text('带内边距的容器'),
)

3.2 Padding组件

Padding(
  padding: EdgeInsets.all(16),
  child: Container(
    color: Colors.blue[50],
    child: Text('使用Padding组件'),
  ),
)

3.3 padding实战案例:卡片

class Card extends StatelessWidget {
  final String title;
  final String description;

  const Card({
    required this.title,
    required this.description,
  });

  
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 8,
            offset: Offset(0, 2),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(height: 8),
          Text(
            description,
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey[600],
            ),
          ),
        ],
      ),
    );
  }
}

四、margin外边距

margin可以通过Container的margin属性实现外边距。

4.1 基本用法

Container(
  margin: EdgeInsets.all(16),
  color: Colors.red,
  child: Text('带外边距的容器'),
)

Container(
  margin: EdgeInsets.symmetric(
    horizontal: 16,
    vertical: 8,
  ),
  color: Colors.green,
  child: Text('带外边距的容器'),
)

4.2 margin实战案例:列表项

class ListItem extends StatelessWidget {
  final String title;
  final String subtitle;

  const ListItem({
    required this.title,
    required this.subtitle,
  });

  
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 8),
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: Colors.grey[300]!),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: TextStyle(
              fontSize: 16,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(height: 4),
          Text(
            subtitle,
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey[600],
            ),
          ),
        ],
      ),
    );
  }
}

五、SizedBox vs Spacer对比

特性 SizedBox Spacer
尺寸 固定尺寸 弹性占据剩余空间
灵活性 不可变 可伸缩
使用场景 固定间距 分隔元素
性能 无差异 无差异

5.1 选择指南

// 使用SizedBox的场景
- 需要固定间距
- 精确控制间距值
- 小间距(4-8px)

// 使用Spacer的场景
- 需要弹性间距
- 让元素分散排列
- 大间距

六、间距设计规范

6.1 间距标准

class Spacing {
  static const double xs = 4.0;
  static const double sm = 8.0;
  static const double md = 16.0;
  static const double lg = 24.0;
  static const double xl = 32.0;
  static const double xxl = 48.0;
}

// 使用
SizedBox(height: Spacing.md)
SizedBox(width: Spacing.lg)

6.2 间距应用示例

// 标题与内容的间距
Text('标题')
SizedBox(height: Spacing.md)  // 16px
Text('内容')

// 按钮之间的间距
ElevatedButton(...)
SizedBox(width: Spacing.sm)  // 8px
OutlinedButton(...)

// 卡片之间的间距
Card(..., margin: EdgeInsets.symmetric(vertical: Spacing.md))
Card(..., margin: EdgeInsets.symmetric(vertical: Spacing.md))

七、实战案例:分段式列表

7.1 需求描述

创建一个分段式列表,每个分段有标题和列表项。

7.2 完整实现

class SectionedList extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildSection('今天', [
          _buildListItem('完成任务A'),
          _buildListItem('完成任务B'),
          _buildListItem('完成任务C'),
        ]),
        SizedBox(height: Spacing.lg),
        _buildSection('明天', [
          _buildListItem('计划任务D'),
          _buildListItem('计划任务E'),
        ]),
      ],
    );
  }

  Widget _buildSection(String title, List<Widget> items) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: EdgeInsets.symmetric(horizontal: Spacing.md),
          child: Text(
            title,
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
              color: Colors.blue[700],
            ),
          ),
        ),
        SizedBox(height: Spacing.md),
        ...items,
      ],
    );
  }

  Widget _buildListItem(String text) {
    return Padding(
      padding: EdgeInsets.symmetric(
        horizontal: Spacing.md,
        vertical: Spacing.sm,
      ),
      child: Row(
        children: [
          Icon(Icons.check_circle, color: Colors.green),
          SizedBox(width: Spacing.sm),
          Expanded(child: Text(text)),
        ],
      ),
    );
  }
}

// 间距常量
class Spacing {
  static const double xs = 4.0;
  static const double sm = 8.0;
  static const double md = 16.0;
  static const double lg = 24.0;
}

八、实战案例:弹性卡片

class FlexibleCard extends StatelessWidget {
  final String title;
  final String description;

  const FlexibleCard({
    required this.title,
    required this.description,
  });

  
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(Spacing.md),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 8,
          ),
        ],
      ),
      child: Column(
        children: [
          Row(
            children: [
              Expanded(child: Text(title)),
              SizedBox(width: Spacing.sm),
              Icon(Icons.expand_more),
            ],
          ),
          SizedBox(height: Spacing.sm),
          Text(
            description,
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey[600],
            ),
          ),
        ],
      ),
    );
  }
}

九、间距性能优化

9.1 使用const构造函数

const SizedBox(width: 16)
const SizedBox(height: 8)

9.2 提取间距常量

class Spacing {
  static const SizedBox h4 = SizedBox(width: 4);
  static const SizedBox h8 = SizedBox(width: 8);
  static const SizedBox h16 = SizedBox(width: 16);
  static const SizedBox v4 = SizedBox(height: 4);
  static const SizedBox v8 = SizedBox(height: 8);
  static const SizedBox v16 = SizedBox(height: 16);
}

// 使用
Row(
  children: [
    Text('A'),
    Spacing.h8,
    Text('B'),
  ],
)

十、常见间距问题与解决方案

10.1 间距不够

问题:设置了间距但效果不明显

解决方案:增加间距值

// 不够
SizedBox(width: 4)

// 调整
SizedBox(width: 8)
SizedBox(width: 16)

10.2 间距过大

问题:间距太大,浪费空间

解决方案:减少间距值

// 过大
SizedBox(width: 32)

// 调整
SizedBox(width: 16)
SizedBox(width: 8)

10.3 不一致

问题:整个应用的间距不统一

解决方案:使用间距常量类

class AppSpacing {
  static const double xs = 4.0;
  static const double sm = 8.0;
  static const double md = 16.0;
  static const double lg = 24.0;
}

// 在整个应用中使用统一标准

十一、总结

合理的间距控制是构建美观界面的关键。通过SizedBox、Spacer、padding、margin等方式,可以精确控制组件之间的间距。

核心要点

  1. SizedBox:固定间距,精确控制
  2. Spacer:弹性间距,占据剩余空间
  3. padding:内边距,控制内容与边界的距离
  4. margin:外边距,控制组件与周围元素的距离
  5. 统一标准:使用间距常量类保持一致性

应用场景

// 使用SizedBox的场景
- 固定的小间距
- 精确控制间距值
- 按钮之间的间距

// 使用Spacer的场景
- 导航栏两侧留白
- 元素分散排列
- 弹性分隔布局

// 使用padding的场景
- 卡片内边距
- 容器内容间距
- 按钮内边距

// 使用margin的场景
- 列表项间距
- 卡片之间的间距
- 块级元素间距

通过掌握这些间距控制技巧,你将能够构建出美观、协调、符合设计规范的用户界面。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐