- 原文地址:MDC-102 Flutter: Material Structure and Layout (Flutter)
- 原文作者:codelabs.developers.google.com
- 译文出自:掘金翻译计划
- 本文永久链接:github.com/xitu/gold-m…
- 译者:DevMcryYu
- 校对者:Rickon
MDC-102 Flutter:Material 结构和布局(Flutter)
1. 介绍
Material Components(MDC)帮助开发者实现 Material Design。MDC 由谷歌团队的工程师和 UX 设计师创造,为 Android、iOS、Web 和 Flutter 提供很多美观实用的 UI 组件。在教程 MDC-101 中,你使用了两个 Material 组件:文本框和墨水波纹效果的按钮来构建一个登陆页面。现在让我们通过添加导航、结构和数据来拓展应用。
你将要构建
在本教程中,你将为 Shrine —— 一个销售服装和家居用品的电子商务应用程序构建一个主页面。它将含有:
- 一个位于顶部的应用栏
- 一个由产品填充的网格列表
这是四篇教程里的第二篇,它将引导你为 Shrine 的产品构建应用程序。我们建议你按照教程的顺序一步一步地编写你的代码。
相关的教程可以在以下位置找到:
- MDC-101: Material Components(MDC)基础
- MDC-103: Material Design Theming 的颜色、形状、高度和类型
- MDC-104: Material Design 高级组件。
到 MDC-104 的最后,你将会构建一个像这样的应用:
将要用到的 MDC 组件
- 顶部应用栏(Top app bar)
- 网格(Grid)
- 卡片(Card)
本教程中,你将使用 MDC-Flutter 提供的默认组件。你将会在 MDC-103: Material Design Theming 的颜色、形状、高度和类型中学习如何定制它们。
你将需要
- Flutter SDK
- 安装好 Flutter 插件的 Android Studio,或者你喜欢的代码编辑器
- 示例代码
要在 iOS 上构建和运行 Flutter 应用程序,你需要满足以下要求:
- 运行 macOS 的计算机
- Xcode 9 或更新版本
- iOS 模拟器,或者 iOS 物理设备
要在 Android 上构建和运行 Flutter 应用程序,你需要满足以下要求:
- 运行 macOS、Windows 或 Linux 的计算机
- Android Studio
- Android 模拟器(随 Android Studio 一起提供)或 Android 物理设备
2. 安装 Flutter 环境
前提条件
要开始使用 Flutter 开发移动应用程序,你需要:
- Flutter SDK
- 装有 Flutter 插件的 IntelliJ IDE,或者你喜欢的代码编辑器
Flutter 的 IDE 工具适用于 Android Studio、IntelliJ IDEA Community(免费)和 IntelliJ IDEA Ultimate。
要在 iOS 上构建和运行 Flutter 应用程序,你需要满足以下要求:- 运行 macOS 的计算机
- Xcode 9 或更新版本
- iOS 模拟器,或者 iOS 物理设备
- 运行 macOS、Windows 或者 Linux 的计算机
- Android Studio
- Android 模拟器(随 Android Studio 一起提供)或 Android 物理设备
重要提示:如果连接到计算机的 Android 手机上出现“允许 USB 调试”对话框,请启用始终允许从此计算机选项,然后单击确定。
在继续本教程之前,请确保你的 SDK 处于正确的状态。如果之前安装过 Flutter,则使用 flutter upgrade
来确保 SDK 处于最新版本。
flutter upgrade
复制代码
运行 flutter upgrade
将自动运行 flutter doctor
。如果这是首次安装 Flutter 且不需升级,那么请手动运行 flutter doctor
。查看显示的所有检查标记;这将会下载你需要的任何缺少的 SDK 文件,并确保你的计算机配置无误以进行 Flutter 的开发。
flutter doctor
复制代码
3. 下载教程初始应用程序
从 MDC-101 继续?
如果你完成了 MDC-101,那么本教程所需代码应该已经准备就绪,跳转到 添加应用栏 步骤。
从头开始?
下载初始应用程序
此入门程序位于 material-components-flutter-codelabs-102-starter_and_101-complete/mdc_100_series
目录中。
...或者从 GitHub 克隆它
要从 GitHub 克隆此项目,请运行以下命令:
git clone https://github.com/material-components/material-components-flutter-codelabs.git
cd material-components-flutter-codelabs
git checkout 102-starter_and_101-complete
复制代码
更多帮助:从 GitHub 上克隆存储库
正确的分支
教程 MDC-101 到 104 连续构建。所以当你完成 102 的代码后,它将变成 103 教程的初始代码!代码被分成不同的分支,你可以使用以下命令将它们全部列出:
git branch --list
要查看完整代码,请切换到
103-starter_and_102-complete
分支。
建立你的项目
以下步骤默认你使用的是 Android Studio (IntelliJ)。
创建项目
-
在终端中,导航到
material-components-flutter-codelabs
-
运行
flutter create mdc_100_series
打开项目
-
打开 Android Studio。
-
如果你看到欢迎页面,单击 打开已有的 Android Studio 项目。
- 导航到
material-components-flutter-codelabs/mdc_100_series
目录并单击打开,这将打开此项目。
在构建项目一次之前,你可以忽略在分析中见到的任何错误。
- 在左侧的项目面板中,删除测试文件
../test/widget_test.dart
- 如果出现提示,安装所有平台和插件更新或 FlutterRunConfigurationType,然后重新启动 Android Studio。
提示:确保你已安装 Flutter 和 Dart 插件。
运行初始程序
以下步骤默认你在 Android 模拟器或设备上进行测试。你也可以在 iOS 模拟器或设备上进行,只要你安装了 Xcode。
- 选择设备或模拟器
如果 Android 模拟器尚未运行,请选择 Tools -> Android -> AVD Manager 来创建您设备并启动模拟器。如果 AVD 已存在,你可以直接在 IntelliJ 的设备选择器中启动模拟器,如下一步所示。
(对于 iOS 模拟器,如果它尚未运行,通过选择 Flutter Device Selection -> Open iOS Simulator 来在你的开发设备上启动它。)
- 启动 Flutter 应用:
- 在你的编辑器窗口顶部寻找 Flutter Device Selection 下拉菜单,然后选择设备(例如,iPhone SE / Android SDK built for )。
- 点击运行图标( )。
如果你无法成功运行此应用程序,停下来解决你的开发环境问题。尝试导航到
material-components-flutter-codelabs
;如果你在终端中下载 .zip 文件,导航到material-components-flutter-codelabs-...
然后运行flutter create mdc_100_series
。
成功!Shrine 的初始登陆代码应该在你的模拟器中运行了。你可以看到 Shrine 的 logo 和它下面的名称 "Shrine"。
现在登录页面看起来不错,让我们用一些产品来填充应用。
4. 添加顶部应用栏
当登陆页面消失时主页面将出现并显示“你做到了!”。这很棒!但是我们的用户不知道能做什么操作,也不知道现在位于应用何处,为了解决这个问题,是时候添加导航了。
导航 是指允许用户在应用中移动的组件、交互、视觉提示和信息结构。它使得内容和功能更加注目,任务也因此易于完成。
在 Material 指南中了解更多有关导航的信息。
Material Design 提供确保高度可用性的导航模式,其中最注目的组件就是顶部应用栏。
你可以将顶部应用栏当作 iOS 中的“导航栏”,或者简单看成一个 “App Bar” 或 “Header”。
要提供导航并让用户快速访问其他操作,让我们添加一个顶部应用栏。
添加应用栏部件
在 home.dart
中,将应用栏添加到 Scaffold 中:
return Scaffold(
// TODO: 添加应用栏(102)
appBar: AppBar(
// TODO: 添加按钮和标题(102)
),
复制代码
将 AppBar 添加到 Scaffold 的 appBar:
字段位置,为了我们完美的布局,让应用栏保持在页面的顶部或底部。
Scaffold 在中是一个重要的部件。它为像抽屉、snack bar 和 bottom sheet 等各种常见 Material 组件提供方便的 API。它甚至可以帮助布置一个 Floating Action Button。
在 Flutter 文档中了解更多有关 Scaffold 的信息。
保存项目,当 Shrine 应用更新后,单击 Next 来查看主屏幕。
应用栏看起来不错,但它还需要一个标题。
如果应用没有更新,再次单击 “Play” 按钮,或者点击 “Play” 后的 “Stop”。
添加文本部件
在 home.dart
中,给应用栏添加一个标题:
// TODO: 添加应用栏(102)
appBar: AppBar(
// TODO: 添加按钮和标题(102)
title: Text('SHRINE'),
// TODO:添加后续按钮(102)
复制代码
保存项目。
到目前为止,你应该已经注意到我们所说的“平台差异”了。Material 明白 Android、iOS、Web 各平台都有差异。用户对他们有不同的期望。举例来说,在 iOS 里标题几乎总是居中的,这是 UIKit 提供的默认配置。在 Android 上标题是左对齐的。所以如果你使用的是 Android 模拟器或设备,那么标题应该位于左侧,对于 iOS 模拟器和设备而言,它应该是居中的。
了解更多信息,请查参阅有关跨平台适配的 Material 文章。
许多应用栏在标题旁边都设有按钮,让我们在应用中添加一个菜单图标。
添加位于首部的图标按钮
还是在 home.dart
中,在 AppBar 的 leading
字段设置一个图标按钮:(放在 title:
字段前,按照部件从首到尾的顺序):
return Scaffold(
appBar: AppBar(
// TODO: 添加按钮和标题(102)
leading: IconButton(
icon: Icon(
Icons.menu,
semanticLabel: 'menu',
),
onPressed: () {
print('Menu button');
},
),
复制代码
保存项目。
菜单图标(也被称作“汉堡包”)会在你期望的位置显示出来。
IconButton 类是在你的应用里引入 Material 图标的快捷方式。它有一个 Icon 部件。 Flutter 在 Icons 类里有整套的图标。它会根据字符串常量的映射自动导入图标。
在 Flutter 文档中了解更多有关 Icons 类的信息。有关 Icon 部件的信息请阅读这个 Flutter 文档。
你也可以在标题尾部添加按钮。在 Flutter 中,它们被称为 "action"。
Leading(首部) 和 trailing(尾部) 是表达方向的术语,指的是与语言无关的文本行的开头和结尾。当使用一个像英语这样的 LTR(左到右)语言时, leading 意味着 左侧 而 trailing 代表着 右侧。在像阿拉伯语这样的 RTL(右到左)语言时, leading 意味着 右侧 而 trailing 代表着 左侧。
了解 UI 镜像的更多信息,请参阅 双向性 Material Design 准则。
添加 action
还有两个 IconButton 的空间。
在 AppBar 实例中的标题后面添加它们:
// TODO: 添加尾部按钮(102)
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
semanticLabel: 'search',
),
onPressed: () {
print('Search button');
},
),
IconButton(
icon: Icon(
Icons.tune,
semanticLabel: 'filter',
),
onPressed: () {
print('Filter button');
},
),
],
复制代码
保存你的项目。你的主屏幕看起来应该像这样:
现在这个应用在左侧有一个按钮、一个标题,右侧还有两个 action。应用栏还利用阴影显示高度,表示它与内容处于不同的层级。
在 Icon 类中,SemanticLabel 字段是在 Flutter 中添加辅助功能信息的常用方法。这很像 Android 的 Content Label 或 iOS 的 UIAccessibility
accessibilityLabel
。你会在很多类中见到它。这个字段的信息很好地向使用屏幕阅读器的人说明了该按钮的作用。
对于没有
semanticLabel:
字段的部件,你可以将其包装在 Semantics 部件中,在其 Flutter 文档中了解更多有关的信息。
5. 在网格中添加卡片
现在我们的应用像点样子了,让我们接着放置一些卡片来组织内容。
卡片 是显示单体内容和动作的独立的元素。它们是一种可以灵活地呈现近似内容集合的方式。
在 Material 指南有关卡片的文章中了解更多信息。
要了解卡片部件,请参阅在 Flutter 中构建布局。
添加网格视图
让我们从应用栏底部添加一个卡片开始。单一的 卡片 部件不足以让我们将它放到我们想要的位置,所以我们需要将它封装在一个 网格视图 中。
用 GridView 替换 Scaffold 中 body 字段的 Center:
// TODO: 添加网格视图(102)
body: GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.all(16.0),
childAspectRatio: 8.0 / 9.0,
// TODO: 构建一组卡片(102)
children: <Widget>[Card()],
),
复制代码
让我们分析这段代码。网格视图调用 count()
构造函数,因要添加的项目数是可数的而不是无限的。但它需要更多信息来定义其布局。
crossAxisCount:
指定横向显示数目,我们设置成 2 行。
Flutter 中的 Cross axis(横轴) 表示非滚动轴。可滚动的方向称为 主轴。所以如果你的应用像网格视图默认的那样垂直滚动,那么横轴就是水平方向。
详情请参阅构建布局。
padding:
字段为网格视图的 4 条边设置填充。当然你现在看不到首尾的填充,因为网格视图内还没有其他子项。
childAspectRatio:
字段依据宽高比确定其大小。
默认地,网格视图中的项目尺寸相同。
将这些加在一起,网格视图按照如下方式计算每个子项的宽度:([整个网格宽度] - [左填充] - [右填充]) / 列数
。在这里就是:([整个网格宽度] - 16 - 16) / 2
。
高度是根据宽度计算得来的,通过应用宽高比:([整个网格宽度] - 16 - 16) / 2 * 9 / 8
。我们翻转了 8 和 9,因为我们是用宽度来计算高度。
我们已经有了一个空的卡片了,让我们添加一些子部件到卡片中。
布局内容
卡片内应该包含一张图片、一个标题和一个次级文本。
更新网格视图的子项:
// TODO: 构建一组卡片(102)
children: <Widget>[
Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 18.0 / 11.0,
child: Image.asset('assets/diamond.png'),
),
Padding(
padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Title'),
SizedBox(height: 8.0),
Text('Secondary Text'),
],
),
),
],
),
)
],
复制代码
这段代码添加了一个列部件,用来垂直地布局子部件。
crossAxisAlignment:
字段指定 CrossAxisAlignment.start
属性,这意味着“文本与前沿对齐”。
AspectRatio 部件决定图像的形状,无论提供的是何种图像。
Padding 使得文本与边框保持一定距离。
两个 Text 部件垂直堆叠,在其间保持 8 个单位的间隔(SizedBox)。我们使用另一个 Column 来把它们放到 Padding 中。
保存你的项目:
在这个预览里,你可以看到卡片从边缘置入,并带有圆角和阴影(这代表着卡片的高度)。整个形状在 Material 中被称为 “container(容器)”。(不要与名为 Container 的实际部件类混淆。)
除了容器以外,在 Material 中卡片内所有的元素实际上都是可选的。你可以添加标题文本、缩略图、头像或者小标题文本、分隔符甚至是按钮和图标。
了解更多消息,请参阅 Material 指南上有关卡片的文章。
卡片经常以集合的形式和其他卡片一起出现,让我们在网格视图中给它们布局。
6. 生成卡片集合
每当屏幕上出现多张卡片时,它们就会组成一个或多个集合。集合中的卡片是共面的,这意味着卡片共享相同的静止高度。(除了卡片被拾起或拖动,但在这里我们不会这么做。)
将卡片添加到集合
现在我们的卡片是网格视图内的 children:
字段子项。这有一大段难以阅读的嵌套代码。让我们将它提取到一个函数中来生成任意数量的空卡片,然后返回给我们。
// TODO: 生成卡片集合(102)
List<Card> _buildGridCards(int count) {
List<Card> cards = List.generate(
count,
(int index) => Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 18.0 / 11.0,
child: Image.asset('assets/diamond.png'),
),
Padding(
padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Title'),
SizedBox(height: 8.0),
Text('Secondary Text'),
],
),
),
],
),
),
);
return cards;
}
复制代码
将生成的卡片分配给网格视图的 children
字段。记得用新代码替换网格视图中的所有内容。
// TODO: 添加网格视图(102)
body: GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.all(16.0),
childAspectRatio: 8.0 / 9.0,
children: _buildGridCards(10) // 替换所有内容
),
复制代码
保存你的项目:
卡片已经在这了,但它们什么都没有显示。现在是时候添加一些产品数据了。
###添加产品数据
这个应用中的产品有着图像、名称和价格。让我们把这些添加到已有的卡片部件中。
然后,在 home.dart
中,导入数据模型需要的新包和文件:
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'model/products_repository.dart';
import 'model/product.dart';
复制代码
最后,更改 _buildGridCards()
来获取产品信息,并将数据应用到卡片中:
// TODO: 生成卡片集合(102)
// 替换整个方法
List<Card> _buildGridCards(BuildContext context) {
List<Product> products = ProductsRepository.loadProducts(Category.all);
if (products == null || products.isEmpty) {
return const <Card>[];
}
final ThemeData theme = Theme.of(context);
final NumberFormat formatter = NumberFormat.simpleCurrency(
locale: Localizations.localeOf(context).toString());
return products.map((product) {
return Card(
// TODO: 调整卡片高度(103)
child: Column(
// TODO: 卡片的内容设置居中(103)
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AspectRatio(
aspectRatio: 18 / 11,
child: Image.asset(
product.assetName,
package: product.assetPackage,
// TODO: 调整盒子尺寸(102)
),
),
Expanded(
child: Padding(
padding: EdgeInsets.fromLTRB(16.0, 12.0, 16.0, 8.0),
child: Column(
// TODO: 标签底部对齐并居中(103)
crossAxisAlignment: CrossAxisAlignment.start,
// TODO: 更改最内部的列(103)
children: <Widget>[
// TODO: 处理溢出的标签(103)
Text(
product.name,
style: theme.textTheme.title,
maxLines: 1,
),
SizedBox(height: 8.0),
Text(
formatter.format(product.price),
style: theme.textTheme.body2,
),
],
),
),
),
],
),
);
}).toList();
}
复制代码
注意:应用现在无法编译和运行,我们还需要进行修改。
要设置文本的样式,我们使用当前 BuildContext 中的 ThemeData。
了解有关文本样式的更多信息,请参阅 Material 指南中的排版一文。了解有关主题的更多信息,请参考教程下一章 MDC-103: Material Design Theming 的颜色、形状、高度和类型。
在尝试编译之前,将 BuildContext 传入 build()
方法中的 _buildGridCards()
:
// TODO: Add a grid view (102)
body: GridView.count(
crossAxisCount: 2,
padding: EdgeInsets.all(16.0),
childAspectRatio: 8.0 / 9.0,
children: _buildGridCards(context) // Changed code
),
复制代码
你可能注意到了我们没有在卡片间添加任何垂直的间隔,这是因为在其顶部与底部默认有 4 个单位的填充。
保存你的项目:
产品的数据显示出来了,但是图像四周有额外的空间。图像默认依据 .scaleDown
的 BoxFit 绘制(在这个情况下)。让我们将其更改为 .fitWidth
来让它们放大一点,删除多余的空间。
修改图像的 fit:
字段:
// TODO: 调整盒子尺寸(102)
fit: BoxFit.fitWidth,
复制代码
现在我们的产品完美的展现在应用中了!
7. 总结
我们的应用已经有了基本的流程,将用户从登陆屏幕带到可以查看产品的主屏幕。通过几行代码,我们添加了一个顶部应用栏(带有标题和三个按钮)以及卡片(用于显示我们应用的内容)。我们的主屏幕简洁实用,具有基本的结构和可操作的内容。
完成的 MDC-102 应用可以在
103-starter_and_102-complete
分支中找到。你可以用此分支下的应用来对照验证你的版本。
下一步
通过顶部应用栏、卡片、文本框和按钮,我们已经使用了 MDC-Flutter 库中的四个核心组件!你可以访问 Flutter 部件目录来探索更多组件。
虽然它完全正常运行,我们的应用尚未表达任何特殊的品牌特点。在 MDC-103: Material Design Theming 的颜色、形状、高度和类型中,我们将定制这些组件的样式,来诠释一个充满活力的、现代的品牌。
如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。