这是一个指引你完成第一个 Flutter 应用的手把手操作教程(我们也称之为是 codelab)。我们将会着手创建一个简单的 Flutter 应用,无需 Dart 语言和移动开发语言经验,只需你具备面向对象语言开发基础(如变量,循环和条件语句)即可。
完整的教程分为两部分,本页面是第一部分的内容。
第一部分的内容概览
你将完成一个简单的移动应用程序,功能是:为一个创业公司生成建议的公司名称。用户可以选择和取消选择的名称、保存喜欢的名称。该代码一次生成十个名称,当用户滚动时,会生成一新批名称。
页面上方的这个 GIF 可以引导你预览本 codelab 做完之后的应用效果图。
Flutter 如何在 Android 和 iOS 系统里自动适应不同的 UI 体系
Flutter 工程/项目的基本结构
查找和使用 packages 来扩展功能
使用热重载 (hot reload) 加快开发周期
如何实现有状态的 widget
如何创建一个无限的、延迟加载的列表
在本 codelab 的 第二部分,你还将学到添加交互,修改应用的主题,以及为应用添加一个新的页面(在 Flutter,我们称之为 route)
你需要安装两部分内容来完成本次实验: 和 设置。本 codelab 里,我们以 macOS 环境下的 Android Studio 以做演示, 但你可以选用更顺手的配置。
你可以通过如下任何设备完成本 codelab:
开启开发者模式 (developer mode) 的 Android 和 / 或 iOS 真机;
;
。
按照 中所描述的步骤, 创建一个简单的、基于模板的 Flutter 工程,然后将项目命名为 startup_namer (而不是 myapp), 接下来你将会修改这个工程来完成最终的 App。
小提示
如果你没有在你的集成开发环境(IDE)里看到 “New Flutter Project”(新建一个 Flutter 工程), 请按照完成 Dart 和 Flutter 插件的安装。
在这个示例中,你将主要编辑 Dart 代码所在的 lib/main.dart 文件:
1. 替换 lib/main.dart
lib/main.dart
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
me>
Android
iOS
观察和分析
本示例创建了一个具有 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 site 上找到 english_words
软件包以及其他许多开源软件包。
Pub site 链接:https://pub.flutter-io.cn/flutter
1. pubspec 文件管理 Flutter 应用程序的 assets(资源,如图片、package等)。在pubspec.yaml 中,将 english_words(3.1.0或更高版本)添加到依赖项列表, 如下面高亮显示的行:
2. Android Studio 的编辑器视图中查看 pubspec 时, 单击右上角的 Packages get,这会将依赖包安装到你的项目。你可以在控制台中看到以下内容:
在执行 Packages get
命令的时候,同时会自动生成一个名为 pubspec.lock
的文件,这里包含了你依赖 packages 的名称和版本。
3. 在 lib/main.dart
中引入,如下所示:
lib/main.dart
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
在你输入时,Android Studio会为你提供有关库导入的建议。然后它将呈现灰色的导入字符串,让你知道导入的库截至目前尚未被使用。
4. 接下来,我们使用 English words 包生成文本来替换字符串”Hello World”:
备忘
「大驼峰式命名法」也称为 upper camel case 或 Pascal case, 表示字符串中的每个单词(包括第一个单词)都以大写字母开头。所以,uppercamelcase 会变成 UpperCamelCase。
5. 如果应用程序正在运行,请使用热重载按钮 更新正在运行的应用程序。每次单击热重载或保存项目时,都会在正在运行的应用程序中随机选择不同的单词对。这是因为单词对是在 build 方法内部生成的。每次 MaterialApp 需要渲染时或者在 Flutter Inspector 中切换平台时 build 都会运行。
Android
iOS
如果你的应用程序运行不正常,请查找是否有拼写错误。如果需要,使用下面链接中的代码来对比更正。
pubspec.yaml
链接:https://raw.githubusercontent.com/flutter/codelabs/master/startup_namer/step2_use_package/pubspec.yaml
lib/main.dart
链接:https://raw.githubusercontent.com/flutter/codelabs/master/startup_namer/step2_use_package/lib/main.dart
第三步:添加一个 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。
1. 创建一个最简的 state 类,这个类可以在任意地方创建而不一定非要在 MyApp 里, 我们的示例代码是放在 MyApp 类的最下面了:
lib/main.dart (RandomWordsState)
class RandomWordsState extends State {
// TODO Add build() method
}
注意一下 State
的声明。这表明我们在使用专门用于 RandomWords
的 State 泛型类。应用的大部分逻辑和状态都在这里 —— 它会维护 RandomWords widget 的状态。这个类会保存代码生成的单词对,这个单词对列表会随着用户滑动而无限增长, 另外还会保存用户喜爱的单词对(第二部分), 也即当用户点击爱心图标的时候会从喜爱的列表中添加或者移除当前单词对。
RandomWordsState
依赖 RandomWords
,我们接下来会创建这个类。
2. 添加有状态的 RandomWords
widget 到 main.dart
,RandomWords
widget 除了创建 State 类之外几乎没有其他任何东西:
lib/main.dart (RandomWords)
class RandomWords extends StatefulWidget {
@override
RandomWordsState createState() => RandomWordsState();
}
在添加状态类后,IDE 会提示该类缺少 build 方法。接下来,你将添加一个基本的 build 方法,该方法通过将生成单词对的代码从 MyApp
移动到 RandomWordsState
来生成单词对。
3. 将 build()
方法添加到 RandomWordState
中,如下所示:
lib/main.dart (RandomWordsState)
class RandomWordsState extends State {
@override
Widget build(BuildContext context) {
final wordPair = WordPair.random();
return Text(wordPair.asPascalCase);
}
}
4. 如下所示,删除 MyApp
里生成文字的代码:
5. 重启应用。应用应该像之前一样运行,每次热重载或保存应用程序时都会显示一个单词对。
小提示
如果你尝试热重载,则可能会看到一条警告,考虑重新启动当前应用:
Reloading…
因为重启应用之后就可以生效,故这可能是误报。
如果你的应用程序运行不正常,可以使用下面链接中的代码来对比更正。
lib/main.dart
链接:https://raw.githubusercontent.com/flutter/codelabs/master/startup_namer/step3_stateful_widget/lib/main.dart
第四步:创建一个无限滚动的 ListView
在这一步中,你将扩展(继承)RandomWordsState
类,以生成并显示单词对列表。当用户滚动时,ListView
中显示的列表将无限增长。 ListView
的 builder
工厂构造函数允许你按需建立一个懒加载的列表视图。
1. 向 RandomWordsState
类中添加一个 _suggestions
列表以保存建议的单词对, 同时,添加一个_biggerFont
变量来增大字体大小。
lib/main.dart
class RandomWordsState extends State {
final _suggestions = [];
final _biggerFont = const TextStyle(fontSize: 18.0);
// ···
}
备忘
在 Dart 语言中使用下划线前缀标识符,会强制其变成私有。
接下来,我们将向 RandomWordsState
类添加一个 _buildSuggestions()
方法, 此方法构建显示建议单词对的 ListView
。
ListView
类提供了一个名为 itemBuilder
的 builder 属性,这是一个工厂匿名回调函数, 接受两个参数BuildContext
和行迭代器 i
。迭代器从 0 开始,每调用一次该函数 i
就会自增, 每次建议的单词对都会让其递增两次,一次是 ListTile,另一次是 Divider。它用于创建一个在用户滚动时候无限增长的列表。
2. 向 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]);
});
对于每一个单词对,_buildSuggestions()
都会调用一次 _buildRow()
。这个函数在 ListTile
中显示每个新词对,这使你在下一步中可以生成更漂亮的显示行,详见本 codelab 的第二部分。
对于每个建议的单词对都会调用一次itemBuilder,然后将单词对添加到 ListTile行中。在偶数行,该函数会为单词对添加一个 ListTile row,在奇数行,该函数会添加一个分割线的 widget,来分隔相邻的词对。注意,在小屏幕上,分割线看起来可能比较吃力。
在每一列之前,添加一个1像素高的分隔线 widget。
语法 “i ~/ 2”表示i除以2,但返回值是整形(向下取整),比如 i 为:1, 2, 3, 4, 5 时,结果为 0, 1, 1, 2, 2,这个可以计算出 ListView 中减去分隔线后的实际单词对数量。
如果是建议列表中最后一个单词对,接着再生成10个单词对,然后添加到建议列表。
3. 在 RandomWordsState
中添加 _buildRow()
函数:
lib/main.dart (_buildRow)
Widget _buildRow(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
}
4. 更新 RandomWordsState 的 build 方法以使用 _buildSuggestions(),而不是直接调用单词生成库,代码更改后如下:(使用 Scaffold 类实现基础的 Material Design 布局)
lib/main.dart (build)
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Startup Name Generator'),
),
body: _buildSuggestions(),
);
}
5. 更新 MyApp
的 build()
方法,修改 title
的值来改变标题,修改 home
的值为 RandomWords
widget。
6. 重新启动你的项目工程应用,你应该看到一个单词对列表。尽可能地向下滚动,你将继续看到新的单词对。
Android
iOS
遇到问题?
如果你的应用没有正常运行,你可以使用一下链接中的代码对比更正。
lib/main.dart
链接:https://raw.githubusercontent.com/flutter/codelabs/master/startup_namer/step4_infinite_list/lib/main.dart
重点提醒
请勿在调试模式和热重载功能开启的情况下做性能测试。
截止目前文档所示内容,你的应用应该运行在调试 (debug)模式中,这个模式意味着在更大的性能开销下实现了更快速的开发效率,比如热重载功能的启用。因此,你可能要面临较差质量的动画效果。要查看应用在发布(release)模式下的性能,请尝试执行下面的命令。
Profile 模式运行应用的动画效果将会比调试模式更平滑。
你已经完成了一个可以同时运行在 iOS 和 Android 平台的 Flutter 应用!同时收获了如下内容:
从零开始创建了一个 Flutter 应用;
编写 Dart 代码;
使用外部的第三方库(package);
在开发过程中试用了热重载(hot reload);
实现了一个有状态的 widget;
创建了一个懒加载的,无限滚动的列表。
如果你想继续扩展你的应用,可以关注第二部分,你将会从以下方面修改你的应用:
为应用添加交互功能,一个能点击的小心心,来保存喜欢的公司名字;
为应用添加一个新的页面(Route),查看收藏列表;
修改应用的主题,变成一个白色系的应用。