1. 简介
Flutter 是 Google 的界面工具包,可用于通过单一代码库为移动设备、网络和桌面设备制作本机编译的精美应用程序。Flutter 可与现有代码一起使用,已受到全球开发者和组织的青睐,是免费的开放源代码。
在此 Codelab 中,您将创建一个简单的移动端 Flutter 应用。如果您熟悉面向对象的代码和基本编程概念(如变量、循环和条件语句),则可以完成此 Codelab。不需要参考以往的 Dart、移动端或 Web 端的编程经验。
您将在第 1 部分学习的内容
- 如何编写像 iOS、Android 和 Web 原生应用一样的 Flutter 应用
- Flutter 应用的基本结构
- 找到并使用软件包来扩展功能
- 使用热重载缩短开发周期
- 如何实现有状态微件
- 如何创建延迟加载的无限列表
在此 Codelab 的 第 2 部分,您将添加交互性、修改应用的主题背景,并添加导航至新页面的功能(在 Flutter 中称为路由)。
您将在第 1 部分构建的应用
您将实现一个简单的应用,该应用可为初创公司生成建议名称。用户可以选择和取消选择名称,以便保存最适合的名称。代码会一次延迟生成 10 个名称。随着用户滚动屏幕,系统可生成更多名称。用户滚动屏幕的距离不受限制。
以下动画 GIF 显示的是应用在完成部分的工作方式:
您想要从本 Codelab 中学到什么?
2. 设置您的 Flutter 环境
您需要使用两个软件来完成此实验,即 Flutter SDK 和 编辑器。(此 Codelab 假定您使用 Android Studio,但您可以使用自己偏好的编辑器。)
您可以使用以下任意设备运行 Codelab:
- 已连接至您的计算机并设置为开发者模式的实体 Android 或 iOS 设备
- iOS 模拟器(需要安装 Xcode 工具)
- Android 模拟器(需要在 Android Studio 中设置)
- 浏览器(需要使用 Chrome 进行调试)
如果要编译在 Web 上运行的应用,则必须启用此功能(目前是测试版)。要启用 Web 支持,请使用以下说明:
$ flutter channel beta
$ flutter upgrade
$ flutter config --enable-web
只需要运行一次 config
命令。启用 Web 支持后,您还需为自己创建的每个 Flutter 应用针对 Web 进行编译。在 **devices(设备)**下拉菜单下的 IDE 中,或在命令行使用 flutter devices
后,您现在应该会看到列出的 Chrome 和 Web 服务器。Chrome 设备会自动启动 Chrome。Web 服务器会启动托管应用的服务器,以便您从任意浏览器进行加载。您可在开发期间使用 Chrome 设备,以便使用 DevTools,如果想在其他浏览器上进行测试,则可使用 Web 服务器。如需了解详细信息,请参阅 使用 Flutter 构建 Web 应用和 编写您的第一个 Flutter Web 应用。
3. 创建入门级 Flutter 应用
按 创建应用中的说明创建简单的模板化 Flutter 应用。输入 startup_namer
(而非 flutter_app
)作为项目名称。您需要修改此入门级应用,以创建完善的应用。
您通常要编辑 Dart 代码所在的 lib/main.dart
。
替换 lib/main.dart
的内容。 删除 lib/main.dart
中的所有代码,并将其替换为以下代码,此代码会在屏幕中央显示"Hello World"。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
),
body: const Center(
child: const Text('Hello World'),
),
),
);
}
}
运行应用。您应该会看到 Android、iOS 或 Web 输出,具体样式因您的设备而定。
Android | iOS |
观察
- 此示例创建了一个 Material 应用。Material 可视化设计语言是移动和 Web 端应用的标准语言。Flutter 提供了一组丰富的 Material 微件。
- main 方法使用箭头 (
=>
) 表示法。通过箭头表示法来表示一行函数或方法。 - 此应用扩展了
StatelessWidget
,使其本身成为一个微件。在 Flutter 中,几乎每个元素都是微件,其中包括对齐、边距和布局微件。 - Material 库中的
Scaffold
微件提供默认应用栏、标题和正文属性,用于存储主屏幕的微件树。微件子树很复杂。 - 微件的主要任务是,提供
build
方法,描述如何通过其他低级别微件显示此微件。 - 此示例的正文包含一个
Center
微件,其中包含Text
子微件。Center
微件可令其微件子树在屏幕上居中对齐。
4. 使用外部软件包
在此步骤中,您将开始使用名为 english_words
的开源软件包(其中包含数千个常用英语单词),以及某些实用函数。
您可以在 pub.dev 找到 english_words
软件包,以及许多其他开源软件包。
使用 pubspec 文件管理 Flutter 应用的资源。在 pubspec.yaml
中,将 english_words: ^3.1.5
(english_words
3.1.5 或更高版本)附加至依赖项列表:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
english_words: ^3.1.5 # add this line
在 Android Studio 的编辑器视图中查看 pubspec 时,点击 Packages get(获取软件包)。这样可将软件包提取至您的项目中。您应该能在控制台中看到以下内容:
flutter packages get
Running "flutter packages get" in startup_namer...
Process finished with exit code 0
执行"Pub get"也会自动生成"pubspec.lock"文件,其中包含提取至项目的所有软件包的列表及其版本号。
在 **lib/main.dar
**t 中,导入新软件包:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart'; // Add this line.
您键入内容时,Android Studio 会针对要导入的库提供建议。系统随后会将导入字符串显示为灰色,以表示此导入库尚未使用(目前)。
接着,您将使用 english_words
软件包生成文本,而不是使用"Hello World"。
完成以下更改:
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random(); // Add this line.
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
//child: Text('Hello World'), // Replace this text...
child: Text(wordPair.asPascalCase), // With this text.
),
),
);
}
}
如果应用正在运行,则可使用热重载 更新运行的应用。(您可以在命令行中输入 r
进行热重载。)每次在运行的应用中点击"hot reload"(热重载)或保存项目后,您都应该看到随机选择的不同词对。这是因为词对是在 build
方法内生成的,而此方法会在每次 MaterialApp
需要渲染时或在 Flutter Inspector 中切换平台时运行。
Android | iOS |
遇到了问题?
如果您的应用未正常运行,请检查是否有拼写错误。如果需要,请通过使用以下链接中的代码恢复正常状态。
5. 添加有状态微件
无状态微件不可变,这意味着其属性不会改变,所有值都是最终值。
有状态微件的状态在微件的生命周期期间可能会发生变化。实现有状态微件至少需要两个类:
StatefulWidget
,可用于创建State
类的实例。StatefulWidget
对象本身并不可变,并且可以舍弃和重新生成,而State
对象会在微件的生命周期中持续存在。
在此步骤中,您需要添加有状态微件 RandomWords
,以便创建其 State
类 _RandomWordsState
。随后您将在现有 MyApp
无状态微件内使用 RandomWords
作为子级。
为有状态微件创建样板代码。
此代码可在 MyApp
外部的文件中正常运行,而解决方案将其置于文件底层。在 lib/main.dart
中,将鼠标光标置于所有代码之后,输入几次 **Return(返回)**以从新行开始输入内容。在 IDE 中,开始键入 stful
。编辑器会询问您是否想创建 Stateful
微件。按 **Return(返回)**以接受。系统会显示两个类的样板代码,并会确定光标的位置,以便您输入无状态微件的名称。
输入 RandomWords
作为微件的名称。
如以下代码所示,除了创建其 State
类以外,RandomWords
微件没有执行其他操作。
在输入 RandomWords
作为有状态微件的名称后,IDE 会自动更新随附的 State
类,将其命名为 _RandomWordState
。默认情况下,我们会使用下划线作为 State
类的名称前缀。在 Dart 语言中,以下划线为标识符前缀可 强制实施隐私政策,这是命名 State
对象时推荐的最佳做法。
IDE 还会自动更新状态类以扩展 State<RandomWords>
,这表明您正在使用专用于 RandomWords
的通用 State
类。应用的大部分逻辑都驻留在此,该类将维护 RandomWords
微件的状态。该类可用于保存生成词对的列表,此列表会随着用户滚动屏幕无限增长,而且在本实验的https://developers.google.cn/codelabs/first-flutter-app-pt2-cn,此列表会随着用户使用收藏夹收藏词对(通过切换心形图标在列表中添加或移除词对)而无限增长。
现在,这两个类如下所示:
class RandomWords extends StatefulWidget {
@override
_RandomWordsState createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
@override
Widget build(BuildContext context) {
return Container();
}
}
在 _RandomWordsState
中更新 build()
方法。
将 return Container();
替换为以下两行内容:
class _RandomWordsState extends State<RandomWords> {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random(); // NEW
return Text(wordPair.asPascalCase); // NEW
}
}
通过完成以下更改,从 MyApp
中移除词语生成代码:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random(); // DELETE
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
body: Center(
//child: Text(wordPair.asPascalCase), // REPLACE with...
child: RandomWords(), // ...this line
),
),
);
}
}
热重载该应用。应用的表现应该与以前一样,在每次热重载或保存应用后显示词对。
遇到了问题?
如果您的应用无法正常运行,则可通过使用以下链接中的代码恢复正常状态。
6. 创建无限滚动 ListView
在此步骤中,您将扩展 _RandomWordsState
以生成并显示词对列表。随着用户滚动屏幕,此列表(会显示在 ListView
微件中)会无限增长。ListView
中包含 builder
工厂构造函数,您可使用此函数按需延迟构建列表视图。
在 _RandomWordState
类中添加一些状态变量。
添加 _suggestions
列表,以便保存建议的词对。此外,还要添加 _biggerFont
变量,以便增大字体大小。
class _RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[]; // NEW
final TextStyle _biggerFont = const TextStyle(fontSize: 18); // NEW
...
}
接着,您需在 _RandomWordsState
类中添加 _buildSuggestions()
函数。此方法可构建显示建议词对的 ListView
。
ListView
类可提供构建器属性 itemBuilder
,该属性是工厂构建器和以匿名函数形式指定的回调函数。将两个参数传递到函数 BuildContext
和行迭代器 i
中。迭代器从 0 开始,每次调用函数时,其都会针对每个建议词对增加一次计数。使用此方法,建议列表会随着用户滚动屏幕持续增长。
添加完整的 _buildSuggestions
函数。
在 _RandomWordsState
类中,根据需要添加以下函数,从而删除注释:
Widget _buildSuggestions() {
return ListView.builder(
padding: const EdgeInsets.all(16),
// The itemBuilder callback is called once per suggested
// word pairing, and places each suggestion into a ListTile
// row. For even rows, the function adds a ListTile row for
// the word pairing. For odd rows, the function adds a
// Divider widget to visually separate the entries. Note that
// the divider may be difficult to see on smaller devices.
itemBuilder: (BuildContext _context, int i) {
// Add a one-pixel-high divider widget before each row
// in the ListView.
if (i.isOdd) {
return Divider();
}
// The syntax "i ~/ 2" divides i by 2 and returns an
// integer result.
// For example: 1, 2, 3, 4, 5 becomes 0, 1, 1, 2, 2.
// This calculates the actual number of word pairings
// in the ListView,minus the divider widgets.
final int index = i ~/ 2;
// If you've reached the end of the available word
// pairings...
if (index >= _suggestions.length) {
// ...then generate 10 more and add them to the
// suggestions list.
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
}
);
}
_buildSuggestions
函数会为每个词对调用一次 _buildRow
。该函数会在 ListTile
中显示每个新词对,您可在 第 2 部分用其改善各行的外观。
在 _RandomWordsState
中添加 _buildRow
函数:
Widget _buildRow(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
为 _RandomWordsState.
更新 build
方法
更改方法,以使用 _buildSuggestions()
,而不是直接调用词语生成库。( Scaffold
可实现基本的 Material Design 可视化布局。)
@override
Widget build(BuildContext context) {
//final wordPair = WordPair.random(); // Delete these...
//return Text(wordPair.asPascalCase); // ... two lines.
return Scaffold ( // Add from here...
appBar: AppBar(
title: Text('Startup Name Generator'),
),
body: _buildSuggestions(),
); // ... to here.
}
为 MyApp
更新 build
方法,从而更改标题、移除 AppBar
并将主屏幕属性更改为 RandomWords
微件。
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
home: RandomWords(),
);
}
重启应用。无论您在屏幕上滚动多远的距离,您都应该会看到词对列表。
Android | iOS |
遇到了问题?
如果您的应用无法正常运行,则可通过使用以下链接中的代码恢复正常状态。
7. 后续步骤
恭喜!
您已完成此 Codelab 的第 1 部分!如果想扩展此应用,请继续完成 第 2 部分,您可在其中修改应用,如下所示:
- 添加交互性。
- 添加导航至新路由的功能。
- 修改主题颜色。
在完成第 2 部分的操作时,应用如下所示:
其他后续步骤
通过以下资源详细了解 Flutter SDK:
- Flutter 中的布局
- "添加交互性"教程
- 微件简介
- 面向 Android 开发者的 Flutter
- 面向 React Native 开发者的 Flutter
- 面向网页开发者的 Flutter
- Flutter YouTube 频道
其他资源包括以下内容:
此外,您还可 联系 Flutter 社区!