https://codelabs.developers.google.com/codelabs/first-flutter-app-pt2/#1

介绍

第二部分将学到啥

  • 如何编写在iOS和Android上看起来很自然的Flutter应用程序。
  • 使用热重载可以加快开发速度。
  • 如何为有状态小部件(stateful widget)添加交互性。
  • 如何创建和导航,并导航到到第二个屏幕。
  • 如何使用主题更改应用程序的外观。

第二部分将构建什么

在part1教程的基础上,用户可以保存和取消保存名字。点击应用栏右上角的列表图标会导航到保存名称的新页面。

开发环境搭建

为列表添加图标

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

1). 将_saved Set添加到RandomWordsState。此Set存储用户收藏的单词配对。 Set优先于List,因为正确实现的Set不允许重复条目。

class RandomWordsState extends State<RandomWords> {
  final List<WordPair> _suggestions = <WordPair>[];
  final Set<WordPair> _saved = new Set<WordPair>();   // Add this line.
  final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

2). 在_buildRow函数中,添加alreadySaved检查单词是否收藏。

Widget _buildRow(WordPair pair) {
  final bool alreadySaved = _saved.contains(pair);  // Add this line.
  ...
}

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

3). 添加icons,如下:

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

添加交互

此步骤中,你将使得心形图标可以点击。当用户点击列表中的条目,将切换“收藏”状态,把单词从收藏集合中添加或删除。

为此,您将修改_buildRow函数。如果单词条目已添加到收藏夹,再次点击它会将其从收藏夹中删除。 当一个tile被轻击时,该函数调用setState()来通知框架状态已经改变。

1). 添加onTap,如下:

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

Tips:在Flutter的响应式(reactive)框架中,调用setState()会触发对State对象的build()方法的调用,从而更新UI页面。

导航到新页面

在此步骤中,您将添加一个显示收藏夹的新页面(flutter中称之为route)。您将学习如何在home route和新route之间导航(navigate )。

在Flutter中,Navigator管理包含应用程序路由的栈。将route推送到Navigator的栈,将显示更新为该route。从导航器的堆栈弹出route,将显示返回到上一个route(先进后出)。

接下来,您将在RandomWordsState的构建方法中向AppBar添加列表图标。当用户单击列表图标时,包含已保存收藏夹的新路径将被推送到Navigator器,显示图标。

1). 将图标及其相应的操作添加到构建方法:

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

注: 此处actions对应的是Widget数组

2). 给RandomWordsState类添加_pushSaved()函数

void _pushSaved() {}

3). reload App,可以看到导航条右测的’列表’按钮,接下来是添加具体的响应事件。

4). 调用Navigator.push,调用Navigator.push,如下所示,将route推送到Navigator的堆栈。 IDE会抱怨无效代码,但您将在下一节中解决此问题。

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

接下来,你会添加MaterialPageRoute以及它的构造器。现在,添加生成ListTile行的代码。 ListTile的divideTiles()方法在每个ListTile之间添加水平间距。divided变量保存最后一行,由便捷函数(convenience function,)转换为列表,toList()

5). 添加代码如下

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

builder属性返回一个Scaffold,其中包含新路径的app bar,名为“Saved Suggestions”。 新路由的主体包含一个包含ListTiles行的ListView;每一行用分隔符分隔。

6). 添加分割线,如下:

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

        return new Scaffold(         // Add 6 lines from here...
          appBar: new AppBar(
            title: const Text('Saved Suggestions'),
          ),
          body: new ListView(children: divided),
        );                           // ... to here.
      },
    ),
  );
}

7). hot reload此程序。收藏一些名称单词并点按应用栏中的列表图标,将会跳转到新的route。 新route显示包含收藏的单词对。请注意,导航器会在应用栏中添加“后退”按钮。您不必显式(explicitly)实现Navigator.pop方法。点击后退按钮返回到主route。

修改UI主题

在此步骤中,您将修改应用程序的主题。主题控制应用程序的外观。您可以使用默认主题(取决于物理设备或模拟器),也可以自定义主题以反映您的品牌(branding)。

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

1). 修改MyApp类中的颜色

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

作为读者的练习,使用ThemeData来更改UI的其他方面。 Material库中的Colors类提供了许多可以使用的颜色常量,热重载使得用户界面快速简便地进行实验。

其他

学习Flutter SDK的其他东西:

更多资源包括:

Please reach out to us at our mailing list. We’d love to hear from you!