Flutter 布局系统
布局是构建用户界面的基础。Flutter 提供了丰富的布局 Widget,让你可以轻松创建各种复杂的界面。
布局基础
盒模型
Flutter 的布局基于盒模型概念,每个 Widget 都是一个矩形盒子:
布局约束
Flutter 使用"约束向下,尺寸向上"的布局机制:
- 父 Widget 向下传递约束:告诉子 Widget 可用的空间范围
- 子 Widget 向上传递尺寸:告诉父 Widget 自己需要多大空间
- 父 Widget 定位子 Widget:根据子 Widget 的尺寸进行布局
单子布局 Widget
Container
Container 是最常用的布局 Widget,可以设置尺寸、边距、装饰等:
Container(
width: 200,
height: 200,
margin: EdgeInsets.all(8), // 外边距
padding: EdgeInsets.all(16), // 内边距
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.black, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(0, 4),
),
],
),
child: Text('Container 内容'),
)
Padding
为子 Widget 添加内边距:
Padding(
padding: EdgeInsets.all(16), // 四边相同
child: Text('带内边距的文本'),
)
// 不同方向的内边距
Padding(
padding: EdgeInsets.only(left: 16, top: 8),
child: Text('只设置左边和上边'),
)
// 对称内边距
Padding(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Text('水平16,垂直8'),
)
Center
将子 Widget 居中:
Center(
child: Text('居中文本'),
)
// 限制尺寸
Center(
widthFactor: 2, // 宽度是子 Widget 的 2 倍
heightFactor: 2, // 高度是子 Widget 的 2 倍
child: Text('居中'),
)
Align
对齐子 Widget:
Align(
alignment: Alignment.center, // 居中
child: Text('居中'),
)
Align(
alignment: Alignment.topLeft, // 左上角
child: Text('左上角'),
)
Align(
alignment: Alignment.bottomRight, // 右下角
child: Text('右下角'),
)
// 自定义对齐位置(-1 到 1)
Align(
alignment: Alignment(0.5, -0.5), // 右上方偏移
child: Text('自定义位置'),
)
SizedBox
设置固定尺寸:
SizedBox(
width: 100,
height: 50,
child: ElevatedButton(
onPressed: () {},
child: Text('固定大小按钮'),
),
)
// 强制子 Widget 填充
SizedBox.expand(
child: Text('填充整个空间'),
)
// 设置间距
SizedBox(height: 16), // 垂直间距
SizedBox(width: 8), // 水平间距
AspectRatio
保持宽高比:
AspectRatio(
aspectRatio: 16 / 9, // 16:9 宽高比
child: Container(
color: Colors.blue,
),
)
ConstrainedBox
添加约束:
ConstrainedBox(
constraints: BoxConstraints(
minWidth: 100,
maxWidth: 200,
minHeight: 50,
maxHeight: 100,
),
child: Container(
color: Colors.blue,
child: Text('约束范围内的容器'),
),
)
多子布局 Widget
Row
水平排列子 Widget:
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 主轴对齐
crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴对齐
children: [
Container(color: Colors.red, width: 50, height: 50),
Container(color: Colors.green, width: 50, height: 50),
Container(color: Colors.blue, width: 50, height: 50),
],
)
主轴对齐方式(MainAxisAlignment):
start:从起始位置开始end:从结束位置开始center:居中spaceBetween:两端对齐,间距平均分配spaceAround:每个子元素两侧间距相等spaceEvenly:所有间距相等
交叉轴对齐方式(CrossAxisAlignment):
start:顶部/左侧对齐end:底部/右侧对齐center:居中stretch:拉伸填充baseline:基线对齐(需要设置textBaseline)
Column
垂直排列子 Widget:
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('第一行'),
SizedBox(height: 8),
Text('第二行'),
SizedBox(height: 8),
ElevatedButton(
onPressed: () {},
child: Text('按钮'),
),
],
)
Expanded 和 Flexible
控制子 Widget 如何填充可用空间:
Row(
children: [
Expanded(
flex: 2, // 占 2/3 空间
child: Container(color: Colors.red, height: 50),
),
Expanded(
flex: 1, // 占 1/3 空间
child: Container(color: Colors.blue, height: 50),
),
],
)
// Flexible 允许子 Widget 不完全填充
Row(
children: [
Flexible(
fit: FlexFit.tight, // 强制填充(与 Expanded 相同)
child: Container(color: Colors.red, height: 50),
),
Flexible(
fit: FlexFit.loose, // 允许不填充
child: Container(color: Colors.blue, height: 50, width: 50),
),
],
)
Stack
层叠布局,子 Widget 可以重叠:
Stack(
children: [
Container(
width: 200,
height: 200,
color: Colors.blue,
),
Positioned(
top: 20,
left: 20,
child: Container(
width: 50,
height: 50,
color: Colors.red,
),
),
Positioned(
bottom: 20,
right: 20,
child: Text(
'叠放文本',
style: TextStyle(color: Colors.white),
),
),
],
)
Wrap
自动换行布局:
Wrap(
spacing: 8, // 水平间距
runSpacing: 8, // 垂直间距
alignment: WrapAlignment.start,
children: [
Chip(label: Text('Flutter')),
Chip(label: Text('Dart')),
Chip(label: Text('Android')),
Chip(label: Text('iOS')),
Chip(label: Text('Web')),
Chip(label: Text('Desktop')),
],
)
列表布局
ListView
滚动列表:
// 简单列表
ListView(
padding: EdgeInsets.all(16),
children: [
ListTile(
leading: Icon(Icons.person),
title: Text('用户信息'),
subtitle: Text('查看和编辑个人信息'),
trailing: Icon(Icons.chevron_right),
onTap: () {
print('点击用户信息');
},
),
Divider(),
ListTile(
leading: Icon(Icons.settings),
title: Text('设置'),
trailing: Icon(Icons.chevron_right),
),
],
)
// 构建器模式(懒加载,适合长列表)
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(
title: Text('项目 $index'),
leading: CircleAvatar(
child: Text('${index + 1}'),
),
);
},
)
// 带分隔线的列表
ListView.separated(
itemCount: 100,
separatorBuilder: (context, index) => Divider(height: 1),
itemBuilder: (context, index) {
return ListTile(title: Text('项目 $index'));
},
)
GridView
网格布局:
// 固定列数的网格
GridView.count(
crossAxisCount: 3,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
padding: EdgeInsets.all(8),
children: List.generate(9, (index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('$index')),
);
}),
)
// 构建器模式
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 8,
crossAxisSpacing: 8,
childAspectRatio: 1.5, // 宽高比
),
itemCount: 20,
itemBuilder: (context, index) {
return Card(
child: Center(child: Text('项目 $index')),
);
},
)
// 最大宽度的网格
GridView.builder(
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200, // 最大宽度
mainAxisSpacing: 8,
crossAxisSpacing: 8,
),
itemCount: 20,
itemBuilder: (context, index) {
return Container(
color: Colors.blue[100 * (index % 9)],
child: Center(child: Text('$index')),
);
},
)
自定义布局
CustomScrollView 和 Slivers
Sliver 是可以滚动区域的一部分,CustomScrollView 可以组合多个 Sliver:
CustomScrollView(
slivers: [
// AppBar
SliverAppBar(
expandedHeight: 200,
floating: false,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('标题'),
background: Image.network(
'https://picsum.photos/400/200',
fit: BoxFit.cover,
),
),
),
// 网格
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 4,
crossAxisSpacing: 4,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('$index')),
);
},
childCount: 18,
),
),
// 列表
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
title: Text('列表项 $index'),
);
},
childCount: 50,
),
),
// 固定高度的项目
SliverFixedExtentList(
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(context, index) {
return Container(
color: index.isEven ? Colors.white : Colors.grey[100],
child: ListTile(title: Text('项目 $index')),
);
},
childCount: 30,
),
),
],
)
布局技巧
响应式布局
使用 MediaQuery 和 LayoutBuilder 创建响应式布局:
// 使用 MediaQuery
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isTablet = screenWidth > 600;
return Container(
padding: EdgeInsets.all(isTablet ? 32 : 16),
child: isTablet ? buildTabletLayout() : buildPhoneLayout();
);
}
// 使用 LayoutBuilder
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
return Row(
children: [
Expanded(child: buildSidebar()),
Expanded(flex: 3, child: buildContent()),
],
);
} else {
return Column(
children: [
buildSidebar(),
Expanded(child: buildContent()),
],
);
}
},
)
布局性能优化
// 1. 使用 const 构造函数
const Text('不变的文本', style: TextStyle(fontSize: 16));
// 2. 使用 ListView.builder 而不是 ListView
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
)
// 3. 提取 Widget 以减少重建
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
_buildHeader(),
_buildContent(),
_buildFooter(),
],
);
}
Widget _buildHeader() => Text('标题');
Widget _buildContent() => Expanded(child: Text('内容'));
Widget _buildFooter() => Text('页脚');
}
// 4. 使用 Key 来保持状态
ListView.builder(
key: PageStorageKey('my_list'), // 保持滚动位置
itemBuilder: (context, index) => ItemWidget(key: ValueKey(items[index].id)),
)
常见布局模式
卡片布局
Card(
elevation: 4,
margin: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
'https://picsum.photos/400/200',
fit: BoxFit.cover,
width: double.infinity,
height: 150,
),
Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'卡片标题',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text('卡片描述内容,可以包含多行文本。'),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(onPressed: () {}, child: Text('取消')),
SizedBox(width: 8),
ElevatedButton(onPressed: () {}, child: Text('确定')),
],
),
],
),
),
],
),
)
列表项布局
ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage('https://picsum.photos/100'),
),
title: Text('用户名'),
subtitle: Text('这是一条消息的预览...'),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('10:30', style: TextStyle(fontSize: 12, color: Colors.grey)),
SizedBox(height: 4),
CircleAvatar(
radius: 10,
backgroundColor: Colors.red,
child: Text('3', style: TextStyle(fontSize: 12, color: Colors.white)),
),
],
),
onTap: () {
print('点击列表项');
},
)
小结
本章我们学习了:
- 布局基础:盒模型、约束机制
- 单子布局:Container、Padding、Center、Align、SizedBox
- 多子布局:Row、Column、Stack、Wrap
- 列表布局:ListView、GridView
- 自定义布局:CustomScrollView、Slivers
- 响应式布局:MediaQuery、LayoutBuilder
- 性能优化:const、builder、Key
练习
- 创建一个响应式的网格布局,手机上显示 2 列,平板上显示 4 列
- 实现一个带有浮动按钮和 SliverAppBar 的可滚动页面
- 创建一个聊天消息列表,支持不同的消息样式
- 实现一个瀑布流布局的图片画廊
下一步
下一章我们将学习 状态管理,掌握如何在 Flutter 中管理应用状态。