编写您的第一个 Flutter 应用(第 2 部分)

Flutter 是 Google 的界面工具包,用于利用单一代码库为移动设备、网络和桌面设备构建精巧的原生编译应用。Flutter 是一款免费的开放源码工具包,可与现有代码一起使用,广受全球开发者和组织的青睐。

在此 Codelab 中,您将扩展一个基本的 Flutter 移动应用,以使其具有互动功能。您还将创建用户可导航到的第二个页面(称为路由)。最后,您将修改应用的主题(颜色)。您在第 1 部分创建了一个无限延迟加载的列表,而此 codelab 是对第 1 部分的扩展,但如果您想直接从第 2 部分开始,我们将提供起始代码。

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

  • 如何编写在 iOS、Android 和网络上看起来自然的 Flutter 应用
  • 如何使用热重载来缩短开发周期
  • 如何向有状态微件添加互动功能
  • 如何创建和导航到第二个屏幕
  • 如何使用主题更改应用的外观

您将在第 2 部分构建的内容

您将从一个为初创公司生成建议名称的无限列表的简单移动应用着手。在此 Codelab 结束时,您的最终用户将可以选择和取消选择名称,从而保留最佳名称。点按应用栏右上角的列表图标,即可导航到仅列出已收藏名称的新页面(称为路由)。

下方的动画 GIF 展示了完成的应用是如何工作的。

7fcab088cd22cff7.gif

您想通过此 Codelab 学习什么?

我不熟悉这个主题,想深入了解一下。 我对这个主题有所了解,但我想重温一下。 我在寻找示例代码以用到我的项目中。 我在寻找有关特定内容的说明。

如果您尚未完成第 1 部分,请参阅《编写您的第一个 Flutter 应用(第 1 部分)》中的“设置您的 Flutter 环境”一节,设置您的 Flutter 开发环境。

如果您已经完成了此 Codelab 的第 1 部分,那么您就已经有了起始应用 startup_namer。您可以继续执行下一步

如果没有 startup_namer,也不必担心,您可以按照以下说明进行创建。

b2f84ff91b0e1396.png创建一个简单的模板化 Flutter 应用。创建一个名为 startup_namer 的 Flutter 项目,并迁移到 null 安全,具体代码如下所示:

$ flutter create startup_namer
$ cd startup_namer
$ dart migrate --apply-changes

您将主要编辑 Dart 代码所在的 lib/main.dart

b2f84ff91b0e1396.png 删除 lib/main.dart 中的所有代码。将其替换为此文件中的代码,此代码会显示一个无限延迟加载的初创公司建议名称列表。

b2f84ff91b0e1396.png 更新 pubspec.yaml,以添加英文单词软件包:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  english_words: ^4.0.0-0    // NEW

此英文单词软件包会生成随机的单词对,以作为初创公司的备选名称。

b2f84ff91b0e1396.png 在 Android Studio 的编辑器视图中查看 pubspec 时,点击右上角的 Pub get(获取 Pub),这会将此软件包提取到您的项目中。您应该会在控制台中看到以下内容:

flutter pub get
Running "flutter pub get" in startup_namer...
Process finished with exit code 0

b2f84ff91b0e1396.png 运行应用

根据需要滚动屏幕,查看持续出现的初创公司建议名称。

在此步骤中,您将向每行添加心形图标。在下一步中,您将把这些图标设为可点按并保存收藏的名称。

b2f84ff91b0e1396.png_saved Set 添加到 _RandomWordsState。此 Set 会存储用户收藏的单词对。Set 优于 List,因为正确实现的 Set 不允许存在重复条目。

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _saved = <WordPair>{};     // NEW
  final _biggerFont = TextStyle(fontSize: 18.0);
  ...
}

b2f84ff91b0e1396.png_buildRow 函数中,添加一个 alreadySaved 检查,用于确保单词对尚未添加到收藏夹。

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);  // NEW
  ...
}

_buildRow() 中,您还将向 ListTile 对象添加心形图标,以启用收藏功能。在下一步中,您将添加与心形图标互动的功能。

b2f84ff91b0e1396.png 在文本后面添加图标,如下所示:

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(   // NEW from here...
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),                // ... to here.
  );
}

b2f84ff91b0e1396.png 热重载应用。

现在,您会看到每一行都有心形图标,但还无法与之互动。

Android

iOS

有问题?

如果您的应用运行不正常,则可以使用以下链接中提供的代码恢复到正常状态。

在此步骤中,您将让这些图标变得可点按。如果用户点按列表中的某个条目,即可切换其收藏状态,从而将此单词对添加到一组已保存的收藏名称或从中移除。

为此,您需要修改 _buildRow 函数。如果某单词条目已添加到收藏夹,再次点按它即可将它从收藏夹中移除。点按某个图块后,此函数会调用 setState() 来通知框架状态已发生变化。

b2f84ff91b0e1396.pngonTap 添加到 _buildRow 方法,如下所示:

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return ListTile(
    title: Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
    onTap: () {      // NEW lines from here...
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else {
          _saved.add(pair);
        }
      });
    },               // ... to here.
  );
}

b2f84ff91b0e1396.png 热重载应用。

您应该能够点按任意图块来收藏或取消收藏条目。点按图块会生成从点按点发出的隐式墨水飞溅动画。

Android

iOS

有问题?

如果您的应用运行不正常,则可以使用以下链接中提供的代码恢复到正常状态。

在此步骤中,您将添加一个显示收藏夹的新页面(在 Flutter 中称为路由)。您将了解如何在主路由和新路由之间进行导航。

在 Flutter 中,Navigator 管理一个包含应用路由的堆栈。将某路由推送到 Navigator 的堆栈,即可将显示内容更新为此路由。从 Navigator 的堆栈中弹出路由,即可将显示内容恢复为前一个路由。

接下来,您将向 _RandomWordsStatebuild 方法中的 AppBar 添加一个列表图标。当用户点击此列表图标时,包含已保存的收藏项的新路由将推送到 Navigator,从而显示此图标。

b2f84ff91b0e1396.png 将该图标及其对应的操作添加到 build 方法中:

class _RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Startup Name Generator'),
        actions: [
          IconButton(icon: Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

b2f84ff91b0e1396.png_pushSaved() 函数添加到 _RandomWordsState 类。

  void _pushSaved() {
  }

b2f84ff91b0e1396.png 热重载应用。列表图标 a114478ae13b853.png 会显示在应用栏中。现在点按它将不会执行任何操作,因为 _pushSaved 函数是空的。

接下来,您将构建一个路由,并将其推送到 Navigator 的堆栈。此操作会更改显示内容以显示新路由。新页面的内容在匿名函数中使用 MaterialPageRoutebuilder 属性构建而成。

b2f84ff91b0e1396.png 调用 Navigator.push(如下所示),这会将路由推送到 Navigator 的堆栈。IDE 会指出代码无效,但您将在下一部分中修复此问题。

void _pushSaved() {
  Navigator.of(context).push(
  );
}

接下来,您将添加 MaterialPageRoute 及其构建器。现在,添加用于生成 ListTile 行的代码。ListTiledivideTiles() 方法会在每个 ListTile 之间添加水平间距。divided 变量用于存储由便利函数 toList() 转换为列表的最终行。

b2f84ff91b0e1396.png添加代码,如以下代码段所示:

  void _pushSaved() {
    Navigator.of(context).push(
      MaterialPageRoute<void>(
        // NEW lines from here...
        builder: (BuildContext context) {
          final tiles = _saved.map(
            (WordPair pair) {
              return ListTile(
                title: Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(
            context: context,
            tiles: tiles,
          ).toList();

          return Scaffold(
            appBar: AppBar(
              title: Text('Saved Suggestions'),
            ),
            body: ListView(children: divided),
          );
        }, // ...to here.
      ),
    );
  }

builder 属性会返回 Scaffold,其中包含名为 SavedSuggestions 的新路由的应用栏。新路由的正文由包含 ListTiles 行的 ListView 组成。各行之间由分隔线隔开。

b2f84ff91b0e1396.png 热重载应用。将一些选项添加到收藏夹,然后点按应用栏中的列表图标。这时会显示包含收藏夹的新路由。请注意,Navigator 会向应用栏添加“返回”按钮。您无需显式实现 Navigator.pop。点按返回按钮,即可返回主路由。

iOS - 主路由

iOS - 已保存的建议路由

有问题?

如果您的应用运行不正常,则可以使用以下链接中提供的代码恢复到正常状态。

在此步骤中,您将修改应用的主题。主题控制应用的外观和风格。您可以使用默认主题(视实体设备或模拟器而定),也可以自定义主题以反映您的品牌。

通过配置 ThemeData 类,您可以轻松更改应用的主题。此应用使用默认主题,但您将把应用的主色更改为白色。

b2f84ff91b0e1396.pngMyApp 类中更改颜色:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      theme: ThemeData(          // Add the 3 lines from here...
        primaryColor: Colors.white,
      ),                         // ... to here.
      home: RandomWords(),
    );
  }
}

b2f84ff91b0e1396.png 热重载应用。整个背景现在都为白色,包括应用栏。

作为练习,请使用 ThemeData 更改界面的其他方面。Material 库中的 Colors 类提供了很多可供您使用的颜色常量。热重载让您可以快速轻松地对界面试用这些元素。

Android

iOS

有问题?

如果您的应用状态不正常,则可以使用以下链接中提供的代码恢复到正常状态。

您编写了一个在 iOS 和 Android 设备上运行的互动式 Flutter 应用,具体操作步骤如下:

  • 编写 Dart 代码
  • 使用热重载缩短开发周期
  • 实现有状态微件,为应用添加交互功能
  • 创建路由并添加在主路由和新路由之间切换的逻辑
  • 了解如何使用主题更改应用界面的外观

如需详细了解 Flutter SDK,请参阅下列资源:

其他资源包括:

此外,您还可联系 Flutter 社区