编写您的第一款 Flutter 应用(第 1 部分)

1. 简介

Flutter 是 Google 的界面工具包,可用于通过单一代码库为移动设备、网络和桌面设备制作本机编译的精美应用程序。Flutter 可与现有代码一起使用,已受到全球开发者和组织的青睐,是免费的开放源代码。

在此 Codelab 中,您将创建一个简单的移动端 Flutter 应用。如果您熟悉面向对象的代码和基本编程概念(如变量、循环和条件语句),则可以完成此 Codelab。不需要参考以往的 Dart、移动端或 Web 端的编程经验。

您将在第 1 部分学习的内容

  • 如何编写像 iOS、Android 和 Web 原生应用一样的 Flutter 应用
  • Flutter 应用的基本结构
  • 找到并使用软件包来扩展功能
  • 使用热重载缩短开发周期
  • 如何实现有状态微件
  • 如何创建延迟加载的无限列表

在此 Codelab 的 第 2 部分,您将添加交互性、修改应用的主题背景,并添加导航至新页面的功能(在 Flutter 中称为路由)。

您将在第 1 部分构建的应用

您将实现一个简单的应用,该应用可为初创公司生成建议名称。用户可以选择和取消选择名称,以便保存最适合的名称。代码会一次延迟生成 10 个名称。随着用户滚动屏幕,系统可生成更多名称。用户滚动屏幕的距离不受限制。

以下动画 GIF 显示的是应用在完成部分的工作方式:

94d7d0223cd33f56.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 应用

b2f84ff91b0e1396.png按 创建应用中的说明创建简单的模板化 Flutter 应用。输入 startup_namer(而非 flutter_app)作为项目名称。您需要修改此入门级应用,以创建完善的应用。

您通常要编辑 Dart 代码所在的 lib/main.dart。

b2f84ff91b0e1396.png替换 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'),
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png 运行应用。您应该会看到 Android、iOS 或 Web 输出,具体样式因您的设备而定。

Android

iOS

cf1e10b838bf60ee.png观察

  • 此示例创建了一个 Material 应用。Material 可视化设计语言是移动和 Web 端应用的标准语言。Flutter 提供了一组丰富的 Material 微件。
  • main 方法使用箭头 (=>) 表示法。通过箭头表示法来表示一行函数或方法。
  • 此应用扩展了 StatelessWidget,使其本身成为一个微件。在 Flutter 中,几乎每个元素都是微件,其中包括对齐、边距和布局微件。
  • Material 库中的 Scaffold 微件提供默认应用栏、标题和正文属性,用于存储主屏幕的微件树。微件子树很复杂。
  • 微件的主要任务是,提供 build 方法,描述如何通过其他低级别微件显示此微件。
  • 此示例的正文包含一个 Center 微件,其中包含 Text 子微件。Center 微件可令其微件子树在屏幕上居中对齐。

4. 使用外部软件包

在此步骤中,您将开始使用名为 english_words 的开源软件包(其中包含数千个常用英语单词),以及某些实用函数。

您可以在 pub.dev 找到 english_words 软件包,以及许多其他开源软件包。

b2f84ff91b0e1396.png使用 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

b2f84ff91b0e1396.png在 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"文件,其中包含提取至项目的所有软件包的列表及其版本号。

b2f84ff91b0e1396.png在 **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"。

b2f84ff91b0e1396.png完成以下更改:

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.
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png如果应用正在运行,则可使用热重载 e11f6ccd1560a28b.png 更新运行的应用。(您可以在命令行中输入 r 进行热重载。)每次在运行的应用中点击"hot reload"(热重载)或保存项目后,您都应该看到随机选择的不同词对。这是因为词对是在 build 方法内生成的,而此方法会在每次 MaterialApp 需要渲染时或在 Flutter Inspector 中切换平台时运行。

Android

iOS

遇到了问题?

如果您的应用未正常运行,请检查是否有拼写错误。如果需要,请通过使用以下链接中的代码恢复正常状态。

5. 添加有状态微件

无状态微件不可变,这意味着其属性不会改变,所有值都是最终值。

有状态微件的状态在微件的生命周期期间可能会发生变化。实现有状态微件至少需要两个类:

  1. StatefulWidget,可用于创建 State 类的实例。StatefulWidget 对象本身并不可变,并且可以舍弃和重新生成,而 State 对象会在微件的生命周期中持续存在。

在此步骤中,您需要添加有状态微件 RandomWords,以便创建其 State 类 _RandomWordsState。随后您将在现有 MyApp 无状态微件内使用 RandomWords 作为子级。

b2f84ff91b0e1396.png为有状态微件创建样板代码。

此代码可在 MyApp 外部的文件中正常运行,而解决方案将其置于文件底层。在 lib/main.dart 中,将鼠标光标置于所有代码之后,输入几次 **Return(返回)**以从新行开始输入内容。在 IDE 中,开始键入 stful。编辑器会询问您是否想创建 Stateful 微件。按 **Return(返回)**以接受。系统会显示两个类的样板代码,并会确定光标的位置,以便您输入无状态微件的名称。

b2f84ff91b0e1396.png输入 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();
  }
}

b2f84ff91b0e1396.png在 _RandomWordsState 中更新 build() 方法。

将 return Container(); 替换为以下两行内容:

class _RandomWordsState extends State<RandomWords> {
  @override                                  
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();      // NEW
    return Text(wordPair.asPascalCase);      // NEW
  }                                         
}

b2f84ff91b0e1396.png通过完成以下更改,从 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
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png热重载该应用。应用的表现应该与以前一样,在每次热重载或保存应用后显示词对。

遇到了问题?

如果您的应用无法正常运行,则可通过使用以下链接中的代码恢复正常状态。

6. 创建无限滚动 ListView

在此步骤中,您将扩展 _RandomWordsState 以生成并显示词对列表。随着用户滚动屏幕,此列表(会显示在 ListView 微件中)会无限增长。ListView 中包含 builder 工厂构造函数,您可使用此函数按需延迟构建列表视图。

b2f84ff91b0e1396.png在 _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 开始,每次调用函数时,其都会针对每个建议词对增加一次计数。使用此方法,建议列表会随着用户滚动屏幕持续增长。

b2f84ff91b0e1396.png添加完整的 _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 部分用其改善各行的外观。

b2f84ff91b0e1396.png在 _RandomWordsState 中添加 _buildRow 函数:

  Widget _buildRow(WordPair pair) {
    return ListTile(
      title: Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }

b2f84ff91b0e1396.png为 _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.
  }

b2f84ff91b0e1396.png为 MyApp 更新 build 方法,从而更改标题、移除 AppBar 并将主屏幕属性更改为 RandomWords 微件。

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      home: RandomWords(),
    );
  }

b2f84ff91b0e1396.png重启应用。无论您在屏幕上滚动多远的距离,您都应该会看到词对列表。

Android

iOS

遇到了问题?

如果您的应用无法正常运行,则可通过使用以下链接中的代码恢复正常状态。

7. 后续步骤

恭喜!

您已完成此 Codelab 的第 1 部分!如果想扩展此应用,请继续完成 第 2 部分,您可在其中修改应用,如下所示:

  • 添加交互性。
  • 添加导航至新路由的功能。
  • 修改主题颜色。

在完成第 2 部分的操作时,应用如下所示:

17a70f54709401bc.gif

其他后续步骤

通过以下资源详细了解 Flutter SDK:

其他资源包括以下内容:

此外,您还可 联系 Flutter 社区!