Flutter 中的布局
Flutter 布局机制的核心是 Widget。在 Flutter 中,几乎所有东西都是 Widget,甚至布局模型也是 Widget。您在 Flutter 应用中看到的图像、图标和文本都是 Widget。但您看不到的东西也是 Widget,例如排列、约束和对齐可见 Widget 的行、列和网格。
您可以通过组合 Widget 来构建更复杂的 Widget 以创建布局。例如,下面的第一个屏幕截图显示了 3 个图标,每个图标下方都有一个标签。
第二个屏幕截图显示了视觉布局,显示了 3 列一行,其中每列包含一个图标和一个标签。
以下是此 UI 的 Widget 树图
其中大部分应该如您所料,但您可能想知道容器(以粉红色显示)。Container
是一个 Widget 类,允许您自定义其子 Widget。当您想要添加填充、边距、边框或背景颜色时,可以使用Container
,仅举其一些功能。
在此示例中,每个Text
Widget 都放置在一个Container
中以添加边距。Row
的整体也放置在一个Container
中以在该行周围添加填充。
此示例中的其余 UI 由属性控制。使用其color
属性设置Icon
的颜色。使用Text.style
属性设置字体、颜色、粗细等。列和行具有允许您指定其子元素如何垂直或水平对齐以及子元素应占用多少空间的属性。
布局部件
#如何在 Flutter 中布局单个 Widget?本节将向您展示如何创建和显示简单的 Widget。它还显示了简单的 Hello World 应用的完整代码。
在 Flutter 中,只需几个步骤即可在屏幕上放置文本、图标或图像。
1. 选择布局部件
#根据您希望如何对齐或约束可见 Widget,从各种布局 Widget中进行选择,因为这些特性通常会传递给包含的 Widget。
此示例使用Center
,它将其内容水平和垂直居中。
2. 创建可见部件
#例如,创建一个Text
Widget。
Text('Hello World'),
创建一个Image
Widget。
return Image.asset(
image,
fit: BoxFit.cover,
);
创建一个Icon
Widget。
Icon(
Icons.star,
color: Colors.red[500],
),
3. 将可见部件添加到布局部件
#所有布局 Widget 都有以下两种情况之一
- 如果它们接受单个子 Widget,则为
child
属性,例如Center
或Container
。 - 如果它们接受 Widget 列表,则为
children
属性,例如Row
、Column
、ListView
或Stack
。
将Text
Widget 添加到Center
Widget 中。
const Center(
child: Text('Hello World'),
),
4. 将布局部件添加到页面
#Flutter 应用本身就是一个 Widget,大多数 Widget 都有一个build()
方法。在应用的build()
方法中实例化并返回一个 Widget 会显示该 Widget。
Material 应用
#对于Material
应用,您可以使用Scaffold
Widget;它提供默认横幅、背景颜色,并具有用于添加抽屉、SnackBar 和底部表单的 API。然后,您可以将Center
Widget 直接添加到主页的body
属性中。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
const String appTitle = 'Flutter layout demo';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: const Text(appTitle),
),
body: const Center(
child: Text('Hello World'),
),
),
);
}
}
Cupertino 应用
#要创建Cupertino
应用,请使用CupertinoApp
和CupertinoPageScaffold
Widget。
与Material
不同,它不提供默认横幅或背景颜色。您需要自己设置这些。
要设置默认颜色,请将配置好的
CupertinoThemeData
传递到应用的theme
属性。要向应用顶部添加 iOS 风格的导航栏,请将
CupertinoNavigationBar
Widget 添加到脚手架的navigationBar
属性。您可以使用CupertinoColors
提供的颜色来配置 Widget 以匹配 iOS 设计。要布局应用的主体,请使用所需的 Widget 作为其值的脚手架的
child
属性,例如Center
或Column
。
要了解您可以添加的其他 UI 组件,请查看Cupertino 库。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const CupertinoApp(
title: 'Flutter layout demo',
theme: CupertinoThemeData(
brightness: Brightness.light,
primaryColor: CupertinoColors.systemBlue,
),
home: CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
backgroundColor: CupertinoColors.systemGrey,
middle: Text('Flutter layout demo'),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Hello World'),
],
),
),
),
);
}
}
非 Material 应用
#对于非 Material 应用,您可以将Container
Widget 添加到应用的build()
方法中。
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(color: Colors.white),
child: const Center(
child: Text(
'Hello World',
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 32,
color: Colors.black87,
),
),
),
);
}
}
默认情况下,非 Material 应用不包含AppBar
、标题或背景颜色。如果要在非 Material 应用中使用这些功能,则必须自己构建它们。此应用将背景颜色更改为白色,并将文本更改为深灰色以模仿 Material 应用。
垂直和水平布局多个部件
#最常见的布局模式之一是垂直或水平排列 Widget。您可以使用Row
Widget 水平排列 Widget,使用Column
Widget 垂直排列 Widget。
要在 Flutter 中创建行或列,您可以将子 Widget 列表添加到Row
或Column
Widget 中。依次,每个子 Widget 本身可以是行或列,依此类推。以下示例显示了如何在行或列内部嵌套行或列。
此布局组织为Row
。该行包含两个子元素:左侧的列和右侧的图像。
左侧列的 Widget 树嵌套了行和列。
您将在嵌套行和列中实现 Pavlova 的一些布局代码。
对齐部件
#您可以使用mainAxisAlignment
和crossAxisAlignment
属性控制行或列如何对齐其子元素。对于行,主轴沿水平方向运行,交叉轴沿垂直方向运行。对于列,主轴沿垂直方向运行,交叉轴沿水平方向运行。
MainAxisAlignment
和CrossAxisAlignment
枚举提供了各种常量用于控制对齐方式。
在以下示例中,3 个图像中的每个图像宽度均为 100 像素。渲染框(在本例中为整个屏幕)的宽度超过 300 像素,因此将主轴对齐方式设置为spaceEvenly
会在每个图像之间、之前和之后平均分配剩余的水平空间。
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
应用源代码: row_column
列的工作方式与行相同。以下示例显示了一个包含 3 个图像的列,每个图像的高度为 100 像素。渲染盒(在本例中为整个屏幕)的高度超过 300 像素,因此将主轴对齐方式设置为 spaceEvenly
会将剩余的垂直空间平均分配到每个图像之间、上方和下方。
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Image.asset('images/pic1.jpg'),
Image.asset('images/pic2.jpg'),
Image.asset('images/pic3.jpg'),
],
);
应用源代码: row_column
部件大小
#当布局过大而无法适应设备时,受影响边缘会出现黄色和黑色条纹图案。这是一个 示例,显示了一个过宽的行。
可以使用 Expanded
组件将小部件的大小调整为适合行或列。要修复前面行图像过宽的示例,请使用 Expanded
组件包装每个图像。
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
child: Image.asset('images/pic2.jpg'),
),
Expanded(
child: Image.asset('images/pic3.jpg'),
),
],
);
应用源代码: sizing
也许您希望某个小部件占据与其同级小部件两倍的空间。为此,请使用 Expanded
组件的 flex
属性,这是一个确定小部件弹性因子的整数。默认弹性因子为 1。以下代码将中间图像的弹性因子设置为 2。
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Image.asset('images/pic1.jpg'),
),
Expanded(
flex: 2,
child: Image.asset('images/pic2.jpg'),
),
Expanded(
child: Image.asset('images/pic3.jpg'),
),
],
);
应用源代码: sizing
打包部件
#默认情况下,行或列沿其主轴占据尽可能多的空间,但如果您希望将子元素紧密地排列在一起,请将其 mainAxisSize
设置为 MainAxisSize.min
。以下示例使用此属性将星形图标紧密排列在一起。
Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
)
应用源代码: pavlova
嵌套行和列
#布局框架允许您根据需要将行和列嵌套在行和列内部。让我们看看以下布局中概述部分的代码。
概述部分实现为两行。评分行包含五个星形和评论数量。图标行包含三列图标和文本。
评分行的组件树
ratings
变量创建一个包含 5 个星形图标和文本的较小行的行。
final stars = Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
Icon(Icons.star, color: Colors.green[500]),
const Icon(Icons.star, color: Colors.black),
const Icon(Icons.star, color: Colors.black),
],
);
final ratings = Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
stars,
const Text(
'170 Reviews',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 20,
),
),
],
),
);
评分行下方的图标行包含 3 列;每列包含一个图标和两行文本,如其组件树所示。
iconList
变量定义了图标行。
const descTextStyle = TextStyle(
color: Colors.black,
fontWeight: FontWeight.w800,
fontFamily: 'Roboto',
letterSpacing: 0.5,
fontSize: 18,
height: 2,
);
// DefaultTextStyle.merge() allows you to create a default text
// style that is inherited by its child and all subsequent children.
final iconList = DefaultTextStyle.merge(
style: descTextStyle,
child: Container(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Icon(Icons.kitchen, color: Colors.green[500]),
const Text('PREP:'),
const Text('25 min'),
],
),
Column(
children: [
Icon(Icons.timer, color: Colors.green[500]),
const Text('COOK:'),
const Text('1 hr'),
],
),
Column(
children: [
Icon(Icons.restaurant, color: Colors.green[500]),
const Text('FEEDS:'),
const Text('4-6'),
],
),
],
),
),
);
leftColumn
变量包含评分和图标行,以及描述 Pavlova 的标题和文本。
final leftColumn = Container(
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
child: Column(
children: [
titleText,
subTitle,
ratings,
iconList,
],
),
);
左侧列放置在 SizedBox
中以限制其宽度。最后,UI 使用包含左侧列和图像的整个行构建,该行位于 Card
内部。
该 Pavlova 图片 来自 Pixabay。您可以使用 Image.network()
嵌入网络上的图像,但在此示例中,图像保存到项目中的 images 目录中,添加到 pubspec 文件 中,并使用 Images.asset()
访问。有关更多信息,请参阅 添加资源和图像。
body: Center(
child: Container(
margin: const EdgeInsets.fromLTRB(0, 40, 0, 30),
height: 600,
child: Card(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 440,
child: leftColumn,
),
mainImage,
],
),
),
),
),
应用源代码: pavlova
常见的布局部件
#Flutter 拥有丰富的布局组件库。以下是一些最常用的组件。我们的目的是让您尽快上手,而不是让您被完整的列表淹没。有关其他可用组件的信息,请参阅 组件目录,或使用 API 参考文档 中的搜索框。此外,API 文档中的组件页面通常会建议可能更适合您需求的类似组件。
以下组件分为两类:来自 组件库 的标准组件和来自 Material 库 的专用组件。任何应用都可以使用组件库,但只有 Material 应用才能使用 Material 组件库。
标准部件
#Container
:为小部件添加填充、边距、边框、背景颜色或其他装饰。GridView
:将小部件作为可滚动的网格布局。ListView
:将小部件作为可滚动的列表布局。Stack
:将一个小部件叠加在另一个小部件之上。
Material 部件
#容器
#许多布局大量使用 Container
通过填充分隔小部件,或添加边框或边距。您可以通过将整个布局放置在 Container
中并更改其背景颜色或图像来更改设备的背景。
摘要 (Container)
- 添加填充、边距、边框
- 更改背景颜色或图像
- 包含单个子组件,但该子组件可以是 Row、Column,甚至可以是组件树的根。
示例 (Container)
#此布局由一个包含两行的列组成,每行包含 2 个图像。一个 Container
用于将列的背景颜色更改为浅灰色。
Widget _buildImageColumn() {
return Container(
decoration: const BoxDecoration(
color: Colors.black26,
),
child: Column(
children: [
_buildImageRow(1),
_buildImageRow(3),
],
),
);
}
Container
还用于为每个图像添加圆角边框和边距。
Widget _buildDecoratedImage(int imageIndex) => Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(width: 10, color: Colors.black38),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
margin: const EdgeInsets.all(4),
child: Image.asset('images/pic$imageIndex.jpg'),
),
);
Widget _buildImageRow(int imageIndex) => Row(
children: [
_buildDecoratedImage(imageIndex),
_buildDecoratedImage(imageIndex + 1),
],
);
您可以在 教程 中找到更多 Container
示例。
应用源代码: container
网格视图
#使用 GridView
将小部件作为二维列表布局。GridView
提供了两个预制列表,或者您可以构建自己的自定义网格。当 GridView
检测到其内容过长而无法适应渲染盒时,它会自动滚动。
摘要 (GridView)
#- 将小部件以网格形式布局
- 检测列内容何时超出渲染盒并自动提供滚动功能
- 构建您自己的自定义网格,或使用提供的网格之一
GridView.count
允许您指定列数。GridView.extent
允许您指定图块的最大像素宽度。
示例 (GridView)
#使用 GridView.extent
创建一个图块最大宽度为 150 像素的网格。
应用源代码: grid_and_list
使用 GridView.count
创建一个在纵向模式下宽 2 个图块,在横向模式下宽 3 个图块的网格。标题是通过为每个 GridTile
设置 footer
属性创建的。
Dart 代码: grid_list_demo.dart
Widget _buildGrid() => GridView.extent(
maxCrossAxisExtent: 150,
padding: const EdgeInsets.all(4),
mainAxisSpacing: 4,
crossAxisSpacing: 4,
children: _buildGridTileList(30));
// The images are saved with names pic0.jpg, pic1.jpg...pic29.jpg.
// The List.generate() constructor allows an easy way to create
// a list when objects have a predictable naming pattern.
List<Container> _buildGridTileList(int count) => List.generate(
count, (i) => Container(child: Image.asset('images/pic$i.jpg')));
列表视图
#ListView
是一种类似列的组件,当其内容过长而无法适应其渲染盒时,会自动提供滚动功能。
摘要 (ListView)
#- 用于组织框列表的专用
Column
- 可以水平或垂直布局
- 检测其内容何时不适合并提供滚动功能
- 不如
Column
可配置,但更易于使用并支持滚动
示例 (ListView)
#使用 ListView
使用 ListTile
显示企业列表。Divider
将剧院与餐厅分隔开来。
应用源代码: grid_and_list
使用 ListView
显示特定颜色系列的 Colors
来自 Material 2 设计调色板。
Dart 代码: colors_demo.dart
Widget _buildList() {
return ListView(
children: [
_tile('CineArts at the Empire', '85 W Portal Ave', Icons.theaters),
_tile('The Castro Theater', '429 Castro St', Icons.theaters),
_tile('Alamo Drafthouse Cinema', '2550 Mission St', Icons.theaters),
_tile('Roxie Theater', '3117 16th St', Icons.theaters),
_tile('United Artists Stonestown Twin', '501 Buckingham Way',
Icons.theaters),
_tile('AMC Metreon 16', '135 4th St #3000', Icons.theaters),
const Divider(),
_tile('K\'s Kitchen', '757 Monterey Blvd', Icons.restaurant),
_tile('Emmy\'s Restaurant', '1923 Ocean Ave', Icons.restaurant),
_tile('Chaiya Thai Restaurant', '272 Claremont Blvd', Icons.restaurant),
_tile('La Ciccia', '291 30th St', Icons.restaurant),
],
);
}
ListTile _tile(String title, String subtitle, IconData icon) {
return ListTile(
title: Text(title,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 20,
)),
subtitle: Text(subtitle),
leading: Icon(
icon,
color: Colors.blue[500],
),
);
}
堆叠
#使用 Stack
将小部件排列在基本小部件(通常是图像)之上。小部件可以完全或部分地与基本小部件重叠。
摘要 (Stack)
#- 用于重叠另一个小部件的小部件
- 子组件列表中的第一个小部件是基本小部件;后续子组件叠加在该基本小部件之上。
Stack
的内容无法滚动。- 您可以选择裁剪超出渲染盒的子组件。
示例 (Stack)
#使用 Stack
将 Container
(在其半透明黑色背景上显示其 Text
)叠加在 CircleAvatar
之上。Stack
使用 alignment
属性和 Alignment
对文本进行偏移。
应用源代码: card_and_stack
使用 Stack
将图标叠加在图像之上。
Dart 代码: bottom_navigation_demo.dart
Widget _buildStack() {
return Stack(
alignment: const Alignment(0.6, 0.6),
children: [
const CircleAvatar(
backgroundImage: AssetImage('images/pic.jpg'),
radius: 100,
),
Container(
decoration: const BoxDecoration(
color: Colors.black45,
),
child: const Text(
'Mia B',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
);
}
卡片
#来自 Material 库 的 Card
包含相关的信息片段,并且可以由几乎任何小部件组成,但通常与 ListTile
一起使用。Card
只有一个子组件,但其子组件可以是列、行、列表、网格或其他支持多个子组件的组件。默认情况下,Card
将其大小缩小到 0 x 0 像素。您可以使用 SizedBox
限制卡片的大小。
在 Flutter 中,Card
具有略微圆角和阴影,使其具有 3D 效果。更改 Card
的 elevation
属性可以控制阴影效果。例如,将 elevation 设置为 24 会在视觉上将 Card
从表面抬高,并导致阴影变得更加分散。有关支持的 elevation 值列表,请参阅 Elevation 中的 Material 指南。指定不受支持的值将完全禁用阴影。
摘要 (Card)
#- 实现了一个 Material 卡片
- 用于呈现相关的信息片段
- 接受一个子元素,但该子元素可以是
Row
、Column
或其他包含子元素列表的 widget - 显示为圆角和阴影
Card
的内容不能滚动- 来自 Material 库
示例(卡片)
#一个包含 3 个 ListTiles 的 Card
,并使用 SizedBox
包裹以调整大小。一个 Divider
分隔了第一个和第二个 ListTiles
。
应用源代码: card_and_stack
一个包含图像和文本的 Card
。
Dart 代码: cards_demo.dart
Widget _buildCard() {
return SizedBox(
height: 210,
child: Card(
child: Column(
children: [
ListTile(
title: const Text(
'1625 Main Street',
style: TextStyle(fontWeight: FontWeight.w500),
),
subtitle: const Text('My City, CA 99984'),
leading: Icon(
Icons.restaurant_menu,
color: Colors.blue[500],
),
),
const Divider(),
ListTile(
title: const Text(
'(408) 555-1212',
style: TextStyle(fontWeight: FontWeight.w500),
),
leading: Icon(
Icons.contact_phone,
color: Colors.blue[500],
),
),
ListTile(
title: const Text('[email protected]'),
leading: Icon(
Icons.contact_mail,
color: Colors.blue[500],
),
),
],
),
),
);
}
列表项
#使用 ListTile
,一个来自 Material 库 的专门的行 widget,可以轻松创建包含最多 3 行文本以及可选的前导和尾随图标的行。ListTile
最常用于 Card
或 ListView
中,但也可以用在其他地方。
总结(ListTile)
#- 一个包含最多 3 行文本和可选图标的专用行
- 比
Row
配置选项少,但更易于使用 - 来自 Material 库
示例(ListTile)
#一个包含 3 个 ListTile
的 Card
。
应用源代码: card_and_stack
使用带有前导 widget 的 ListTile
。
Dart 代码: list_demo.dart
约束
#要完全理解 Flutter 的布局系统,您需要了解 Flutter 如何在布局中定位和调整组件的大小。有关更多信息,请参阅 理解约束。
视频
#以下视频是 Flutter in Focus 系列的一部分,解释了 Stateless
和 Stateful
widget。
每周 Widget 系列 的每一集都重点介绍一个 widget。其中一些包括布局 widget。
其他资源
#编写布局代码时,以下资源可能会有所帮助。
- 布局教程
- 了解如何构建布局。
- 部件目录
- 描述了 Flutter 中可用的许多 widget。
- Flutter 中的 HTML/CSS 等效项
- 对于熟悉 Web 编程的人员,此页面将 HTML/CSS 功能映射到 Flutter 功能。
- API 参考文档
- 所有 Flutter 库的参考文档。
- 添加资源和图像
- 说明如何将图像和其他资源添加到应用程序的包中。
- 使用 Flutter 从零到一
- 一个人编写他的第一个 Flutter 应用程序的体验。
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-11-25。 查看源代码 或 报告问题.