Wait the light to fall

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

焉知非鱼

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

介绍 #

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

在这个代码实验室中,你将创建一个简单的手机 Flutter 应用。如果你熟悉面向对象的代码和基本的编程概念-如变量、循环和条件, 那么你就可以完成这个 codelab。你不需要以前有 Dart、手机或 Web 编程的经验。

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

  • 如何编写一款在 iOS、Android 和 Web 上看起来很自然的 Flutter 应用?
  • Flutter 应用程序的基本结构。
  • 寻找和使用包来扩展功能。
  • 使用热重装来加快开发周期。
  • 如何实现一个有状态的小组件。
  • 如何创建一个无限的、懒加载的列表。

在这个 codelab 的第2部分中,你将添加交互性,修改应用程序的主题,并添加导航到新页面的能力(在 Flutter 中称为路由)。

你将在第1部分中构建什么 #

你将实现一个移动应用,为一家创业公司生成建议的名字。用户可以选择和取消选择名字,保存最好的名字。代码一次懒惰地生成10个名字。随着用户的滚动,会生成更多的名字。用户可以滚动的范围没有限制。

下面的 GIF 动画显示了应用程序在完成部分时的工作情况。

img

设置你的 Flutter 环境 #

你需要两个软件来完成这个实验室-Flutter SDK一个编辑器。(codelab 假设你使用 Android Studio,但你可以使用你的首选编辑器。)

你可以通过使用以下任何设备来运行 codelab。

  • 一个物理的 AndroidiOS 设备连接到你的计算机并设置为开发者模式。
  • iOS 模拟器(需要安装 Xcode 工具)
  • 安卓模拟器(需要在 Android Studio 中进行设置)
  • 浏览器(调试时需要使用 Chrome 浏览器)

如果你想编译你的应用程序以在 web 上运行,你必须启用此功能(目前处于测试阶段)。要启用 web 支持,请使用以下说明。

flutter channel beta
flutter upgrade
flutter config --enable-web

你只需要运行一次 config 命令。启用 Web 支持后,你创建的每个 Flutter 应用也会为 Web 编译。在你的 IDE 的设备下拉菜单下面,或者在命令行使用 flutter devices,你现在应该看到 Chrome 和 Web 服务器被列出。Chrome 设备会自动启动 Chrome。Web 服务器会启动一个托管应用程序的服务器,这样你就可以从任何浏览器加载它。在开发过程中使用 Chrome 设备,以便你可以使用 DevTools,而当你要在其他浏览器上进行测试时使用 Web 服务器。有关更多信息,请参阅使用 Flutter 构建 Web 应用程序在 Web 上编写你的第一个 Flutter 应用程序

创建 Flutter 应用程序的启动器 #

通过使用创建应用程序中的说明来创建一个简单的、模板化的 Flutter 应用程序。输入 startup_namer(而不是 flutter_app)作为项目名称。您将修改启动器应用程序来创建完成的应用程序。

提示:如果你在 IDE 中没有看到能够启动一个新的 Flutter 项目作为一个选项,那么请确保你已经安装了 Flutter 和 Dart 的插件

你将主要编辑 lib/main.dart,Dart 的代码就在这里。

替换 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'),
        ),
      ),
    );
  }
}

提示:当把代码粘贴到你的应用程序中时,缩进会变得歪斜。你可以用以下 Flutter 工具来解决。

  • Android Studio/IntelliJ IDEA: 右键点击 Dart 代码,选择用 dartfmt 重格式代码
  • VS code: 右键点击并选择格式化文档
  • 终端: 运行 flutter format <文件名>

运行应用程序。您应该看到 Android,iOS 或 Web 输出,取决于您的设备。

安卓系统:

img

iOS:

img

小贴士:第一次在物理设备上运行时,可能需要一段时间来加载。之后,你可以使用热重载来快速更新。在支持的 IDE 中,如果应用正在运行,Save 也会执行热重载。当使用 flutter run 直接从控制台运行应用程序时,输入 r 来执行热重载。

观察:

  • 这个例子创建了一个 Material 应用。Material 是一种视觉设计语言,是移动和 Web 的标准。Flutter 提供了一套丰富的 Material 部件。
  • main 方法使用箭头(=>)符号。对单行函数或方法使用箭头符号。
  • 应用程序扩展了 StatelessWidget,这使得应用程序本身成为一个组件。在 Flutter 中,几乎所有的东西都是组件,包括对齐、填充和布局。
  • Scaffold 组件来自 Material 库,它提供了一个默认的应用栏、一个标题和一个 body 属性,其中存放着主屏幕的组件树。组件子树可以相当复杂。
  • 组件的主要工作是提供一个 build 方法,描述如何用其他低级组件来显示该组件。
  • 本例的主体由包含 Text 子部件的 Center 部件组成。Center 组件将其组件子树对齐到屏幕的中心。

使用外部软件包 #

在这一步中,您将开始使用一个名为 english_words 的开源包,它包含了几千个最常用的英语单词,还有一些实用函数。

你可以在 pub.dev 找到 english_words 包,以及许多其他开源包。

pubspec 文件管理着 Flutter 应用的资产。在 pubspec.yaml 中,附加 english_words: ^3.1.5(english_words 3.1.5 或更高)到依赖列表中。

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.2
  english_words: ^3.1.5   # add this line

在 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” 文件,其中包含所有拉入项目的包的列表和它们的版本号。

lib/main.dart 中,导入新包:

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

当你输入时,Android Studio 会给你建议导入的库。然后,它将导入的字符串渲染成灰色,让你知道导入的库是未使用的(到目前为止)。

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

做以下修改。

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

提示: Pascal 大小写(也称为上驼形大小写)意味着字符串中的每个单词,包括第一个单词,都以大写字母开头。所以,uppercamelcase 就变成了 UpperCamelCase

如果应用程序正在运行,热重载来更新正在运行的应用程序。(在命令行中,你可以输入 r 来热重载。)每次点击热重载或保存项目时,你应该会在运行中的应用程序中看到一个不同的单词对,随机选择。这是因为单词对是在 build 方法里面生成的,每次 MaterialApp 需要渲染时,或者在 Flutter Inspector 中切换 Platform 时,都会运行该方法。

Android:

img

iOS:

img

有问题? #

如果您的应用程序没有正确运行,请查找错别字。如果需要,请使用以下链接中的代码来恢复正常。

添加一个有状态的组件 #

无状态组件是不可改变的,这意味着它们的属性不能改变-所有值都是最终值。

有状态组件维护的状态可能在组件的生命周期内发生变化。实现一个有状态的组件至少需要两个类。1) 一个 StatefulWidget,它可以创建一个 State 类的实例。StatefulWidget 对象本身是不可变的,可以被丢弃和再生,但 State 对象会在 widget 的生命周期内持久存在。

在这一步骤中,您将添加一个有状态的组件 RandomWords,并创建其 State_RandomWordsState。然后,您将在现有的 MyApp 无状态组件中使用 RandomWords 作为子类。

为有状态组件创建模板代码。

它可以放在 MyApp 以外的文件中的任何位置,但解决方案将其放在文件的底部。在 lib/main.dart 中,将光标定位在所有代码之后,输入回车键几次,重新开始一行。在你的 IDE 中,开始输入 stful。编辑器会询问你是否要创建一个 Stateful 的组件。按回车键接受。两个类的模板代码出现了,光标定位让你输入无状态组件的名称。

输入 RandomWords 作为您的小组件的名称。

正如您在下面的代码中所看到的,RandomWords 组件除了创建它的 State 类之外,几乎没有其他的功能。

一旦您输入 RandomWords 作为有状态组件的名称,IDE 会自动更新相应的 State 类,将其命名为 _RandomWordState。默认情况下,State 类的名称是以下划线为前缀的。在标识符前加上下划线可以加强 Dart 语言的隐私性,也是 State 对象的最佳实践。

IDE 也会自动更新 State 类以扩展 State<RandomWords>,表明你正在使用一个专门用于 RandomWords 的通用 State 类。应用程序的大部分逻辑都在这里-它为 RandomWords 组件维护状态。这个类保存了生成的词对列表,随着用户的滚动而无限增长,在本实验室的第二部分中,当用户通过切换心形图标从列表中添加或删除这些词对时,该类会对其进行收藏。

现在两个类的外观如下:

class RandomWords extends StatefulWidget {
  @override
  _RandomWordsState createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

更新 _RandomWordsState 中的 build() 方法。

用以下两行替换 return Container();:

class _RandomWordsState extends State<RandomWords> {
  @override                                  
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();      // NEW
    return Text(wordPair.asPascalCase);      // NEW
  }                                         
}

通过以下修改,删除 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
        ),
      ),
    );
  }
}

热重载应用程序。应用程序应该像以前一样,每次热重载或保存应用程序时都会显示一个单词配对。

提示:如果您在热重载时看到警告,表明您可能需要重新启动应用程序,您应该考虑重新启动应用程序。这可能是一个假阳性,但重启可以确保您的更改反映在应用程序的 UI 中。

遇到问题了? #

如果您的应用程序没有正确运行,您可以使用以下链接中的代码来恢复正常。

创建一个无限滚动的 ListView #

在这一步中,您将展开 _RandomWordsState 来生成并显示单词配对列表。随着用户的滚动,列表(显示在 ListView 小组件中)会无限增长。ListView 中的构建器工厂构造函数允许你按需懒惰地构建一个列表视图。

_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开始,每次调用函数时递增,每一个建议的单词配对都会递增一次。这个模型允许建议列表在用户滚动时继续增长。

添加整个 _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部分中使行更有吸引力。

_RandomWordsState 中添加一个 _buildRow 函数。

Widget _buildRow(WordPair pair) {
    return ListTile(
      title: Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }

更新 _RandomWordsState 的构建方法。

将其改为使用 _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.
  }

更新 MyApp 的构建方法,更改标题,删除 AppBar,并将 home 属性改为 RandomWords 部件。

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

重新启动应用程序。无论你滚动多远,你都应该看到一个单词配对的列表。

Android:

img

iOS:

img

遇到问题了? #

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

今后的步骤 #

恭喜你!

你已经完成了这个代码实验室的第一部分! 如果你想扩展这款应用,请进入第二部分,你将对应用进行如下修改。

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

当第2部分完成后,应用程序将是这样的:

img

其他后续步骤 #

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

其他资源包括以下几点:

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