原文地址:
https://codelabs.flutter-io.cn/codelabs/first-flutter-app-pt2-cn/index.html#0
https://codelabs.flutter-io.cn
1. 介绍
Flutter 是 Google 用以帮助开发者在 iOS 和 Android 两个平台开发高质量原生 UI 的移动 SDK。Flutter 兼容现有的代码,免费且开源,在全球开发者中广泛被使用。
在本 codelab 里,你将在一个基础的 Flutter 应用里加入交互功能,同时创建第二个页面(Flutter 里称之为 route)用户可以进入这个页面。最终,你将可以修改应用的主题(配色)。这是"创建你的第一个 Flutter 应用"第一篇的"续集",如果你希望从本篇开始也没问题,我们提供了初始化工程代码。
在第二部分,你可以了解到:
- Flutter 可以在 Android 和 iOS 系统里自动适应不同的 UI 体系;
- 使用热重载(hot reload)加速你的开发进程;
- 在 stateful widget 上添加交互;
- 导航到第二个页面;
- 修改应用的主题。
在第二部分,你将可以制作出:
完成一个简单的移动应用程序,功能是:为一个创业公司生成建议的名称。用户可以选择和取消选择的名称、保存(收藏)喜欢的名称。该代码一次生成十个名称,当用户滚动时,会生成一新批名称。用户可以点击导航栏右边的列表图标,以打开到仅列出收藏名称的新页面(route)。
下面这个 GIF 可以引导你预览本 codelab 做完之后的应用效果图:
2. 设定 Flutter 开发环境
你需要安装两部分来完成本次实验,Flutter 的 SDK 和编辑器(editor),这个 codelab 里,我们以 Android Studio 作为编辑器,但你可以用个人更顺手的编辑器。
提醒: 中国的开发者们,请先看一下这篇文档,查看是否需要对网络环境进行特别设置。
你可以通过如下任何设备完成本 codelab:
- 开启开发者模式(developer mode)的 Android 和 iOS 真机;
- iOS 模拟器;(需要安装 Xcode 等)
- Android 模拟器。(需要安装 Android Studio 等)
3. 创建初始化工程
如果你已经实践了第一部分:编写你的第一个 Flutter App [1/2],则可以直接跳过本篇,直接进行下一部分。
如果你尚未开始,别怕,请照着下面的步骤来一遍:
创建一个简单的、基于模板的 Flutter 工程,按照这个指南中所描述的步骤,然后将项目命名为 startup_namer(而不是 myapp),接下来你将会修改这个工程来完成最终的 App。
删除这个文件里的所有内容 lib/main.dart
,替换为这个文件的内容,这块代码实现了一个瀑布流似的文字列表,文字列表是一些初创公司的名字。
更新 pubspec.yaml
文件的内容,加入 English Words 这个 package:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.0
english_words: ^3.1.0 // 加入这一行...
这个 English Words package 的会自动生成一些随机的英文单词,我们这里假定这些是用于初创公司名字的。
在 Android Studio 的编辑器视图修改 pubspec 文件的时候,点击右上角的 Packages get,这将帮助你下载这些 package 到你的项目里,终端(console)里应该可以看到如下的内容:
flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0
运行你的工程,随意下滑查看这个列表,这里都是给你提供的初创公司名字喔!
4. 向列表里添加图标
在这部分,我们将为每一行添加一个心形的(收藏)图标,下一步你将能够为这个图标加入点击收藏的功能。
添加一个 _saved
Set(集合)到 RandomWordsState,这个集合存储用户喜欢(收藏)的单词对。 在这里,Set 比 List 更合适,因为 Set 中不允许重复的值。
class RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final Set<WordPair> _saved = new Set<WordPair>(); // 新增本行
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
...
}
在 _buildRow
方法中添加 alreadySaved
来检查确保单词对还没有添加到收藏夹中。
Widget _buildRow(WordPair pair) {
final bool alreadySaved = _saved.contains(pair); // 新增本行
...
}
同时在 _buildRow()
中, 添加一个心形 ❤️图标到 ListTiles以启用收藏功能。接下来,你就可以给心形 ❤️图标添加交互能力了。
向列表添加图标,如下所示:
Widget _buildRow(WordPair pair) {
final bool alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: new Icon( // 新增代码开始 ...
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
), // ... 新增代码结束
);
}
热重载应用,你现在可以在每一行看到心形 ❤️图标️,但它们还没有交互。
Android | iOS |
---|---|
遇到问题了?
如果您的应用没有正常运行,请查看下面链接处的代码,对比更正。
5. 添加交互
在这部分,我们将为刚刚的心形 ❤️图标增加交互,当用户点击列表中的条目,切换其"收藏"状态,并将该词对添加到或移除出"收藏夹"。
为了做到这个,我们在 _buildRow
中让心形 ❤️图标变得可以点击。如果单词条目已经添加到收藏夹中, 再次点击它将其从收藏夹中删除。当心形 ❤️图标被点击时,函数调用 setState()
通知框架状态已经改变。
增加 onTap
方法,如下所示:
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
onTap: () { // 增加如下 9 行代码...
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
}, // ... 一直到这里
);
}
提示: 在 Flutter 的响应式风格的框架中,调用 setState()
会为 State 对象触发 build()
方法,从而导致对 UI 的更新
热重载应用,你就可以点击任何一行测试收藏或取消收藏功能,你的点击同时自带 Material Design 里的水波动画特效。
Android | iOS |
---|---|
遇到问题了?
如果您的应用没有正常运行,请查看下面链接处的代码,对比更正。
6. 导航到新页面
在这一步中,您将添加一个显示收藏夹内容的新页面(在 Flutter 中称为路由[route])。您将学习如何在主路由和新路由之间导航(切换页面)。
在 Flutter 中,导航器管理应用程序的路由栈。将路由推入(push)到导航器的栈中,将会显示更新为该路由页面。 从导航器的栈中弹出(pop)路由,将显示返回到前一个路由。
接下来,我们在 RandomWordsState 的 build
方法中为 AppBar 添加一个列表图标。当用户点击列表图标时,包含收藏夹的新路由页面入栈显示。
将该图标及其相应的操作添加到 build
方法中:
class RandomWordsState extends State<RandomWords> {
...
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Startup Name Generator'),
actions: <Widget>[ // 新增代码开始 ...
new IconButton(icon: const Icon(Icons.list), onPressed: _pushSaved),
], // ... 代码新增结束
),
body: _buildSuggestions(),
);
}
...
}
提示: 某些 widget 属性需要单个 widget(child),而其它一些属性,如 action,需要一组widgets(children),用方括号 [] 表示。
在 RandomWordsState
这个类里添加 _pushSaved()
方法:
class RandomWordsState extends State<RandomWords> {
...
// 新增代码开始
void _pushSaved() {
}
// 新增代码结束
}
热重载应用,“列表图标”将会出现在导航栏中。现在点击它不会有任何反应,因为 _pushSaved
函数还是空的。
接下来,(当用户点击导航栏中的列表图标时)我们会建立一个路由并将其推入到导航管理器栈中。此操作会切换页面以显示新路由,新页面的内容会在 MaterialPageRoute 的 builder
属性中构建,builder
是一个匿名函数。
添加 Navigator.push
调用,这会使路由入栈(以后路由入栈均指推入到导航管理器的栈)
void _pushSaved() {
Navigator.of(context).push(
);
}
接下来,添加 MaterialPageRoute 及其 builder。 现在,添加生成 ListTile 行的代码,ListTile 的 divideTiles()
方法在每个 ListTile 之间添加 1 像素的分割线。 该 divided
变量持有最终的列表项,并通过 toList()
方法非常方便的转换成列表显示。
添加如下所示的代码:
void _pushSaved() {
Navigator.of(context).push(
new MaterialPageRoute<void>( // 新增如下20行代码 ...
builder: (BuildContext context) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
final List<Widget> divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
},
), // ... 新增代码结束
);
}
builder 返回一个 Scaffold,其中包含名为"Saved Suggestions"的新路由的应用栏。新路由的body 由包含 ListTiles 行的 ListView 组成;每行之间通过一个分隔线分隔。
添加水平分隔符,如下代码所示:
void _pushSaved() {
Navigator.of(context).push(
new MaterialPageRoute<void>(
builder: (BuildContext context) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return new ListTile(
title: new Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
final List<Widget> divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
return new Scaffold( // 新增 6 行代码开始 ...
appBar: new AppBar(
title: const Text('Saved Suggestions'),
),
body: new ListView(children: divided),
); // ... 新增代码段结束.
},
),
);
}
热重载应用程序,点击列表项收藏一些项,点击“列表图标”,在新的 route(路由)页面中显示收藏的内容。Navigator(导航器)会在应用栏中自动添加一个"返回"按钮,无需调用Navigator.pop
,点击后退按钮就会返回到主页路由。
遇到问题了?
如果您的应用没有正常运行,请查看下面链接处的代码,对比更正。
7. 使用 Themes 修改 UI
这一部分,我们将会一起修改应用的主题。Flutter 里我们使用 theme 来控制你应用的外观和风格,你可以使用默认主题,该主题取决于物理设备或模拟器,也可以自定义主题以适应您的品牌。
你可以通过配置 ThemeData 类轻松更改应用程序的主题,目前我们的应用程序使用默认主题,下面将更改 primaryColor 颜色为白色。
在 MyApp 这个类里修改颜色:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
theme: new ThemeData( // 新增代码开始...
primaryColor: Colors.white,
), // ... 代码新增结束
home: new RandomWords(),
);
}
}
热重载应用。 你会发现,整个背景将会变为白色,包括 app bar(应用栏)。
一个小练习,你可以看一下 ThemeData 的文档,添加其他属性来更多改变 UI 样式。Material library 中的 Colors 类提供了许多可以使用的颜色常量, 你可以使用热重载来快速简单地尝试、实验。
遇到问题了?
如果您的应用没有正常运行,请查看下面链接处的代码,对比更正。
8. 完成啦!
真棒,你完成了一个可运行在 Android 和 iOS 系统上的、包含交互的 Flutter 应用,在这个 codelab 里,你已经做了下面的事情:
写了 Dart 代码
使用热重载加速了开发进程
实现了一个 stateful widget,为你的应用加入了交互功能
创建了一个新的页面(route),为主页和这个新页面的跳转加入了逻辑
学会了如何使用 themes 修改应用的 UI
9. 扩展阅读
了解更多 Flutter SDK:
- 在 Flutter 中构建布局的教程(英文)
- 为你的应用添加交互的教程(英文)
- Flutter Widget 框架概述(英文)
- 给 Android 开发者的 Flutter 介绍(英文)
- Flutter for React Native Developers给React Native开发者的 Flutter 介绍(英文)
- 给 Web 开发者的 Flutter 介绍(英文)
资源清单:
- 使用 Flutter 构建精美移动应用(优达学城免费线上课程)
- Flutter Cookbook
- 从 Java 到 Dart 语言 codelab(英文)
- 了解 Dart 语言
请在我们的邮件列表与我们保持联系,我们期待听到你的反馈!
感谢
特别感谢来自 FlutterChina.club 的 Du Wen 提供的翻译作为基础。
感谢来自社区的葛佳恒、Lu Cheng 的翻译。