Wait the light to fall

编写你的第一个 Flutter 应用,第二部分

焉知非鱼

Flutter 是 Google 的 UI 工具包,用于从单一代码库中为手机、网页和桌面构建漂亮的、原生编译的应用程序。Flutter 可以与现有的代码一起使用,被世界各地的开发者和组织使用,并且是免费和开源的。

介绍 #

Flutter 是 Google 的 UI 工具包,用于从单一代码库中为移动、Web 和桌面构建漂亮的、原生编译的应用程序。Flutter 可以与现有的代码一起工作,被世界各地的开发者和组织使用,并且是免费和开源的。

在这个代码实验室中,您将扩展一个基本的、移动的 Flutter 应用程序,以包含交互性。您还将创建一个用户可以导航到的第二个页面(称为路由)。最后,您将修改应用程序的主题(颜色)。这个代码实验室扩展了第1部分,在这部分中,你将创建一个无限的懒惰加载的列表,但如果你想从第2部分开始,我们将提供起始代码。

你将在第二部分学到什么 #

  • 如何编写一款在 iOS、Android 和 Web 上看起来很自然的 Flutter 应用?
  • 如何使用热重装,加快开发周期?
  • 如何为有状态的 widget 添加交互性?
  • 如何创建并导航到第二个屏幕?
  • 如何使用主题来改变应用程序的外观?

你将在第二部分建立什么 #

您将从一个简单的移动应用程序开始,为创业公司生成一个无尽的建议名称列表。在代码实验室结束时,您的最终用户可以选择和取消选择名称,保存最好的名称。点击应用栏右上角的列表图标可以导航到一个新的页面(称为路由),该页面只列出了最喜欢的名字。

下面的 GIF 动画显示了完成的应用程序将如何工作。

img

设置您的 Flutter 环境 #

如果你还没有完成第1部分,请看设置你的 Flutter 环境,在编写你的第一个Flutter应用,第1部分,设置你的 Flutter 开发环境。

获取启动应用程序 #

如果你已经完成了这个 codelab 的第一部分,你已经有了启动应用程序,startup_namer。你可以进行下一步。

如果你没有 startup_namer,不要害怕,你可以使用下面的说明得到它。

使用创建应用程序中的说明创建一个简单的模板化 Flutter 应用程序。将项目命名为 startup_namer(而不是 flutter_app)。

删除 lib/main.dart 中的所有代码。用这个文件中的代码替换,它显示了一个无限的,懒惰加载的建议启动名称列表。

更新 pubspec.yaml,加入英文单词包。

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  english_words: ^3.1.5    // NEW

英文单词包会生成一对随机的单词,作为潜在的启动名称。

在 Android Studio 的编辑器视图中查看 pubspec 时,点击右上角的 Pub get,这将包拉到你的项目中。你应该在控制台中看到以下内容:

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

运行该应用。

随意滚动,查看持续供应的拟创业公司名称。

将图标添加到列表中 #

在这一步中,你将为每一行添加心形图标。在下一步中,您将使它们可点击并保存收藏夹。

_RandomWordsState 中添加一个 _saved Set。这个 Set 存储了用户收藏的单词配对。SetList 更受欢迎,因为一个正确实现的 Set 不允许重复的条目。

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

_buildRow 函数中,添加一个 alreadySaved 检查,以确保一个单词配对还没有被添加到收藏夹中。

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

_buildRow() 中,你还将为 ListTile 对象添加心形图标以实现收藏夹。在下一步中,你将添加与心形图标交互的功能。

在文本之后添加图标,如下图所示。

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

热重新加载应用程序。

你现在应该看到每一行都有空心,但它们还没有互动。

Android

img

iOS

img

遇到问题了? #

如果你的应用程序不能正常运行,你可以使用下面链接中的代码来回到正轨。

增加互动性 #

在这一步中,你将使心形图标可以点击。当用户点击列表中的一个条目,切换其收藏状态时,该词对就会从一组保存的收藏夹中添加或删除。

要做到这一点,你将修改 _buildRow 函数。如果一个词条已经被添加到收藏夹中,再次点击它就会将其从收藏夹中删除。当一个磁贴被点击后,函数会调用 setState() 来通知框架状态已经改变。

_buildRow 方法中加入 onTap,如下图所示:

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

提示:在 Flutter 的反应式框架中,调用 setState() 会触发对 State 对象的 build() 方法的调用,导致 UI 的更新。

热重载应用。

你应该能够点击任何磁贴来收藏或不收藏该条目。点击瓷砖会产生一个隐含的从点击点发出的泼墨动画。

Android

img

iOS

img

遇到问题了? #

如果你的应用程序不能正常运行,你可以使用下面链接中的代码来回到正轨。

导航到一个新的屏幕 #

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

在 Flutter 中,Navigator 管理着一个包含应用程序路由的堆栈。将一个路由推到 Navigator 的堆栈上,会将显示更新到该路由。从 Navigator 的堆栈中弹出一条路由,会将显示返回到之前的路由。

接下来,您将在 _RandomWordsStatebuild 方法中为 AppBar 添加一个列表图标。当用户点击列表图标时,一个包含保存的收藏夹的新路由会被推送到 Navigator,显示图标。

build 方法中添加图标及其对应的操作:

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

提示:一些小组件属性会取一个小组件(child),而其他属性,如 action,会取一组小组件(children), 如方括号([])所示。

_RandomWordsState 类中添加一个 _pushSaved() 函数。

void _pushSaved() {
}

热重新加载应用程序。列表图标出现在应用栏中。点击它还没有任何作用,因为 _pushSaved 函数是空的。

接下来,你将建立一条路由,并将其推送到 Navigator 的栈中。这个操作会改变屏幕以显示新的路由。新页面的内容是在 MaterialPageRoute 的构建器属性中以匿名函数的方式构建的。

调用 Navigator.push,如下图所示,它将路由推送到 Navigator 的堆栈中。IDE 会抱怨无效代码,但你会在下一节中解决这个问题。

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

接下来,你将添加 MaterialPageRoute 和它的构建器。现在,添加生成 ListTile 行的代码。ListTiledivideTiles() 方法在每个 ListTile 之间增加了水平间距。被划分的变量持有通过方便函数 toList() 转换为列表的最终行。

添加代码,如下面的代码片段所示:

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 组成。每一行都由一个分隔符隔开。

热重载应用。将一些选择收藏起来,然后点击应用栏中的列表图标。新的路由出现,包含收藏夹。请注意,Navigator 在应用栏中增加了一个"返回"按钮。你不必明确地实现 Navigator.pop。点击"返回"按钮就可以返回到主路由。

iOS - Main route

img

iOS - Saved suggestions route

img

遇到问题了? #

如果你的应用程序没有正确运行,那么你可以使用下面链接中的代码来回到正轨。

使用主题改变用户界面 #

在这一步中,您将修改应用程序的主题。主题控制你的应用程序的外观和感觉。您可以使用默认主题,这取决于物理设备或模拟器,或者自定义主题以反映您的品牌。

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

MyApp 类中更改颜色:

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

热重载应用。现在整个背景都是白色的,甚至应用栏也是白色的。

作为一个练习,使用 ThemeData 来改变 UI 的其他方面。Material 库中的 Colors 类提供了许多你可以玩的颜色常量。热重载使得对 UI 的实验变得快速而简单。

Android

img

iOS

img

遇到问题了? #

如果你已经偏离了轨道,那么使用下面链接中的代码来查看最终应用的代码。

= lib/main.dart

做得很好! #

你写了一个交互式的 Flutter 应用,可以在 iOS 和 Android 上运行,具体做法如下

  • 编写 Dart 代码。
  • 使用热重载来加快开发周期。
  • 实现一个有状态的 widget,为你的应用添加交互性。
  • 创建途径并添加在原途径和新途径之间移动的逻辑。
  • 学习如何使用主题改变你的应用程序的 UI 外观。

今后的步骤 #

从以下资源中了解更多关于 Flutter SDK 的信息。

其他资源包括以下几点:

同时,与 Flutter 社区联系起来!