Appearance
第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:运行应用
- 启动模拟器或连接真机
- 运行项目
- 观察首页布局效果
6.9 新手易错点
布局溢出
错误:子组件尺寸超过父组件尺寸,导致布局溢出。
解决方案:
- 使用
Expanded或Flexible组件 - 使用
SingleChildScrollView避免垂直溢出 - 使用
Wrap组件避免水平溢出 - 合理设置组件尺寸
对齐方式错误
错误:不理解 mainAxisAlignment 和 crossAxisAlignment 的区别。
解决方案:
- 记住 Row 的主轴是水平方向,Column 的主轴是垂直方向
mainAxisAlignment控制主轴方向的对齐crossAxisAlignment控制交叉轴方向的对齐
组件嵌套过多
错误:组件嵌套层次过深,导致代码难以维护。
解决方案:
- 将复杂布局拆分为多个子组件
- 使用自定义组件封装重复的布局结构
- 保持组件职责单一
尺寸计算错误
错误:硬编码尺寸,导致在不同屏幕尺寸上显示异常。
解决方案:
- 使用相对尺寸(如百分比)
- 使用
MediaQuery获取屏幕尺寸 - 使用屏幕适配插件
- 测试不同屏幕尺寸的显示效果
性能问题
错误:布局过于复杂,导致渲染性能下降。
解决方案:
- 避免不必要的嵌套
- 使用
const构造函数 - 合理使用
RepaintBoundary优化渲染 - 避免在
build方法中执行耗时操作
6.10 小结
本章介绍了 Flutter 的布局开发,包括布局核心原则、线性布局、弹性布局、流式布局、层叠布局、卡片布局以及布局适配等内容。这些布局组件是构建 Flutter 应用界面的基础,掌握它们的使用方法对于 Flutter 开发至关重要。
通过实操案例,我们使用这些布局组件搭建了一个简单的首页布局,体验了布局组件的组合和使用。在实际开发中,你可以根据需要组合这些布局组件,创建更加复杂和美观的用户界面。
在接下来的章节中,我们将学习 Flutter 的状态管理,掌握如何管理应用的状态,实现更复杂的交互逻辑。
