Skip to content

第6章:Flutter 布局开发

6.1 布局核心原则

1. 组件嵌套

Flutter 的布局是通过组件的嵌套来实现的,每个组件都可以包含子组件,形成一个树状结构。

原则

  • 合理嵌套组件,避免过深的嵌套层次(一般不超过5层)
  • 使用 Container、SizedBox 等容器组件来控制布局
  • 利用布局组件的属性来控制子组件的排列和对齐

2. 布局方向

Flutter 提供了多种布局方向的组件:

  • 水平布局:Row 组件
  • 垂直布局:Column 组件
  • 层叠布局:Stack 组件
  • 流式布局:Wrap 组件

3. 尺寸适配

  • 固定尺寸:使用 SizedBox、Container 等设置固定宽高
  • 自适应尺寸:使用 Expanded、Flexible 等组件
  • 屏幕适配:使用 MediaQuery 获取屏幕尺寸
  • 响应式布局:根据屏幕尺寸调整布局

6.2 线性布局(Row、Column)

Row(水平布局)

Row 组件用于在水平方向排列子组件:

dart
Row(
  mainAxisAlignment: MainAxisAlignment.center, // 主轴对齐方式
  crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴对齐方式
  children: [
    Container(width: 50, height: 50, color: Colors.red),
    Container(width: 50, height: 50, color: Colors.green),
    Container(width: 50, height: 50, color: Colors.blue),
  ],
);

主轴对齐方式(mainAxisAlignment)

描述
MainAxisAlignment.start左对齐
MainAxisAlignment.center居中对齐
MainAxisAlignment.end右对齐
MainAxisAlignment.spaceBetween两端对齐,中间间距相等
MainAxisAlignment.spaceAround每个子组件两侧间距相等
MainAxisAlignment.spaceEvenly所有间距相等

交叉轴对齐方式(crossAxisAlignment)

描述
CrossAxisAlignment.start顶部对齐
CrossAxisAlignment.center居中对齐
CrossAxisAlignment.end底部对齐
CrossAxisAlignment.stretch拉伸填充
CrossAxisAlignment.baseline基线对齐

Column(垂直布局)

Column 组件用于在垂直方向排列子组件:

dart
Column(
  mainAxisAlignment: MainAxisAlignment.center, // 主轴对齐方式
  crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴对齐方式
  children: [
    Container(width: 50, height: 50, color: Colors.red),
    Container(width: 50, height: 50, color: Colors.green),
    Container(width: 50, height: 50, color: Colors.blue),
  ],
);

主轴对齐方式(mainAxisAlignment)

描述
MainAxisAlignment.start顶部对齐
MainAxisAlignment.center居中对齐
MainAxisAlignment.end底部对齐
MainAxisAlignment.spaceBetween两端对齐,中间间距相等
MainAxisAlignment.spaceAround每个子组件两侧间距相等
MainAxisAlignment.spaceEvenly所有间距相等

交叉轴对齐方式(crossAxisAlignment)

描述
CrossAxisAlignment.start左对齐
CrossAxisAlignment.center居中对齐
CrossAxisAlignment.end右对齐
CrossAxisAlignment.stretch拉伸填充
CrossAxisAlignment.baseline基线对齐

Expanded 组件

Expanded 组件用于实现子组件自适应填充剩余空间:

dart
Row(
  children: [
    Container(width: 50, height: 50, color: Colors.red),
    Expanded(
      flex: 1, // 权重
      child: Container(height: 50, color: Colors.green),
    ),
    Container(width: 50, height: 50, color: Colors.blue),
  ],
);

6.3 弹性布局(Flex、Expanded)

Flex 布局

Flex 组件是 Row 和 Column 的父类,可以通过 direction 属性指定布局方向:

dart
Flex(
  direction: Axis.horizontal, // 水平布局
  // direction: Axis.vertical, // 垂直布局
  mainAxisAlignment: MainAxisAlignment.center,
  children: [
    Container(width: 50, height: 50, color: Colors.red),
    Container(width: 50, height: 50, color: Colors.green),
    Container(width: 50, height: 50, color: Colors.blue),
  ],
);

Flexible 组件

Flexible 组件与 Expanded 类似,但可以设置 fit 属性:

dart
Row(
  children: [
    Container(width: 50, height: 50, color: Colors.red),
    Flexible(
      flex: 1,
      fit: FlexFit.tight, // 强制填充
      // fit: FlexFit.loose, // 不强制填充
      child: Container(height: 50, color: Colors.green),
    ),
    Container(width: 50, height: 50, color: Colors.blue),
  ],
);

6.4 流式布局(Wrap、Flow)

Wrap(流式布局)

Wrap 组件用于子组件超出屏幕后自动换行:

dart
Wrap(
  direction: Axis.horizontal, // 水平方向
  spacing: 10, // 水平间距
  runSpacing: 10, // 垂直间距
  alignment: WrapAlignment.center, // 对齐方式
  children: List.generate(10, (index) => Container(
    width: 80,
    height: 80,
    color: Colors.primaries[index % Colors.primaries.length],
    child: Center(child: Text('$index')),
  )),
);

Flow(自定义流式布局)

Flow 组件提供了更灵活的流式布局控制,但使用较复杂:

dart
Flow(
  delegate: MyFlowDelegate(),
  children: List.generate(10, (index) => Container(
    width: 80,
    height: 80,
    color: Colors.primaries[index % Colors.primaries.length],
    child: Center(child: Text('$index')),
  )),
);

class MyFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    double x = 0.0;
    double y = 0.0;
    double rowHeight = 0.0;
    
    for (int i = 0; i < context.childCount; i++) {
      Size size = context.getChildSize(i)!;
      if (x + size.width > context.size.width) {
        x = 0.0;
        y += rowHeight;
        rowHeight = 0.0;
      }
      context.paintChild(i, transform: Matrix4.translationValues(x, y, 0.0));
      x += size.width + 10;
      rowHeight = math.max(rowHeight, size.height + 10);
    }
  }

  @override
  bool shouldRepaint(covariant FlowDelegate oldDelegate) {
    return false;
  }
}

6.5 层叠布局(Stack、Positioned)

Stack(层叠容器)

Stack 组件用于子组件按顺序叠加:

dart
Stack(
  alignment: Alignment.center, // 对齐方式
  fit: StackFit.loose, // 适应方式
  children: [
    Container(
      width: 200,
      height: 200,
      color: Colors.red,
    ),
    Container(
      width: 150,
      height: 150,
      color: Colors.green,
    ),
    Container(
      width: 100,
      height: 100,
      color: Colors.blue,
    ),
  ],
);

Positioned(定位组件)

Positioned 组件用于控制子组件在 Stack 中的位置:

dart
Stack(
  children: [
    Container(
      width: 200,
      height: 200,
      color: Colors.grey,
    ),
    Positioned(
      top: 20,
      left: 20,
      child: Container(
        width: 50,
        height: 50,
        color: Colors.red,
      ),
    ),
    Positioned(
      bottom: 20,
      right: 20,
      child: Container(
        width: 50,
        height: 50,
        color: Colors.blue,
      ),
    ),
  ],
);

6.6 卡片布局(Card)

Card 组件用于实现带阴影、圆角的卡片效果:

dart
Card(
  elevation: 5, // 阴影高度
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(10), // 圆角
  ),
  margin: const EdgeInsets.all(10), // 外边距
  child: Padding(
    padding: const EdgeInsets.all(16), // 内边距
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Card Title',
          style: TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 8),
        const Text(
          'This is the content of the card. It can contain text, images, buttons, etc.',
        ),
        const SizedBox(height: 16),
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            TextButton(
              onPressed: () {},
              child: const Text('Cancel'),
            ),
            ElevatedButton(
              onPressed: () {},
              child: const Text('OK'),
            ),
          ],
        ),
      ],
    ),
  ),
);

6.7 布局适配

MediaQuery 获取屏幕尺寸

dart
final size = MediaQuery.of(context).size;
final width = size.width;
final height = size.height;

Container(
  width: width * 0.8, // 屏幕宽度的80%
  height: height * 0.4, // 屏幕高度的40%
  color: Colors.blue,
  child: const Center(child: Text('Responsive Container')),
);

屏幕适配插件推荐

flutter_screenutil

安装

yaml
dependencies:
  flutter_screenutil: ^5.8.4

使用

dart
// 在 main.dart 中初始化
void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(375, 812), // 设计稿尺寸
      minTextAdapt: true,
      splitScreenMode: true,
      builder: (context, child) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(),
        );
      },
    );
  }
}

// 在组件中使用
Container(
  width: 100.w, // 自动适配
  height: 100.h, // 自动适配
  child: Text(
    'Hello',
    style: TextStyle(fontSize: 16.sp), // 自动适配
  ),
);

6.8 实操案例:搭建首页布局

目标

使用布局组件搭建一个简单的首页布局,包含线性布局、卡片布局、图片组件等。

步骤 1:创建项目

使用 VS Code 或 Android Studio 创建一个新的 Flutter 项目。

步骤 2:创建首页布局

修改 lib/main.dart 文件:

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Home Page',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
        actions: [
          IconButton(
            onPressed: () {},
            icon: const Icon(Icons.search),
          ),
          IconButton(
            onPressed: () {},
            icon: const Icon(Icons.notifications),
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 欢迎区域
            const Text(
              'Welcome Back!',
              style: TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 8),
            const Text(
              'How are you today?',
              style: TextStyle(
                fontSize: 16,
                color: Colors.grey,
              ),
            ),
            const SizedBox(height: 24),

            // 快捷操作
            const Text(
              'Quick Actions',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                _buildActionCard('Messages', Icons.message, Colors.blue),
                _buildActionCard('Calendar', Icons.calendar_today, Colors.green),
                _buildActionCard('Tasks', Icons.task, Colors.orange),
                _buildActionCard('Notes', Icons.note, Colors.purple),
              ],
            ),
            const SizedBox(height: 32),

            // 最近活动
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text(
                  'Recent Activities',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                TextButton(
                  onPressed: () {},
                  child: const Text('See All'),
                ),
              ],
            ),
            const SizedBox(height: 16),
            Column(
              children: [
                _buildActivityCard(
                  'Meeting with Team',
                  'Today, 2:00 PM',
                  Icons.meeting_room,
                  Colors.blue,
                ),
                const SizedBox(height: 12),
                _buildActivityCard(
                  'Project Deadline',
                  'Tomorrow, 5:00 PM',
                  Icons.event_deadline,
                  Colors.red,
                ),
                const SizedBox(height: 12),
                _buildActivityCard(
                  'Lunch with Client',
                  'Friday, 12:30 PM',
                  Icons.restaurant,
                  Colors.green,
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildActionCard(String title, IconData icon, Color color) {
    return Column(
      children: [
        Container(
          width: 60,
          height: 60,
          decoration: BoxDecoration(
            color: color.withOpacity(0.1),
            borderRadius: BorderRadius.circular(12),
          ),
          child: Icon(icon, color: color),
        ),
        const SizedBox(height: 8),
        Text(
          title,
          style: const TextStyle(fontSize: 12),
        ),
      ],
    );
  }

  Widget _buildActivityCard(
      String title, String time, IconData icon, Color color) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            Container(
              width: 48,
              height: 48,
              decoration: BoxDecoration(
                color: color.withOpacity(0.1),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Icon(icon, color: color),
            ),
            const SizedBox(width: 16),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    title,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    time,
                    style: const TextStyle(
                      fontSize: 14,
                      color: Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
            const Icon(Icons.chevron_right),
          ],
        ),
      ),
    );
  }
}

步骤 3:运行应用

  1. 启动模拟器或连接真机
  2. 运行项目
  3. 观察首页布局效果

6.9 新手易错点

布局溢出

错误:子组件尺寸超过父组件尺寸,导致布局溢出。

解决方案

  • 使用 ExpandedFlexible 组件
  • 使用 SingleChildScrollView 避免垂直溢出
  • 使用 Wrap 组件避免水平溢出
  • 合理设置组件尺寸

对齐方式错误

错误:不理解 mainAxisAlignmentcrossAxisAlignment 的区别。

解决方案

  • 记住 Row 的主轴是水平方向,Column 的主轴是垂直方向
  • mainAxisAlignment 控制主轴方向的对齐
  • crossAxisAlignment 控制交叉轴方向的对齐

组件嵌套过多

错误:组件嵌套层次过深,导致代码难以维护。

解决方案

  • 将复杂布局拆分为多个子组件
  • 使用自定义组件封装重复的布局结构
  • 保持组件职责单一

尺寸计算错误

错误:硬编码尺寸,导致在不同屏幕尺寸上显示异常。

解决方案

  • 使用相对尺寸(如百分比)
  • 使用 MediaQuery 获取屏幕尺寸
  • 使用屏幕适配插件
  • 测试不同屏幕尺寸的显示效果

性能问题

错误:布局过于复杂,导致渲染性能下降。

解决方案

  • 避免不必要的嵌套
  • 使用 const 构造函数
  • 合理使用 RepaintBoundary 优化渲染
  • 避免在 build 方法中执行耗时操作

6.10 小结

本章介绍了 Flutter 的布局开发,包括布局核心原则、线性布局、弹性布局、流式布局、层叠布局、卡片布局以及布局适配等内容。这些布局组件是构建 Flutter 应用界面的基础,掌握它们的使用方法对于 Flutter 开发至关重要。

通过实操案例,我们使用这些布局组件搭建了一个简单的首页布局,体验了布局组件的组合和使用。在实际开发中,你可以根据需要组合这些布局组件,创建更加复杂和美观的用户界面。

在接下来的章节中,我们将学习 Flutter 的状态管理,掌握如何管理应用的状态,实现更复杂的交互逻辑。

© 2026 编程马·菜鸟教程 版权所有