导语
在 Flutter 中,布局即代码 。掌握
Row、Column和Flex这三个基础弹性布局组件,足以应对 80% 以上的 UI 场景。它们不仅灵活高效,还能轻松实现跨平台响应式设计。本文通过两个典型实战案例(登录页 + 商品卡片),深入解析核心属性、使用技巧与常见陷阱,并附完整可运行代码!
一、核心布局组件解析
1. Row ------ 水平布局
- 作用 :将子组件沿水平方向(主轴)排列。
- 关键属性 :
mainAxisAlignment:主轴(水平)对齐方式(如start、center、spaceBetween)crossAxisAlignment:交叉轴(垂直)对齐方式(如start、center、stretch)children:子组件列表(必须为非空 List)
2. Column ------ 垂直布局
- 作用 :将子组件沿垂直方向(主轴)排列。
- 关键属性 :与
Row完全一致,仅主轴方向不同(垂直为主轴,水平为交叉轴)。
3. Flex ------ 通用弹性容器
- 作用 :
Row和Column的父类,通过direction属性动态切换布局方向。 - 关键属性 :
direction: Axis.horizontal→ 等效于Rowdirection: 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 中使用 Expanded 或 Flexible 包裹可伸缩子项 |
| 对齐混乱 | 文字/图标未按预期对齐 | 明确区分主轴(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 的能力。下一步可学习 Stack、Wrap、ListView 等进阶布局,迈向更复杂的界面设计!