Flutter 布局实战:掌握 Row/Column/Flex 弹性布局

导语

在 Flutter 中,布局即代码 。掌握 RowColumnFlex 这三个基础弹性布局组件,足以应对 80% 以上的 UI 场景。它们不仅灵活高效,还能轻松实现跨平台响应式设计。本文通过两个典型实战案例(登录页 + 商品卡片),深入解析核心属性、使用技巧与常见陷阱,并附完整可运行代码!


一、核心布局组件解析

1. Row ------ 水平布局

  • 作用 :将子组件沿水平方向(主轴)排列。
  • 关键属性
    • mainAxisAlignment:主轴(水平)对齐方式(如 startcenterspaceBetween
    • crossAxisAlignment:交叉轴(垂直)对齐方式(如 startcenterstretch
    • children:子组件列表(必须为非空 List)

2. Column ------ 垂直布局

  • 作用 :将子组件沿垂直方向(主轴)排列。
  • 关键属性 :与 Row 完全一致,仅主轴方向不同(垂直为主轴,水平为交叉轴)。

3. Flex ------ 通用弹性容器

  • 作用RowColumn 的父类,通过 direction 属性动态切换布局方向。
  • 关键属性
    • direction: Axis.horizontal → 等效于 Row
    • direction: Axis.vertical → 等效于 Column

💡 小贴士 :日常开发中优先使用 Row / Column,仅在需要动态切换方向时才用 Flex


二、实战案例 1:登录页面布局

复制代码

dart

编辑

复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '登录页面布局',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const LoginPage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('用户登录')),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center, // 主轴居中(垂直方向)
          children: [
            // 用户名输入框
            TextField(
              decoration: InputDecoration(
                hintText: '请输入用户名',
                prefixIcon: const Icon(Icons.person),
                border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
              ),
            ),
            const SizedBox(height: 10), // 间距控制

            // 密码输入框
            TextField(
              obscureText: true,
              decoration: InputDecoration(
                hintText: '请输入密码',
                prefixIcon: const Icon(Icons.lock),
                border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
              ),
            ),
            const SizedBox(height: 20),

            // 登录按钮:占满宽度
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {},
                style: ElevatedButton.styleFrom(
                  padding: const EdgeInsets.symmetric(vertical: 15),
                  shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
                ),
                child: const Text('登录'),
              ),
            ),
            const SizedBox(height: 15),

            // 忘记密码 / 注册账号
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                TextButton(onPressed: () {}, child: const Text('忘记密码?')),
                TextButton(onPressed: () {}, child: const Text('注册账号')),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

优化说明

  • 使用 SizedBox(width: double.infinity) 替代 Flex + Expanded,更简洁直观
  • 所有间距统一用 SizedBox 控制,避免嵌套 Padding

三、实战案例 2:商品卡片布局

复制代码

dart

编辑

复制代码
Widget productCard() {
  return Container(
    margin: const EdgeInsets.all(10),
    padding: const EdgeInsets.all(10),
    decoration: BoxDecoration(
      border: Border.all(color: Colors.grey.shade200),
      borderRadius: BorderRadius.circular(8),
    ),
    child: Row(
      crossAxisAlignment: CrossAxisAlignment.start, // 图文顶部对齐
      children: [
        // 商品图片占位
        Container(
          width: 80,
          height: 80,
          color: Colors.grey.shade100,
          alignment: Alignment.center,
          child: const Icon(Icons.image, size: 40, color: Colors.grey),
        ),
        const SizedBox(width: 10),

        // 商品信息区域:自动撑满剩余空间
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 标题(最多两行,溢出省略)
              const Text(
                'Flutter实战教程:跨端开发从入门到精通',
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
              ),
              const SizedBox(height: 5),

              // 价格
              const Text('价格:¥99.00', style: TextStyle(color: Colors.red, fontSize: 15)),
              const SizedBox(height: 5),

              // 销量 + 按钮
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text('销量:1000+', style: TextStyle(color: Colors.grey, fontSize: 12)),
                  ElevatedButton(
                    onPressed: () {},
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(horizontal: 10),
                      minimumSize: const Size(0, 25),
                    ),
                    child: const Text('加入购物车', style: TextStyle(fontSize: 12)),
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

🔍 布局亮点

  • Expanded 确保文本区自适应剩余宽度,避免溢出
  • crossAxisAlignment: CrossAxisAlignment.start 实现图文顶部对齐
  • TextOverflow.ellipsis 防止长标题破坏布局

四、布局避坑指南(开发者必看!)

问题类型 常见表现 解决方案
溢出错误 RenderFlex children have non-zero flex but incoming width constraints are unbounded Row/Column 中使用 ExpandedFlexible 包裹可伸缩子项
对齐混乱 文字/图标未按预期对齐 明确区分主轴(main axis)与交叉轴(cross axis),根据布局方向选择对齐方式
间距失控 页面松散或拥挤 优先使用 SizedBox 控制固定间距,避免多层 Padding 嵌套
屏幕适配差 在小屏设备上内容被裁剪 结合 MediaQuery.of(context).size 动态计算尺寸,或使用 LayoutBuilder

🛠️ 进阶建议

  • 复杂布局可组合 Row + Column 嵌套(但不宜过深)
  • 对于等分布局,善用 Expanded(flex: n) 设置比例
  • 调试布局时开启 Flutter Inspector(DevTools)可视化查看约束

五、总结

组件 适用场景 使用频率
Row 水平排列(如按钮组、标签栏) ⭐⭐⭐⭐⭐
Column 垂直排列(如表单、列表项) ⭐⭐⭐⭐⭐
Flex 需动态切换方向的通用容器 ⭐⭐

🌟 记住一句话
"主轴决定排列方向,交叉轴决定对齐方式;用 Expanded 防溢出,用 SizedBox 控间距。"

掌握这三大组件,你已具备构建绝大多数 Flutter UI 的能力。下一步可学习 StackWrapListView 等进阶布局,迈向更复杂的界面设计!

相关推荐
哆啦A梦158841 分钟前
60 订单页选择收货地址
前端·javascript·vue.js·node.js
馬致远1 小时前
案例1- 跳动的心
javascript·css·css3
Hilaku1 小时前
利用 link rel="prefetch":如何让用户的页面秒开?
前端·javascript·性能优化
sunly_1 小时前
Flutter:实现多图上传选择的UI
flutter·ui
youyu-youyu1 小时前
h5 签名 vue
javascript·vue.js·ecmascript
Apifox1 小时前
如何通过抓包工具快速生成 Apifox 接口文档?
前端·后端·测试
没事多睡觉6661 小时前
JavaScript 中 this 指向教程
开发语言·前端·javascript
苏打水com1 小时前
浏览器与HTTP核心考点全解析(字节高频)
前端·http
用户99045017780091 小时前
ruoyi集成camunda-前端篇
前端