编写第一个Flutter App(翻译)

博客搬迁至https://blog.wangjiegulu.comhtml

RSS订阅:https://blog.wangjiegulu.com/feed.xmlgit

如下代码 Github 地址:https://github.com/wangjiegulu/flutter_test_01github

编写你的第一个Flutter App

原文:https://flutter.io/get-started/codelab/web

这个你建立第一个Flutter app的指南。若是你熟悉面向对象的代码,基本的编程概念,好比变量,循环,和条件,你就能够完成本教程。你不须要以前有Dart或者手机的编程经验。编程

你将构建什么数组

你将要实现一个简单的手机 app,为一个初创公司去生成一些推荐的名字。用户能够选择和取消选择这些名字,并保存最好的一些名字。代码一次生成10个名字。当用户滚动时,新的一批名字就会被生成。用户能够点击 app bar 右上角的按钮进入一个新的页面来仅展现被喜欢的名字。app

Gif 动图展现了 app 完成以后的运行效果。框架

你将学到什么less

  • Flutter app 的基础结构。
  • 查询和使用包来扩展特性。
  • 使用热重载来实现快速的开发周期。
  • 怎么去实现一个 stateful widget 。
  • 怎么去建立一个无限,懒加载的列表。
  • 怎么去建立和导航到第二个页面。
  • 怎么去使用 Theme 来改变 app 的外观。

你将使用什么dom

  • Flutter SDK:Flutter SDK 包括 Flutter 的引擎,framework, widget ,工具和 Dart SDK。这个 codelab 须要 v0.1.4 或者更新。
  • Android Studio IDE:这个 codelab 具有 Android Studio IDE,可是你也可使用其它的 IDE,或者使用命令行工做。
  • 你的 IDE 插件:你的 IDE 上面必须分别安装 Flutter 和 Dart 插件。除了 Android Studio,Flutter 和 Dart 插件在 VS CodeIntelliJ IDE。

关于怎么搭建你的环境,能够在 查看更多信息。

第1步:建立启动 Flutter app

根据 开始你的第一个 Flutter app 的介绍,建立一个简单,模版的 Flutter app。给项目取名为 startup_namer (替换掉 myapp)。您将修改这个 app 来建立完成的 app。

在这个 codelab 中,你主要编辑 dart 代码存放处的 lib/main.dart

提示:当复制代码到你的 app 中,缩进可能会歪斜。你可使用 Flutter 工具来自动修正它们:

  • Android Studio / IntelliJ IDEA: 在 dart 代码上右键并选择 Reformat Code with dartfmt
  • VS Code: 右键并选择 Format Document
  • Ternimal: 运行 flutter format
  1. 替换 lib/main.dart。

    删除 lib/main.dart 中的全部代码。使用下面的代码进行替换,它会在屏幕的中央展现 "Hello World"。

    import 'package:flutter/material.dart';
    
    void main() => runApp(new MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Welcome to Flutter',
          home: new Scaffold(
            appBar: new AppBar(
              title: new Text('Welcome to Flutter'),
            ),
            body: new Center(
              child: new Text('Hello World'),
            ),
          ),
        );
      }
    }
  2. 运行App,你将会看到以下的屏幕

观察

  • 这个例子建立了一个 Material app。Material 是手机和 web 上的标准的设计语言。Flutter 提供了丰富的 Material widget 。
  • main 方法制定了一个宽箭头(=>)标志,这是一行函数或者方法的简写。
  • App 继承了 StatelessWidget,这使得 app 自己称为了一个 widget。在 Flutter 中,几乎全部一切都是 widget,包括 alignment, padding, 和 layout。
  • Material 库中的 Scaffold,提供了一个默认的 app bar,title,和一个 body 属性,它持有了主页面的 widget 树。widget 的子树可能至关复杂。
  • Widget 的主要的工做是提供一个 build() 方法,它描述了如何根据其余较低级别的 widget 显示 widget。
  • 这个例子中的 widget 树的构成是一个中心的 widget 包含了一个文本的子 child widget。中心 widget 将它的 widget 子树对齐到屏幕的中心。

第2步:使用外部包

在这一步,我将使用一个名为 english_words 的开源包,它包含了几千个最经常使用的英文单词和经常使用的工具方法。

pub.dartlang.org,你能够找到 english_words,以及不少其它的开源包。

  1. pubspec 文件为 Flutter app 管理 assets。在 pubspec.yaml,增长 english_words (3.1.0或者更高) 到依赖列表。新增行在下面已被高亮:

    dependencies:
    flutter:
    sdk: flutter
    
    cupertino_icons: ^0.1.0
    english_words: ^3.1.0
  2. 在 Android Studio’s editor 视图查看 pubspec,点击右上角的 Packages get。这会把包拉取到你的项目中。你会在控制台上看到如下信息:

    flutter packages get
    Running "flutter packages get" in startup_namer...
    Process finished with exit code 0
  3. lib/main.dart,增长一个 english_words 的导入,就如高亮展现的那样:

    import 'package:flutter/material.dart';
    import 'package:english_words/english_words.dart';

    因为你的输入,Android Studio 针对库会给你一些导入的建议。而后将导入字符串呈现为灰色,让你知道倒入的库你没有使用它(目前为止)。

  4. 使用 English words 包生成文本,用来替换掉以前的 "Hello World" 字符串。

    提示:"Pascal case" (也称为 “大驼峰式命名法”),表示字符串中的每一个单词,包括第一个单词,首字母大写。因此,“uppercamelcase” 就变成 “UpperCamelCase”。

    作如下改变,以下面高亮处:

    import 'package:flutter/material.dart';
    import 'package:english_words/english_words.dart';
    
    void main() => runApp(new MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random();
        return new MaterialApp(
          title: 'Welcome to Flutter',
          home: new Scaffold(
            appBar: new AppBar(
              title: new Text('Welcome to Flutter'),
            ),
            body: new Center(
              //child: new Text('Hello World'), // Replace the highlighted text...
              child: new Text(wordPair.asPascalCase),  // With this highlighted text.
            ),
          ),
        );
      }
    }
  5. 若是 app 正在运行中,使用热重载按钮()来更新运行中的 app。每一次你点击了热重载,或者保存了项目,你将会看见不一样的词对,它在运行的 app 中是随机的。这是由于词对在 build 方法中被生成。在每次 MaterialApp 须要渲染或者在 Flutter Inspector 中切换平台的时候。

问题?

若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。


第3步:增长一个 Stateful widget

Stateless widget 是不可改变的,意味着它们的属性不能被修改 —— 全部值都是 final 的。

Stateful widgets 维护了状态,它可能会在 widget 的生命周期内被修改。实现一个 statful widget 须要两个类:1)一个 StatefulWidget 类,用来建立一个实例 2)一个 State 类。StatefulWidget 类自己是不可变的,但 State 类在整个 widget 的生命周期中保持不变。

在这一步中,你将会增长一个 stateful widget,RandomWords,增长它的 State class,RandomWordsState。State 类中将最终维护这个 widget 中推荐喜欢的词对。

  1. 增长 stateful RandomWords widget 到你的 main.dart 中。它能够被放在任何地方,甚至 MyApp 以外,可是这里的解决方案放在了文件的底部。RandomWords widget 除了建立它的 State 类没有什么特别的。

    class RandomWords extends StatefulWidget {
      @override
      createState() => new RandomWordsState();
    }
  2. 增长 RandomWordsState 类。app 的大部分代码将会写在这个类中,它维护拉这个 widget 中的 state。这个类会保存生成的词对,会被用户无限滚动,用户经过列表切换中的心图标来添加或删除它们。

    你将逐步编写这个类。做为开始,经过如下高亮的文原本建立一个最小的 class:

    class RandomWordsState extends State<RandomWords> {
    }
  3. 在增长了 state class 以后,IDE 警告这个类缺乏一个 build 方法。而后,你将增长一个基本的 build 方法经过从 MyApp 转移生成词对的代码到 RandomWordsState 来生成词对:

    class RandomWordsState extends State<RandomWords> {
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random();
        return new Text(wordPair.asPascalCase);
      }
    }
  4. 经过如下高亮改变,从 MyApp 中移除生成词对的代码:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random();  // Delete this line
    
        return new MaterialApp(
          title: 'Welcome to Flutter',
          home: new Scaffold(
            appBar: new AppBar(
              title: new Text('Welcome to Flutter'),
           ),
            body: new Center(
              //child: new Text(wordPair.asPascalCase), // Change the highlighted text to...
              child: new RandomWords(), // ... this highlighted text
            ),
          ),
        );
      }
    }

重启 app,若是你尝试去热重载,你可能会看到一个警告:

Reloading...
Not all changed program elements ran during view reassembly; consider
restarting.

这多是误报,但考虑从新启动以确保你的更改反映在 app UI 中。

app 应该会跟之前同样,每次你热重载或者保存的时候展现一个词对。

问题?

若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。


第4步:建立一个无限滚动的 ListView

在这一步,你将扩展 RandomWordsState 来生成和展现一个列表的词对。当用户滚动时,展现在 ListView widget 的列表会无限滚动。ListView 的 builder factory 构造方法容许你根据须要实现懒加载。

  1. 在 RandomWordsState 类中增长一个 _suggestions list 来保存推荐的词对。注意变量如下划线(_)开头。在 Dart 语言中,如下划线做为前缀标志表明私有。

    也增长一个 biggerFont 变量来使字体变大。

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
    
      final _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
  2. 在 RandomWordsState 类中增长一个 _buildSuggestions() 方法。这个方法构建展现词对的 ListView。

    ListView 类提供了一个 builder 属性,itemBuilder,以匿名方法的方式指定一个工厂构造器和回调方法。两个参数会被传入到方法中 —— BuildContext,和行迭代器,i。迭代器从0开始,每一次方法被调用时递增,每一个推荐词对配对一次。这个模型容许在用户滚动时推荐列表无限滚动。

    增长如下高亮行:

    class RandomWordsState extends State<RandomWords> {
      ...
      Widget _buildSuggestions() {
        return new ListView.builder(
          padding: const EdgeInsets.all(16.0),
          // 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: (context, i) {
            // Add a one-pixel-high divider widget before each row in theListView.
            if (i.isOdd) return new 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 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]);
          }
        );
      }
    }
  3. _buildSuggestions 方法在每一个词配对时调用。这个方法在一个 ListTile 中展现一个新的配对,在下一步中它容许你在行中增长交互。

    RandomWordsState 中增长一个 _buildRow 方法:

    class RandomWordsState extends State<RandomWords> {
      ...
    
      Widget _buildRow(WordPair pair) {
        return new ListTile(
          title: new Text(
            pair.asPascalCase,
            style: _biggerFont,
          ),
        );
      }
    }
  4. 使用 _buildSuggestions() 来更新 RandomWordsState 的 build 方法,而不是直接调用生成词对的库。修改如下高亮改变:

    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        final wordPair = new WordPair.random(); // Delete these two lines.
        Return new Text(wordPair.asPascalCase);
        return new Scaffold (
          appBar: new AppBar(
            title: new Text('Startup Name Generator'),
          ),
        body: _buildSuggestions(),
        );
      }
      ...
    }
  5. 更新 MyApp 的 build 方法。在 MyApp 中移除 Scaffold 和 AppBar 实例。这些应该由 RandomWordsState 去管理,这让在下一步中导航到另外一个页面时修改 app bar 的名字更简单。

    用下面高亮的 build 方法替换原生的方法:

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

重启 app,你将看到一个词对列表。按你想要的去滚动列表,你会看到新的词对。

问题?

若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。


第5步:增长交互

在这一步,你将在没行增长一个可点击的心型图标。当用户点击 list 中的每行时,切换它的 “喜欢” 状态,这会触发词对在保存的集合中增长或者删除。

  1. 在 RandomWordsState 中增长一个 _saved 集合。这个集合存储了用户喜欢了的词对。集合首选 List,由于正确的实现是 Set 不容许重复的条目。

    class RandomWordsState extends State<RandomWords> {
      final _suggestions = <WordPair>[];
    
      final _saved = new Set<WordPair>();
    
      final _biggerFont = const TextStyle(fontSize: 18.0);
      ...
    }
  2. _buildRow 方法中,增长一个 alreadySaved 检查来确保词对是否已经添加到喜欢集合中了。

    Widget _buildRow(WordPair pair) {
      final alreadySaved = _saved.contains(pair);
      ...
    }
  3. _buildRow(),增长一个心型的图标到 ListTile 来启用喜欢状态。稍后,你会在这个心型图标上增长一个交互。

    增长如下高亮:

    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,
        ),
      );
    }
  4. 重启 app,如今你会看到每行都有心型图标,可是它们还不能交互。

  5. _buildRow 方法中让心形图标可点击。若是一个词对已经被添加到喜欢集合,再次点击会从喜欢集合中删除。小心形图标被点击,调用setState()方法来通知系统状态被改变。

    增长高亮行:

    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: () {
          setState(() {
            if (alreadySaved) {
              _saved.remove(pair);
            } else {
              _saved.add(pair);
            }
          });
        },
      );
    }

提示:在 Flutter 响应式风格框架中,调用 setState() 触发 State 对象的 build() 方法的调用,结果更新在 UI 中。

热重载 app,你应该会看到点击任意行来喜欢,取消喜欢条目。注意,点击一行会生成从心型图标发出的隐式墨迹飞溅动画。

问题?

若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。


第6步:导航到新的页面

在这一步,你将增长一个新的页面(在 Flutter 被称为 router)用来展现喜欢的集合。你将会学习到怎么从首页导航到一个新的页面。

在 Fluter,Navigator 管理包含了 app 路由页面的栈。压入一个页面到 Navigator 的栈,更新展现那个页面。从 Navigator 弹出一个页面,返回展现上一个页面。

  1. 在 RandomWordsState 的 build 方法中增长一个列表图标到 AppBar 上。当用户点击这个列表图标,一个包含了喜欢的条目的新页面被压入到 Navigator,展现图标。

    提示:一些widget属性接收单个 widget(child),其它的属性,如 action,接收一个数组 widgets(children),经过中括号([])标明。

    在 build 方法中增长图标和它对应的 action:

    class RandomWordsState extends State<RandomWords> {
      ...
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
          appBar: new AppBar(
            title: new Text('Startup Name Generator'),
            actions: <Widget>[
              new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
            ],
          ),
          body: _buildSuggestions(),
        );
      }
      ...
    }
  2. 在 RandomWordsState 类中增长一个 _pushSaved() 方法。

    class RandomWordsState extends State<RandomWords> {
      ...
      void _pushSaved() {
      }
    }

    热重载 app,列表图标出如今 app bar 上。点击它还不会发生任何事情,由于 _pushSaved 方法是空的。

  3. 当用户点击 app bar 上的列表图标,构建一个页面并压入 Navigator 的栈中。这个 action 会改变屏幕去展现新的页面。

    新页面的内容在 MaterialPageRoute 的 builder 属性中经过匿名方法构建。

    增长调用 Navigator.push,以下高亮代码展现,把页面压入到 Navigator 的栈里。

    void _pushSaved() {
      Navigator.of(context).push(
      );
    }
  4. 增长 MaterialPageRoute 和它的 builder。如今,增长生成 ListTile 行的代码。ListTile 的 divideTiles() 方法在每一个 ListTile 之间添加水平间距。分割变量保存最后一行,由 convienice 函数 toList() 转换为列表。

    void _pushSaved() {
      Navigator.of(context).push(
        new MaterialPageRoute(
          builder: (context) {
            final tiles = _saved.map(
                  (pair) {
                return new ListTile(
                  title: new Text(
                    pair.asPascalCase,
                    style: _biggerFont,
                  ),
                );
              },
            );
            final divided = ListTile
                .divideTiles(
              context: context,
              tiles: tiles,
            )
                .toList();
          },
        ),
      );
    }
  5. builder 属性返回一个 Scaffold,包含了新页面的 app bar,名为 “Save Suggestions”。新页面 body 的构成是一个 ListView 包含了 ListTiles 行;每行由分隔符分割。

    增长如下高亮代码:

    void _pushSaved() {
      Navigator.of(context).push(
        new MaterialPageRoute(
          builder: (context) {
            final tiles = _saved.map(
                  (pair) {
                return new ListTile(
                  title: new Text(
                    pair.asPascalCase,
                    style: _biggerFont,
                  ),
                );
              },
            );
            final divided = ListTile
                .divideTiles(
              context: context,
              tiles: tiles,
            )
                .toList();
    
            return new Scaffold(
              appBar: new AppBar(
                title: new Text('Saved Suggestions'),
              ),
              body: new ListView(children: divided),
            );
          },
        ),
      );
    }
  6. 热重载 app,喜欢其中的一些条目并点击 app bar 上的列表图标。新页面展现出来,且包含了喜欢的条目。注意 Navigator 在 app bar 上增长了一个 “Back” 按钮。你不须要明确地实现 Navigator.pop。点击返回按钮来返回首页。




问题?

若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。


第7步:使用 Theme 来改变 UI

在这一步,你将玩转 app 的 theme。Theme会控制你的 app 的视觉和感受。你可使用默认的 theme,这依赖于物理设备或者模拟器,或者你能够自定义 theme 来反映出你的品牌。

  1. 你能够很简单地经过配置 ThemeData 类来改变 app 的主题。你的 app当前使用的是默认的主题,可是你将修改主要颜色为白色。

    经过增长高亮的代码到 MyApop 来改变 app 的主题为白色:

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
          title: 'Startup Name Generator',
          theme: new ThemeData(
            primaryColor: Colors.white,
          ),
          home: new RandomWords(),
        );
      }
    }
  2. 热重载 app,注意,整个背景都是白色的,甚至是 app bar。

  3. 做为读者的联系,使用 ThemeData 来改变 UI 的其它方面。Material 库中的 Colors 类提供了不少颜色常量可使用,而后热重载使得 UI 实验变的又快又简单。



问题?

若是你的 app 没有正确运行,排查错误。若是须要,请使用如下连接的代码来追踪。


干得不错

你已写了一个运行在 iOS 和 Android 的具备交互性的 Flutter app。在这个 codelab,你已经:

  • 从头建立了一个 Flutter app。
  • 编写 Dart 代码。
  • 使用外部第三方库。
  • 使用热重载来进行快速的开发周期。
  • 实现了 stateful widget,给你的 app 增长了互动性。
  • 使用 ListView 和 ListTiles 建立了一个懒加载,无限滚动的列表。
  • 建立了一个页面,且增长了在主页和新的页面以前移动的逻辑。
  • 学习改变 app 主题外观和主题
相关文章
相关标签/搜索