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

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

在此 Codelab 中,您将创建一个简单的 Flutter 移动应用。如果您熟悉面向对象的代码和基本编程概念(例如变量、循环和条件),则可以完成此 Codelab。您不需要具备 Dart、移动或网络编程经验。

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

  • 如何编写在 iOS、Android 和网络上看起来自然的 Flutter 应用
  • Flutter 应用的基本结构
  • 找到并使用软件包来扩展功能
  • 使用热重载缩短开发周期
  • 如何实现有状态微件
  • 如何创建无限延迟加载的列表

在此 Codelab 的第 2 部分,您将添加互动功能、修改应用的主题,以及添加导航到新页面的功能(在 Flutter 中称为路由)。

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

您将实现一个为初创公司生成建议名称的简单应用。用户可以选择和取消选择名称,从而保留最佳名称。此代码一次会延迟生成 10 个名称。随着用户滚动屏幕,系统会生成更多名称。用户滚动屏幕的距离不受限制。

下方的动画 GIF 展示了在本部分完成后该应用是如何工作的:

6556f8b61acd6a89.gif

您想通过此 Codelab 学习什么?

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

您需要 2 款软件来完成此 Codelab:Flutter SDK一款编辑器。(此 Codelab 假定您会使用 Android Studio,但您也可以使用自己偏好的编辑器。)

您可以使用以下任一设备运行本 Codelab:

  • 一台连接到计算机并设置为开发者模式的实体 AndroidiOS 设备
  • iOS 模拟器(需要安装 Xcode 工具)
  • Android 模拟器(需要在 Android Studio 中设置)
  • 浏览器(调试需要 Chrome)

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 的内容。删除 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 或网络输出结果,具体取决于您的设备。

Android

iOS

cf1e10b838bf60ee.png 观察结果

  • 此示例创建了一个 Material 应用。Material 是移动设备和网络标配的一种视觉设计语言。Flutter 提供一套丰富的 Material 微件。
  • main 方法使用箭头 (=>) 表示法。为单行函数或方法使用箭头表示法。
  • 此应用扩展 StatelessWidget,使应用本身成为微件。在 Flutter 中,几乎每个元素都是微件,包括对齐、内边距和布局。
  • 来自 Material 库的 Scaffold 微件提供默认应用栏、标题和正文属性,用于存储主屏幕的微件树。微件子树可能非常复杂。
  • 微件的主要任务是提供 build 方法,用于说明如何通过其他更低级别的微件显示此微件。
  • 此示例的正文由包含一个 Text 子微件的 Center 微件组成。Center 微件可令其微件子树在屏幕上居中对齐。

在此步骤中,您将开始使用名为 english_words 的开源软件包(其中包含数千个最常用的英语单词)以及一些工具函数。

您可以在 pub.dev 中找到 english_words 软件包以及许多其他开源软件包。

b2f84ff91b0e1396.png pubspec 文件管理 Flutter 应用的资源。在 pubspec.yaml 中,将 english_words: ^4.0.0english_words 4.0.0 或更高版本)附加到依赖项列表中:

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.2
  english_words: ^4.0.0   # add this line

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

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

执行 dart pub get 还会自动生成 pubspec.lock 文件,其中将包含提取到项目中的所有软件包的列表及其版本号。

b2f84ff91b0e1396.pnglib/main.dart 中,导入新软件包:

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';  // Add this line.

在您输入时,Android Studio 会针对要导入的库提供建议。随后,系统随后会将导入字符串显示为灰色,以表示(到目前为止)此导入库尚未使用。

接下来,您将不使用“Hello World”,而使用 english_words 软件包来生成文本。

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(                       // Drop the const, and
          //child: Text('Hello World'),     // Replace this text...
          child: Text(wordPair.asPascalCase),  // With this text.
        ),
      ),
    );
  }
}

b2f84ff91b0e1396.png 如果应用正在运行,则可以利用热重载 e11f6ccd1560a28b.png 更新正在运行的应用。(如果使用命令行,可以输入 r 来执行热重载。)每次点击热重载或保存项目时,都应该会在运行的应用中看到随机选择的不同单词对。这是因为单词对是在 build 方法中生成的,该方法会在每次 MaterialApp 要求渲染时或在 Flutter Inspector 中切换平台时运行。

Android

iOS

有问题?

如果您的应用运行不正常,请检查是否存在拼写错误。如果需要,请使用以下链接中提供的代码恢复到正常状态。

无状态微件是不可变的,这表示它们的属性无法更改,即所有值都是最终值。

有状态微件的状态在微件的生命周期内可能会发生变化。实现有状态微件至少需要两个类:1) StatefulWidget,用于创建 State 类的实例。StatefulWidget 对象本身是不可变的,可以丢弃和重新生成,但 State 对象会在微件的整个生命周期内持续存在。

在此步骤中,您将添加有状态微件 RandomWords,它会创建其 State_RandomWordsState。然后,您要将 RandomWords 用作现有 MyApp 无状态微件内的子项。

b2f84ff91b0e1396.png 为有状态微件创建样板代码。

它可以放在文件中 MyApp 之外的任何位置,但解决方案会将它放在文件底部。在 lib/main.dart 中,将光标置于所有代码之后,多次输入 Return(返回)以另起一行进行输入。在 IDE 中,开始输入 stful。编辑器会询问您是否要创建一个 Stateful 微件。按 Return(返回)接受。这时会显示两个类的样板代码,您可以在光标所在位置输入无状态微件的名称。

b2f84ff91b0e1396.png 输入 RandomWords 作为微件的名称。

如以下代码所示,除了创建自己的 State 类,RandomWords 微件基本上没有其他操作。

一旦输入 RandomWords 作为有状态微件的名称,IDE 就会自动更新随附的 State 类,将其命名为 _RandomWordsState。默认情况下,State 类的名称以下划线为前缀。为标识符添加下划线前缀会在 Dart 语言中强制执行隐私保护,这是建议对 State 对象使用的最佳做法。

IDE 还会自动更新状态类以扩展 State<RandomWords>,指明您在使用专门与 RandomWords 搭配使用的通用 State 类。该应用的大多数逻辑都在于它会维持 RandomWords 微件的状态。这个类会保存已生成的单词对的列表,此列表会随着用户滚动屏幕无限增长,而在此实验的第 2 部分中,此列表会随着用户使用收藏夹收藏单词对(通过切换心形图标在列表中添加或移除单词对)而无限增长。

现在,这两个类如下所示:

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 热重载应用。应用的表现应该与之前一样,在您每次热重载或保存应用后显示一个单词对。

有问题?

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

在此步骤中,您将扩展 _RandomWordsState,以生成并显示单词配对列表。随着用户滚动屏幕,该列表(显示在 ListView 微件中)会无限增长。您可使用 ListView 中的 builder 工厂构造函数按需延迟构建列表视图。

b2f84ff91b0e1396.png_RandomWordsState 类中添加一些状态变量。

添加一个 _suggestions 列表用于保存建议的单词对。此外,添加一个 _biggerFont 变量用于增大字体大小。

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];                 // NEW
  final _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_buildRow 函数添加到 _RandomWordsState

  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.png 更新 MyAppbuild 方法,从而更改标题、移除 AppBar,并将主屏幕属性更改为 RandomWords 微件。

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

b2f84ff91b0e1396.png 重启应用。无论您在屏幕上滚动多远的距离,您都应该会看到单词对列表。

Android

iOS

有问题?

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

恭喜!

您已完成此 Codelab 的第 1 部分!如果您想扩展此应用,请继续完成第 2 部分。在这部分中,将您对应用进行如下修改:

  • 添加互动功能。
  • 添加导航到新路由的功能。
  • 修改主题颜色。

第 2 部分完成后,应用将如下所示:

7fcab088cd22cff7.gif

其他后续步骤

通过以下资源详细了解 Flutter SDK:

其他资源包括:

此外,您还可联系 Flutter 社区