编写您的第一款 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:

  • 已连接至您的计算机并设置为开发者模式的实体 AndroidiOS 设备
  • 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 后,您现在应该会看到列出的 ChromeWeb 服务器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.5english_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.pngMyApp 更新 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 社区