编写第一个 Flutter 应用
这是一个指引你完成第一个 Flutter 应用的手把手操作教程(我们也称之为是 codelab)。我们将会着手创建一个简单的 Flutter 应用,无需 Dart 语言、移动开发语言或 Web 开发经验,只需你具备面向对象语言开发基础即可(如变量,循环和条件语句)。
This is a guide to creating your first Flutter app. If you are familiar with object-oriented code and basic programming concepts such as variables, loops, and conditionals, you can complete this tutorial. You don’t need previous experience with Dart, mobile, or web programming.
完整的教程分为两部分,本页面是第一部分的内容,你可以在这里查看 第二部分 的内容。 (Codelabs 里的第一部分内容与本页内容相同)。
This codelab is part 1 of a two-part codelab. You can find part 2 on Google Developers Codelabs (as well as a copy of this codelab, part 1).
第一部分的内容概览
What you’ll build in part 1
你将完成一个简单的应用,功能是:为一个创业公司生成建议的公司名称。用户可以选择和取消选择的名称、保存喜欢的名称。该代码一次生成十个名称,当用户滚动时,会生成新一批名称。
You’ll implement a simple app that generates proposed names for a startup company. The user can select and unselect names, saving the best ones. The code lazily generates 10 names at a time. As the user scrolls, more names are generated. There is no limit to how far a user can scroll.
页面上方的这个 GIF 可以引导你预览本 codelab 做完之后的应用效果图。
The animated GIF shows how the app works at the completion of part 1.
如果您想要编译在 Web 上运行的应用程序,则必须先启用此功能(当前处于测试版)。要启用 Web 支持,可以按照以下说明操作:
If you want to compile your app to run on the web, you must enable this feature (which is currently in beta). To enable web support, use the following instructions:
$ flutter channel beta
$ flutter upgrade
$ flutter config --enable-web
您只需要运行一次 config 命令。启用 Web 支持后,您创建的每个 Flutter 应用程序都可以针对 Web 平台编译。在 IDE 下拉菜单的 devices,或使用命令行 flutter devices
,您应该就可以看到列出了 Chrome 和 Web server。
Chrome 设备会自动启动 Chrome,Web server 会启动承载该应用的服务器,这样您就可以从任何浏览器加载访问。在开发过程中使用 Chrome 设备时,您也可以使用 DevTools,如果您想要在其他浏览器上进行测试,可以使用 web server。更多相关信息,请参见 使用 Flutter 构建 Web 应用程序 和在 编写你的第一个 Flutter 网页应用。
You need only run the config command once.
After you enable web support, every Flutter app you
create also compiles for the web. In your IDE under
the devices pulldown, or at the command line
using flutter devices
, you should now see Chrome
and Web server listed. The Chrome device
automatically starts Chrome. The Web server
starts a server that hosts the app so that you can
load it from any browser. Use the Chrome device during
development so that you can use DevTools,
and the web server when you want to test on
other browsers. For more information,
see Building a web application with Flutter
and Write your first Flutter app on the web.
第一步:创建初始化工程
Step 1: Create the starter Flutter app
按照 这个指南 中所描述的步骤,创建一个简单的、基于模板的 Flutter 工程,然后将项目命名为 startup_namer (而不是 myapp),接下来你将会修改这个工程来完成最终的 App。
Create a simple, templated Flutter app, using the instructions in Getting Started with your first Flutter app. Name the project startup_namer (instead of flutter_app).
在这个示例中,你将主要编辑 Dart 代码所在的 lib/main.dart 文件,
You’ll mostly edit lib/main.dart, where the Dart code lives.
-
替换 lib/main.dart
删除 lib/main.dart 中的所有代码,然后替换为下面的代码,它将在屏幕的中心显示”Hello World”。Replace the contents of
lib/main.dart
.
Delete all of the code from lib/main.dart. Replace with the following code, which displays “Hello World” in the center of the screen.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'), ), ), ); } }
-
运行 你的工程项目,根据不同的操作系统,你会看到如下运行结果界面:
Run the app in the way your IDE describes. You should see either Android, iOS, or web output, depending on your device.
Android iOS
观察和分析
Observations
-
本示例创建了一个具有 Material Design 风格的应用, Material 是一种移动端和网页端通用的视觉设计语言, Flutter 提供了丰富的 Material 风格的 widgets。在
pubspec.yaml
文件的flutter
部分选择加入uses-material-design: true
会是一个明智之举,通过这个可以让您使用更多 Material 的特性,比如其预定义好的 图标 集。This example creates a Material app. Material is a visual design language that is standard on mobile and the web. Flutter offers a rich set of Material widgets.
It’s a good idea to have auses-material-design: true
entry in theflutter
section of yourpubspec.yaml
file. This will allow you to use more features of Material, such as their set of predefined Icons. -
主函数(main)使用了 (
=>
) 符号,这是 Dart 中单行函数或方法的简写。The
main()
method uses arrow (=>
) notation. Use arrow notation for one-line functions or methods. -
该应用程序继承了
StatelessWidget
,这将会使应用本身也成为一个 widget。在 Flutter 中,几乎所有都是 widget,包括对齐 (alignment)、填充 (padding) 和布局 (layout)。The app extends
StatelessWidget
, which makes the app itself a widget. In Flutter, almost everything is a widget, including alignment, padding, and layout. -
Scaffold
是 Material 库中提供的一个 widget,它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性。 widget 树可以很复杂。The
Scaffold
widget, from the Material library, provides a default app bar, and a body property that holds the widget tree for the home screen. The widget subtree can be quite complex. -
一个 widget 的主要工作是提供一个
build()
方法来描述如何根据其他较低级别的 widgets 来显示自己。A widget’s main job is to provide a
build()
method that describes how to display the widget in terms of other, lower level widgets. -
本示例中的 body 的 widget 树中包含了一个
Center
widget, Center widget 又包含一个Text
子 widget, Center widget 可以将其子 widget 树对齐到屏幕中心。The body for this example consists of a
Center
widget containing aText
child widget. The Center widget aligns its widget subtree to the center of the screen.
第二步:使用外部 package
Step 2: Use an external package
在这一步中,你将开始使用一个名为 english_words 的开源软件包,其中包含数千个最常用的英文单词以及一些实用功能。
In this step, you’ll start using an open-source package named english_words, which contains a few thousand of the most used English words plus some utility functions.
你可以在 pub.dev 上找到
english_words
软件包以及其他许多开源软件包。
You can find the english_words
package,
as well as many other open source packages, on pub.dev.
-
pubspec.yaml
文件管理 Flutter 应用程序的 assets(资源,如图片、package等)。在pubspec.yaml 中,将 english_words(3.1.5 或更高版本)添加到依赖项列表,如下面高亮显示的行:The
pubspec.yaml
file manages the assets and dependencies for a Flutter app. Inpubspec.yaml
, addenglish_words
(3.1.5 or higher) to the dependencies list:{step1_base → step2_use_package}/pubspec.yaml@@ -8,4 +8,5 @@88dependencies:99flutter:1010sdk: flutter1111cupertino_icons: ^0.1.212+ english_words: ^3.1.5 -
在 Android Studio 的编辑器视图中查看
pubspec.yaml
文件时,点击 Pub get 会将依赖包安装到你的项目。你应该会在控制台中看到以下内容:While viewing the
pubspec.yaml
file in Android Studio’s editor view, click Pub get. This pulls the package into your project. You should see the following in the console:$ flutter pub get Running "flutter pub get" in startup_namer... Process finished with exit code 0
在执行
Pub get
命令时会自动生成一个名为pubspec.lock
文件,这里包含了你依赖 packages 的名称和版本。Performing
Pub get
also auto-generates thepubspec.lock
file with a list of all packages pulled into the project and their version numbers. -
在
lib/main.dart
中引入,如下所示:In
lib/main.dart
, import the new package:lib/main.dartimport 'package:flutter/material.dart'; import 'package:english_words/english_words.dart';
在你输入时,Android Studio会为你提供有关库导入的建议。然后它将呈现灰色的导入字符串,让你知道导入的库截至目前尚未被使用。
As you type, Android Studio gives you suggestions for libraries to import. It then renders the import string in gray, letting you know that the imported library is unused (so far).
-
接下来,我们使用 English words 包生成文本来替换字符串”Hello World”:
Use the English words package to generate the text instead of using the string “Hello World”:
{step1_base → step2_use_package}/lib/main.dart@@ -9,6 +10,7 @@910class MyApp extends StatelessWidget {1011@override1112Widget build(BuildContext context) {13+ final wordPair = WordPair.random();1214return MaterialApp(1315title: 'Welcome to Flutter',1416home: Scaffold(@@ -16,7 +18,7 @@1618title: Text('Welcome to Flutter'),1719),1820body: Center(19- child: Text('Hello World'),21+ child: Text(wordPair.asPascalCase),2022),2123),2224); -
如果应用程序正在运行,请使用热重载按钮 offline_bolt 更新正在运行的应用程序。每次单击热重载或保存项目时,都会在正在运行的应用程序中随机选择不同的单词对。这是因为单词对是在 build 方法内部生成的。每次 MaterialApp 需要渲染时或者在 Flutter Inspector 中切换平台时 build 都会运行。
If the app is running, hot reload to update the running app. Each time you click hot reload, or save the project, you should see a different word pair, chosen at random, in the running app. This is because the word pairing is generated inside the build method, which is run each time the
MaterialApp
requires rendering, or when toggling the Platform in Flutter Inspector.Android iOS
遇到问题?
Problems?
如果你的应用程序运行不正常,请查找是否有拼写错误。如果需要通过 Flutter 的 debug 工具,可以查看 开发者工具 页面来查看 debug 和 profile 的工具。如果需要,使用下面链接中的代码来对比更正。
If your app is not running correctly, look for typos. If you want to try some of Flutter’s debugging tools, check out the DevTools suite of debugging and profiling tools. If needed, use the code at the following links to get back on track.
第三步:添加一个 Stateful widget
Step 3: Add a Stateful widget
Stateless widgets 是不可变的,这意味着它们的属性不能改变 —— 所有的值都是 final。
Stateless widgets are immutable, meaning that their properties can’t change—all values are final.
Stateful widgets 持有的状态可能在 widget 生命周期中发生变化,实现一个 stateful widget 至少需要两个类: 1)一个 StatefulWidget 类;2)一个 State 类,StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在。
Stateful widgets maintain state that might change
during the lifetime of the widget. Implementing a stateful
widget requires at least two classes: 1) a StatefulWidget
class
that creates an instance of 2) a State
class. The StatefulWidget
class is, itself, immutable and can be thrown away and regenerated,
but the State
class persists over the lifetime of the widget.
在这一步,你将添加一个 stateful widget(有状态的 widget)—— RandomWords
,它会创建自己的状态类 —— _RandomWordsState
,然后你需要将 RandomWords
内嵌到已有的无状态的 MyApp
widget。
In this step, you’ll add a stateful widget, RandomWords
, which creates
its State
class, _RandomWordsState
. You’ll then use RandomWords
as
a child inside the existing MyApp
stateless widget.
-
创建有状态 widget 的样板代码。
在lib/main.dart
中,将光标置于所有代码之后,输入 回车 几次另起新行。在 IDE 中,输入stful
,编辑器就会提示您是否要创建一个Stateful
widget。按回车键表示接受建议,随后就会出现两个类的样板代码,光标也会被定位在输入有状态 widget 的名称处。Create the boilerplate code for a stateful widget.
Inlib/main.dart
, position your cursor after all of the code, enter Return a couple times to start on a fresh line. In your IDE, start typingstful
. The editor asks if you want to create aStateful
widget. Press Return to accept. The boilerplate code for two classes appears, and the cursor is positioned for you to enter the name of your stateful widget. -
输入
RandomWords
作为有状态 widget 的名称。
RandomWords
widget 的主要作用就是创建其对应的State
类。
输入RandomWords
作为有有状态 widget 的名称后, IDE 会自动更新其对应的State
类,并将其命名为_RandomWordsState
。默认情况下,State
类的名称带有下划线前缀。 Dart 语言中,给标识符加上下划线前缀可以 增强隐私性,并且这也是针对State
对象推荐的最佳实践写法。
IDE 也会自动将状态类继承自State<RandomWords>
,这表示专门用于RandomWords
的通用State
类。该应用程序的大多数逻辑都位于此处—它维护RandomWords
widget 的状态。该类会保存生成的单词对的列表,该列表随用户滚动而无限增长,在本实验的第 2 部分中,用户可以通过点击心形图标,添加或删除列表中收藏的单词对。
这两个类现在都如下所示:Enter
RandomWords
as the name of your widget.
TheRandomWords
widget does little else beside creating itsState
class.
Once you’ve enteredRandomWords
as the name of the stateful widget, the IDE automatically updates the accompanyingState
class, naming it_RandomWordsState
. By default, the name of theState
class is prefixed with an underbar. Prefixing an identifier with an underscore enforces privacy in the Dart language and is a recommended best practice forState
objects.
The IDE also automatically updates the state class to extendState<RandomWords>
, indicating that you’re using a genericState
class specialized for use withRandomWords
. Most of the app’s logic resides here—it maintains the state for theRandomWords
widget. This class saves the list of generated word pairs, which grows infinitely as the user scrolls and, in part 2 of this lab, favorites word pairs as the user adds or removes them from the list by toggling the heart icon.
Both classes now look as follows:class RandomWords extends StatefulWidget { @override _RandomWordsState createState() => _RandomWordsState(); } class _RandomWordsState extends State<RandomWords> { @override Widget build(BuildContext context) { return Container(); } }
-
更新
_RandomWordsState
中的build()
方法:Update the
build()
method in_RandomWordsState
:lib/main.dart (_RandomWordsState)class _RandomWordsState extends State<RandomWords> { @override Widget build(BuildContext context) { final wordPair = WordPair.random(); return Text(wordPair.asPascalCase); } }
-
通过以下差异所示的更改,删除
MyApp
中单词生成的代码:Remove the word generation code from
MyApp
by making the changes shown in the following diff:{step2_use_package → step3_stateful_widget}/lib/main.dart@@ -10,7 +10,6 @@1010class MyApp extends StatelessWidget {1111@override1212Widget build(BuildContext context) {13- final wordPair = WordPair.random();1413return MaterialApp(1514title: 'Welcome to Flutter',1615home: Scaffold(@@ -18,8 +17,8 @@1817title: Text('Welcome to Flutter'),1918),2019body: Center(21- child:Text(wordPair.asPascalCase),20+ child: RandomWords(),2221),2322),2423);2524} -
重启应用。应用应该像之前一样运行,每次热重载或保存应用程序时都会显示一个单词对。
Restart the app. The app should behave as before, displaying a word pairing each time you hot reload or save the app.
遇到问题?
Problems?
如果你的应用程序运行不正常,请查找是否有拼写错误。如果需要通过 Flutter 的 debug 工具,可以查看 开发者工具 页面来查看 debug 和 profile 的工具。如果需要,使用下面链接中的代码来对比更正。
If your app is not running correctly, look for typos. If you want to try some of Flutter’s debugging tools, check out the DevTools suite of debugging and profiling tools. If needed, use the code at the following link to get back on track.
第四步:创建一个无限滚动的 ListView
Step 4: Create an infinite scrolling ListView
在该步骤中,您会拓展 _RandomWordsState
以生成并显示单词对列表。随着用户滚动,列表(显示在 ListView
widget 中)将无限增长。
ListView
的 builder
工厂构造函数使您可以按需延迟构建列表视图。
In this step, you’ll expand _RandomWordsState
to generate
and display a list of word pairings. As the user scrolls the list
(displayed in a ListView
widget) grows infinitely. ListView
’s
builder
factory constructor allows you to build a list view
lazily, on demand.
-
向
_RandomWordsState
类中添加一个_suggestions
列表以保存建议的单词对,同时,添加一个_biggerFont
变量来增大字体大小。Add a
_suggestions
list to the_RandomWordsState
class for saving suggested word pairings. Also, add a_biggerFont
variable for making the font size larger.lib/main.dartclass _RandomWordsState extends State<RandomWords> { final _suggestions = <WordPair>[]; final _biggerFont = TextStyle(fontSize: 18.0); // ··· }
接下来,我们将向
_RandomWordsState
类添加一个_buildSuggestions()
方法,此方法构建显示建议单词对的ListView
。Next, you’ll add a
_buildSuggestions()
function to the_RandomWordsState
class. This method builds theListView
that displays the suggested word pairing.ListView
类提供了一个名为itemBuilder
的 builder 属性,这是一个工厂匿名回调函数,接受两个参数BuildContext
和行迭代器i
。迭代器从 0 开始,每调用一次该函数i
就会自增,每次建议的单词对都会让其递增两次,一次是 ListTile,另一次是 Divider。它用于创建一个在用户滚动时候无限增长的列表。The
ListView
class provides a builder property,itemBuilder
, that’s a factory builder and callback function specified as an anonymous function. Two parameters are passed to the function—theBuildContext
, and the row iterator,i
. The iterator begins at 0 and increments each time the function is called. It increments twice for every suggested word pairing: once for the ListTile, and once for the Divider. This model allows the suggested list to continue growing as the user scrolls. -
向
_RandomWordsState
类添加_buildSuggestions()
方法,内容如下:Add a
_buildSuggestions()
function to the_RandomWordsState
class:lib/main.dart (_buildSuggestions)Widget _buildSuggestions() { return ListView.builder( padding: 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,来分隔相邻的词对。注意,在小屏幕上,分割线看起来可能比较吃力。The
itemBuilder
callback is called once per suggested word pairing, and places each suggestion into aListTile
row. For even rows, the function adds aListTile
row for the word pairing. For odd rows, the function adds aDivider
widget to visually separate the entries. Note that the divider might be difficult to see on smaller devices. -
在每一列之前,添加一个1像素高的分隔线 widget。
Add a one-pixel-high divider widget before each row in the
ListView
. -
语法
i ~/ 2
表示i
除以 2,但返回值是整形(向下取整),比如 i 为:1, 2, 3, 4, 5 时,结果为 0, 1, 1, 2, 2,这个可以计算出ListView
中减去分隔线后的实际单词对数量。The expression
i ~/ 2
dividesi
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 theListView
, minus the divider widgets. -
如果是建议列表中最后一个单词对,接着再生成10个单词对,然后添加到建议列表。
If you’ve reached the end of the available word pairings, then generate 10 more and add them to the suggestions list.
对于每一个单词对,
_buildSuggestions()
都会调用一次_buildRow()
。这个函数在ListTile
中显示每个新词对,这使你在下一步中可以生成更漂亮的显示行,详见本 codelab 的第二部分。The
_buildSuggestions()
function calls_buildRow()
once per word pair. This function displays each new pair in aListTile
, which allows you to make the rows more attractive in the next step. -
-
在
_RandomWordsState
中添加_buildRow()
函数 :Add a
_buildRow()
function to_RandomWordsState
:lib/main.dart (_buildRow)Widget _buildRow(WordPair pair) { return ListTile( title: Text( pair.asPascalCase, style: _biggerFont, ), ); }
-
更新
_RandomWordsState
的build()
方法以使用 _buildSuggestions(),而不是直接调用单词生成库,代码更改后如下:(使用Scaffold
类实现基础的 Material Design 布局)In the
_RandomWordsState
class, update thebuild()
method to use_buildSuggestions()
, rather than directly calling the word generation library. (Scaffold
implements the basic Material Design visual layout.) Replace the method body with the highlighted code:lib/main.dart (build)@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Startup Name Generator'), ), body: _buildSuggestions(), ); }
-
更新
MyApp
的build()
方法,修改title
的值来改变标题,修改home
的值为RandomWords
widget。In the
MyApp
class, update thebuild()
method by changing the title, and changing the home to be aRandomWords
widget:{step3_stateful_widget → step4_infinite_list}/lib/main.dart@@ -10,15 +10,8 @@1010class MyApp extends StatelessWidget {1111@override1212Widget build(BuildContext context) {1313return MaterialApp(14- title: 'WelcometoFlutter',15- home:Scaffold(14+ title: 'Startup Name Generator',15+ home: RandomWords(),16- appBar: AppBar(17- title: Text('Welcome to Flutter'),18- ),19- body: Center(20- child: RandomWords(),21- ),22- ),2316);2417} -
重新启动你的项目工程应用,你应该看到一个单词对列表。尽可能地向下滚动,你将继续看到新的单词对。
Restart the app. You should see a list of word pairings no matter how far you scroll.
Android iOS
遇到问题?
Problems?
如果你的应用程序运行不正常,请查找是否有拼写错误。如果需要通过 Flutter 的 debug 工具,可以查看 开发者工具 页面来查看 debug 和 profile 的工具。如果需要,使用下面链接中的代码来对比更正。
If your app is not running correctly, look for typos. If you want to try some of Flutter’s debugging tools, check out the DevTools suite of debugging and profiling tools. If needed, use the code at the following link to get back on track.
以 profile 模式运行
Profile or release runs
截止目前文档所示内容,你的应用应该运行在调试 (debug) 模式中,这个模式意味着在更大的性能开销下实现了更快速的开发效率,比如热重载功能的启用,因此你可能要面临较差质量的动画效果。当你准备分析应用性能或要打包发布的时候,你可能需要 Flutter 的 profile 或者 release 构建,相关文档,请查阅文档: Flutter 的构建模式选择。
So far you’ve been running your app in debug mode. Debug mode trades performance for useful developer features such as hot reload and step debugging. It’s not unexpected to see slow performance and janky animations in debug mode. Once you are ready to analyze performance or release your app, you’ll want to use Flutter’s “profile” or “release” build modes. For more details, see Flutter’s build modes.
下一步
Next steps

祝贺你!
Congratulations!
你已经完成了一个可以同时运行在 iOS 和 Android 平台的 Flutter 应用!同时收获了如下内容:
You’ve written an interactive Flutter app that runs on both iOS and Android. In this codelab, you’ve:
-
从零开始创建了一个 Flutter 应用;
Created a Flutter app from the ground up.
-
编写 Dart 代码;
Written Dart code.
-
使用外部的第三方库(package);
Leveraged an external, third-party library.
-
在开发过程中试用了热重载 (hot reload);
Used hot reload for a faster development cycle.
-
实现了一个有状态的 widget;
Implemented a stateful widget.
-
创建了一个懒加载的,无限滚动的列表。
Created a lazily loaded, infinite scrolling list.
如果你想继续扩展你的应用,在这里进行 第二部分,你将会从以下方面修改你的应用:
If you would like to extend this app, proceed to part 2 on the Google Developers Codelabs site, where you add the following functionality:
-
为应用添加交互功能,一个能点击的小心心,来保存喜欢的公司名字;
Implement interactivity by adding a clickable heart icon to save favorite pairings.
-
为应用添加一个新的页面(Route),查看收藏列表;
Implement navigation to a new route by adding a new screen containing the saved favorites.
-
修改应用的主题,变成一个白色系的应用。
Modify the theme color, making an all-white app.