编写第一个 Flutter 应用
小提示
这篇 codelab 将带你初体验移动端 Flutter 应用开发。你也许更想尝试编写你的第一个 Flutter 网页应用。 请注意,如果你开启 web,完整的应用程序仅可在这些设备上使用!
这是一个指引你完成第一个 Flutter 应用的手把手操作教程(我们也称之为是 codelab)。我们将会着手创建一个简单的 Flutter 应用,无需 Dart 语言、移动开发语言或 Web 开发经验,只需你具备面向对象语言开发基础即可(如变量,循环和条件语句)。
完整的教程分为两部分,本页面是第一部分的内容,你可以在这里查看 第二部分 的内容。
第一部分的内容概览
你将完成一个简单的移动应用程序,功能是:为一个创业公司生成建议的公司名称。用户可以选择和取消选择的名称、保存喜欢的名称。该代码一次生成十个名称,当用户滚动时,会生成一新批名称。
页面上方的这个 GIF 可以引导你预览本 codelab 做完之后的应用效果图。
第一部分,我们将共同构建:
Flutter 如何在 Android、iOS 和 Web 里自动适应不同的 UI 体系
Flutter 工程/项目的基本结构
查找和使用 packages 来扩展功能
使用热重载 (hot reload) 加快开发周期
如何实现有状态的 widget
如何创建一个无限的、延迟加载的列表
在本 codelab 的 第二部分,你还将学到添加交互,修改应用的主题,以及为应用添加一个新的页面(在 Flutter,我们称之为 route)。
所需工具
你需要安装两部分内容来完成本次实验:Flutter SDK 安装 和 编辑器 (editor) 设置。本 codelab 里,我们以 macOS 环境下的 Android Studio 以做演示,但你可以选用更顺手的配置。
你可以通过如下任何设备完成本 codelab:
浏览器(目前最好可以是 Chrome 浏览器)
第一步:创建初始化工程
按照 这个指南 中所描述的步骤,创建一个简单的、基于模板的 Flutter 工程,然后将项目命名为 startup_namer (而不是 myapp),接下来你将会修改这个工程来完成最终的 App。
小提示
如果你没有在你的集成开发环境(IDE)里看到”New Flutter Project”(新建一个 Flutter 工程),请按照本文档完成 Dart 和 Flutter 插件的安装。
在这个示例中,你将主要编辑 Dart 代码所在的 lib/main.dart 文件,
- 替换 lib/main.dart 删除 lib/main.dart 中的所有代码,然后替换为下面的代码,它将在屏幕的中心显示”Hello World”。
lib/main.dart
- // Copyright 2018 The Flutter team. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- 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: Text('Welcome to Flutter'),
- ),
- body: Center(
- child: Text('Hello World'),
- ),
- ),
- );
- }
- }
小提示
在向你的工程项目中粘贴代码的时候,缩进可能会变形。你可以使用 Flutter 工具自动修复此问题:
Android Studio / IntelliJ IDEA: 右键单击Dart代码,然后选择 Reformat Code with dartfmt
VS Code: 右键单击并选择 Format Document.、
Terminal: 运行
flutter format <filename>
- 运行 你的工程项目,根据不同的操作系统,你会看到如下运行结果界面:
Android
iOS
小提示
第一次真机运行的时候可能会需要更多的等待时间,但是这是值得的,因为接下来你就可以使用热重载(hot reload)功能,真正的为下次运行时更新的预览节省更多时间。
观察和分析
本示例创建了一个具有 Material Design 风格的应用,Material 是一种移动端和网页端通用的视觉设计语言,Flutter 提供了丰富的 Material 风格的 widgets。
主函数(main)使用了 (
=>
) 符号,这是 Dart 中单行函数或方法的简写。该应用程序继承了
StatelessWidget
,这将会使应用本身也成为一个 widget。在 Flutter 中,几乎所有都是 widget,包括对齐 (alignment)、填充 (padding) 和布局 (layout)。Scaffold
是 Material 库中提供的一个 widget,它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性。widget 树可以很复杂。一个 widget 的主要工作是提供一个
build()
方法来描述如何根据其他较低级别的 widgets 来显示自己。本示例中的 body 的 widget 树中包含了一个
Center
widget,Center widget 又包含一个Text
子 widget,Center widget 可以将其子 widget 树对齐到屏幕中心。
第二步:使用外部 package
在这一步中,你将开始使用一个名为 english_words的开源软件包,其中包含数千个最常用的英文单词以及一些实用功能。
你可以在 pub.dev 上找到english_words
软件包以及其他许多开源软件包。
- pubspec 文件管理 Flutter 应用程序的 assets(资源,如图片、package等)。在pubspec.yaml 中,将 english_words(3.1.0或更高版本)添加到依赖项列表,如下面高亮显示的行:
{step1_base → step2_use_package}/pubspec.yaml
@@ -5,4 +5,5 @@
5
5
dependencies:
6
6
flutter:
7
7
sdk: flutter
8
8
cupertino_icons: ^0.1.2
9
- english_words: ^3.1.0
- 在 Android Studio 的编辑器视图中查看 pubspec 时,单击右上角的 Packages get,这会将依赖包安装到你的项目。你可以在控制台中看到以下内容:
$ flutter pub get
Running "flutter pub get" in startup_namer...
Process finished with exit code 0
在执行 Packages get
命令的时候,同时会自动生成一个名为 pubspec.lock
的文件,这里包含了你依赖 packages 的名称和版本。
- 在
lib/main.dart
中引入,如下所示:
lib/main.dart
- import 'package:flutter/material.dart';
- import 'package:english_words/english_words.dart';
在你输入时,Android Studio会为你提供有关库导入的建议。然后它将呈现灰色的导入字符串,让你知道导入的库截至目前尚未被使用。
- 接下来,我们使用 English words 包生成文本来替换字符串”Hello World”:
{step1_base → step2_use_package}/lib/main.dart
@@ -9,6 +10,7 @@
9
10
class MyApp extends StatelessWidget {
10
11
@override
11
12
Widget build(BuildContext context) {
13
- final wordPair = WordPair.random();
12
14
return MaterialApp(
13
15
title: 'Welcome to Flutter',
14
16
home: Scaffold(
@@ -12,7 +14,7 @@
12
14
appBar: AppBar(
13
15
title: Text('Welcome to Flutter')
14
16
),
15
17
body: Center(
16
- child: Text(
'Hello World'),
18
- child: Text(wordPair.asPascalCase),
17
19
),
18
20
),
19
21
);
备忘
「大驼峰式命名法」也称为 upper camel case 或 Pascal case,表示字符串中的每个单词(包括第一个单词)都以大写字母开头。所以,uppercamelcase 会变成 UpperCamelCase。
- 如果应用程序正在运行,请使用热重载按钮 offline_bolt更新正在运行的应用程序。每次单击热重载或保存项目时,都会在正在运行的应用程序中随机选择不同的单词对。这是因为单词对是在 build 方法内部生成的。每次 MaterialApp 需要渲染时或者在 Flutter Inspector 中切换平台时 build 都会运行。
Android
iOS
遇到问题?
如果你的应用程序运行不正常,请查找是否有拼写错误。如果需要通过 Flutter 的 debug 工具,可以查看 开发者工具 页面来查看 debug 和 profile 的工具。如果需要,使用下面链接中的代码来对比更正。
第三步:添加一个 Stateful widget
Stateless widgets 是不可变的,这意味着它们的属性不能改变—— 所有的值都是 final。
Stateful widgets 持有的状态可能在 widget 生命周期中发生变化,实现一个 stateful widget 至少需要两个类:1)一个 StatefulWidget 类;2)一个 State 类,StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在。
在这一步,你将添加一个 stateful widget(有状态的 widget)—— RandomWords
,它会创建自己的状态类 —— RandomWordsState
,然后你需要将 RandomWords
内嵌到已有的无状态的 MyApp
widget。
- 创建一个最简的 state 类,这个类可以在任意地方创建而不一定非要在 MyApp 里,我们的示例代码是放在 MyApp 类的最下面了:
lib/main.dart (RandomWordsState)
- class RandomWordsState extends State<RandomWords> {
- // TODO Add build() method
- }
注意一下 State<RandomWords>
的声明。这表明我们在使用专门用于 RandomWords
的 State 泛型类。应用的大部分逻辑和状态都在这里 —— 它会维护 RandomWords widget 的状态。这个类会保存代码生成的单词对,这个单词对列表会随着用户滑动而无限增长,另外还会保存用户喜爱的单词对(第二部分),也即当用户点击爱心图标的时候会从喜爱的列表中添加或者移除当前单词对。
RandomWordsState
依赖 RandomWords
,我们接下来会创建这个类。
- 添加有状态的
RandomWords
widget 到main.dart
,RandomWords
widget除了创建 State 类之外几乎没有其他任何东西:
lib/main.dart (RandomWords)
- class RandomWords extends StatefulWidget {
- @override
- RandomWordsState createState() => RandomWordsState();
- }
在添加状态类后,IDE 会提示该类缺少 build 方法。接下来,你将添加一个基本的 build 方法,该方法通过将生成单词对的代码从 MyApp
移动到 RandomWordsState
来生成单词对。
- 将
build()
方法添加到RandomWordState
中,如下所示:
lib/main.dart (RandomWordsState)
- class RandomWordsState extends State<RandomWords> {
- @override
- Widget build(BuildContext context) {
- final wordPair = WordPair.random();
- return Text(wordPair.asPascalCase);
- }
- }
- 如下所示,删除
MyApp
里生成文字的代码:
{step2_use_package → step3_stateful_widget}/lib/main.dart
@@ -10,7 +10,6 @@
10
10
class MyApp extends StatelessWidget {
11
11
@override
12
12
Widget build(BuildContext context) {
13
- final wordPair = WordPair.random();
14
13
return MaterialApp(
15
14
title: 'Welcome to Flutter',
16
15
home: Scaffold(
@@ -18,8 +17,8 @@
18
17
title: Text('Welcome to Flutter'),
19
18
),
20
19
body: Center(
21
- child:
Text(wordPair.asPascalCase),
20
- child: RandomWords(),
22
21
),
23
22
),
24
23
);
25
24
}
- 重启应用。应用应该像之前一样运行,每次热重载或保存应用程序时都会显示一个单词对。
小提示
如果你尝试热重载,则可能会看到一条警告,考虑重新启动当前应用:
Reloading… Some program elements were changed during reload but did not run when the view was reassembled; you might need to restart the app (by pressing “R”) for the changes to have an effect.
因为重启应用之后就可以生效,故这可能是误报。
遇到问题?
如果你的应用程序运行不正常,请查找是否有拼写错误。如果需要通过 Flutter 的 debug 工具,可以查看 开发者工具 页面来查看 debug 和 profile 的工具。如果需要,使用下面链接中的代码来对比更正。
第四步:创建一个无限滚动的 ListView
在这一步中,你将扩展(继承)RandomWordsState
类,以生成并显示单词对列表。当用户滚动时,ListView
中显示的列表将无限增长。ListView
的 builder
工厂构造函数允许你按需建立一个懒加载的列表视图。
- 向
RandomWordsState
类中添加一个_suggestions
列表以保存建议的单词对,同时,添加一个_biggerFont
变量来增大字体大小。
lib/main.dart
- class RandomWordsState extends State<RandomWords> {
- final _suggestions = <WordPair>[];
- final _biggerFont = const TextStyle(fontSize: 18.0);
- // ···
- }
备忘
在 Dart 语言中使用下划线前缀标识符,会 强制其变成私有。
接下来,我们将向 RandomWordsState
类添加一个 _buildSuggestions()
方法,此方法构建显示建议单词对的 ListView
。
ListView
类提供了一个名为 itemBuilder
的 builder 属性,这是一个工厂匿名回调函数,接受两个参数 BuildContext
和行迭代器 i
。迭代器从 0 开始,每调用一次该函数 i
就会自增,每次建议的单词对都会让其递增两次,一次是 ListTile,另一次是 Divider。它用于创建一个在用户滚动时候无限增长的列表。
- 向
RandomWordsState
类添加_buildSuggestions()
方法,内容如下:
lib/main.dart (_buildSuggestions)
- Widget _buildSuggestions() {
- return ListView.builder(
- padding: const EdgeInsets.all(16.0),
- itemBuilder: /*1*/ (context, i) {
- if (i.isOdd) return Divider(); /*2*/
- final index = i ~/ 2; /*3*/
- if (index >= _suggestions.length) {
- _suggestions.addAll(generateWordPairs().take(10)); /*4*/
- }
- return _buildRow(_suggestions[index]);
- });
- }
对于每个建议的单词对都会调用一次 itemBuilder,然后将单词对添加到 ListTile 行中。在偶数行,该函数会为单词对添加一个 ListTile row,在奇数行,该函数会添加一个分割线的 widget,来分隔相邻的词对。注意,在小屏幕上,分割线看起来可能比较吃力。
在每一列之前,添加一个1像素高的分隔线 widget。
语法 “i ~/ 2” 表示i除以2,但返回值是整形(向下取整),比如 i 为:1, 2, 3, 4, 5 时,结果为 0, 1, 1, 2, 2,这个可以计算出 ListView 中减去分隔线后的实际单词对数量。
如果是建议列表中最后一个单词对,接着再生成10个单词对,然后添加到建议列表。
对于每一个单词对,_buildSuggestions()
都会调用一次 _buildRow()
。这个函数在 ListTile
中显示每个新词对,这使你在下一步中可以生成更漂亮的显示行,详见本 codelab 的第二部分。
- 在
RandomWordsState
中添加_buildRow()
函数 :
lib/main.dart (_buildRow)
- Widget _buildRow(WordPair pair) {
- return ListTile(
- title: Text(
- pair.asPascalCase,
- style: _biggerFont,
- ),
- );
- }
- 更新 RandomWordsState 的 build 方法以使用 _buildSuggestions(),而不是直接调用单词生成库,代码更改后如下:(使用 Scaffold类实现基础的 Material Design 布局)
lib/main.dart (build)
- @overrideWidget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Startup Name Generator'), ), body: _buildSuggestions(), );}
- 更新
MyApp
的build()
方法,修改title
的值来改变标题,修改home
的值为RandomWords
widget。
{step3_stateful_widget → step4_infinite_list}/lib/main.dart
@@ -10,15 +10,8 @@
10
10
class MyApp extends StatelessWidget {
11
11
@override
12
12
Widget build(BuildContext context) {
13
13
return MaterialApp(
14
- title: '
WelcometoFlutter',
15
- home:
Scaffold(
14
- title: 'StartupNameGenerator',
15
- home: RandomWords(),
16
- appBar: AppBar(
17
- title: Text('Welcome to Flutter'),
18
- ),
19
- body: Center(
20
- child: RandomWords(),
21
- ),
22
- ),
23
16
);
24
17
}
- 重新启动你的项目工程应用,你应该看到一个单词对列表。尽可能地向下滚动,你将继续看到新的单词对。
Android
iOS
遇到问题?
如果你的应用程序运行不正常,请查找是否有拼写错误。如果需要通过 Flutter 的 debug 工具,可以查看 开发者工具 页面来查看 debug 和 profile 的工具。如果需要,使用下面链接中的代码来对比更正。
以 profile 模式运行
重点提醒
请勿在调试模式和热重载功能开启的情况下做性能测试。
截止目前文档所示内容,你的应用应该运行在调试 (debug) 模式中,这个模式意味着在更大的性能开销下实现了更快速的开发效率,比如热重载功能的启用,因此你可能要面临较差质量的动画效果。当你准备分析应用性能或要打包发布的时候,你可能需要 Flutter的 profile 或者 release 构建,相关文档,请查阅文档:Flutter 的构建模式选择。
重点提醒
如果你关心应用大小,请参考 这篇文档。
下一步
The app from part 2
祝贺你!
你已经完成了一个可以同时运行在 iOS 和 Android 平台的 Flutter 应用!同时收获了如下内容:
从零开始创建了一个 Flutter 应用;
编写 Dart 代码;
使用外部的第三方库(package);
在开发过程中试用了热重载 (hot reload);
实现了一个有状态的 widget;
创建了一个懒加载的,无限滚动的列表。
如果你想继续扩展你的应用,在这里进行 第二部分,你将会从以下方面修改你的应用:
为应用添加交互功能,一个能点击的小心心,来保存喜欢的公司名字;
为应用添加一个新的页面(Route),查看收藏列表;
修改应用的主题,变成一个白色系的应用。