Skip to content

开发初体验

vscode:

打开 View > Command Palette。

输入「flutter」,选择 Flutter: New Project。

选择 Application。

新建或选择新项目将存放的上层目录。

输入项目名称,例如 my_app,并点击 Enter。

等待项目创建完成,并且 main.dart 文件展现在编辑器中。

该命令会创建一个名为 myapp,里面包含一个简单的示例程序,里面用到了 Material 组件。

目录结构

  • lib/main.dart: 整个应用的入口文件,其中的 main 函数是整个 Flutter 应用的启动起始函数
  • android、ios 目录: 包含 flutter 应用对应的 Android、iOS 应用实体
  • test 目录: 存放项目的测试代码
  • pubspec.yaml 文件: Flutter 应用的包管理文件,引入第三方包时需要在此文件中管理

Run the app

打开 Android Studio -> More Actions -> Virtual Device Manager -> 打开测试 device(例如 device 名为 flutter test)

定位到 VS Code 的状态栏(窗口底部的蓝色栏):

选择 Flutter:3.3.4 flutter test(android-x86 emulator)

点击 vscode 左上角 运行 -> 启动调试(或者按下 F5)

等待应用启动——启动进度会在 Debug Console 中展示。

当应用编译完成后,就可以在设备上运行这个起步应用了。

尝试热重载 (hot reload)

Flutter 通过 热重载 提供快速开发周期,该功能支持应用程序在运行状态下重载代码,无需重新启动应用程序或者丢失程序运行状态。
修改一下代码,然后告诉 IDE 或者命令行工具你需要热重载,然后看一下模拟器或者设备上应用的变化。

打开 lib/main.dart。

修改字符串

1
'You have pushed the button this many times'

改为

1
'You have clicked the button this many times'

保存修改: invoke Save All, or click Hot Reload

你会发现修改后的字符串几乎马上出现在正在运行的应用程序上。

构建模式

https://flutter.cn/docs/testing/build-modes

构建简单应用第1部分

https://flutter.cn/docs/get-started/codelab

你将完成一个简单的应用,功能是:为一个创业公司生成建议的公司名称。用户可以选择和取消选择的名称、保存喜欢的名称。
该代码一次生成十个名称,当用户滚动时,会生成新一批名称。

做完之后的应用效果图:

第一步:创建初始化工程

打开 View > Command Palette。

输入「flutter」,选择 Flutter: New Project。

选择 Application。

新建或选择新项目将存放的上层目录。

输入项目名称,例如 startup_namer,并点击 Enter。

等待项目创建完成,并且 main.dart 文件展现在编辑器中。

在这个示例中,你将主要编辑 Dart 代码所在的 lib/main.dart 文件,

删除 lib/main.dart 中的所有代码,然后替换为下面的代码,它将在屏幕的中心显示「Hello World」。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @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: Text('Hello World'),
        ),
      ),
    );
  }
}

本示例创建了一个具有 Material Design 风格的应用, Material 是一种移动端和网页端通用的视觉设计语言, Flutter 提供了丰富的 Material 风格的 widgets。在 pubspec.yaml 文件的 flutter 部分选择加入 uses-material-design: true 会是一个明智之举,通过这个可以让您使用更多 Material 的特性,比如其预定义好的 图标 集。

该应用程序继承了 StatelessWidget,这将会使应用本身也成为一个 widget。
在 Flutter 中,几乎所有都是 widget,包括对齐 (alignment)、填充 (padding) 和布局 (layout)。

Scaffold 是 Material 库中提供的一个 widget,它提供了默认的导航栏、标题和包含主屏幕 widget 树的 body 属性。 widget 树可以很复杂。

一个 widget 的主要工作是提供一个 build() 方法来描述如何根据其他较低级别的 widgets 来显示自己。

本示例中的 body 的 widget 树中包含了一个 Center widget, Center widget 又包含一个 Text 子 widget, Center widget 可以将其子 widget 树对齐到屏幕中心。

第二步:使用外部 package

在这一步中,你将开始使用一个名为 english_words(https://pub.flutter-io.cn/packages/english_words) 的开源软件包,其中包含数千个最常用的英文单词以及一些实用功能。

你可以在 pub.dev(https://pub.flutter-io.cn/) 上找到 english_words package 以及其他许多开源的 package。

pubspec.yaml 文件管理着 Flutter 工程中的所有资源和依赖。

通过下面的方式将 english_words 这个 package 加入你的工程里:

在你的 IDE 中,将 english_words: ^4.0.0 加到 cupertino_icons 1.0.4 后面,然后保存文件。

文件保存后就会触发依赖的获取,这等同于执行下面的命令:

1
flutter pub add english_words

输出的结果会类似下面这些:

1
2
3
4
5
6
Resolving dependencies...
These packages are no longer being depended on:
+ english_words 4.0.0
Downloading english_words 4.0.0...

Changed 1 dependency!

依赖关系的获取也会自动生成 pubspec.lock 文件,这个文件包含所有加入项目的 package 和版本号信息。

pubspec.yaml 文件管理着 Flutter 工程中的所有资源和依赖

在 lib/main.dart 中引入,如下所示:

1
import 'package:english_words/english_words.dart';

在你输入时,Android Studio会为你提供有关库导入的建议。然后它将呈现灰色的导入字符串,让你知道导入的库截至目前尚未被使用。

接下来,我们使用 English words 包生成文本来替换字符串”Hello World”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    final wordPair = WordPair.random();
    return MaterialApp(
      title: 'Welcome to Flutter',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Welcome to Flutter'),
        ),
        body: Center(
          child: Text(wordPair.asPascalCase),
        ),
      ),
    );
  }
}

第三步:添加一个 Stateful widget

状态的 widgets 是不可变的,这意味着它们的属性不能改变 —— 所有的值都是 final。

状态的 widgets 也是不可变的,但其持有的状态可能在 widget 生命周期中发生变化,实现一个有状态的 widget 至少需要两个类:
1)一个 StatefulWidget 类;
2)一个 State 类,StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在。

在这一步,你将添加一个有状态的 widget —— RandomWords,它会创建自己的状态类 —— _RandomWordsState,然后你需要将 RandomWords 内嵌到已有的无状态的 MyApp widget。

  1. 创建有状态 widget 的样板代码。
    在 lib/main.dart 中,将光标置于所有代码之后,输入 回车 几次另起新行。
    在 IDE 中,输入 stful,编辑器就会提示您是否要创建一个 Stateful widget。
    按回车键表示接受建议,随后就会出现两个类的样板代码,光标也会被定位在输入有状态 widget 的名称处。

  2. 输入 RandomWords 作为有状态 widget 的名称。
    RandomWords widget 的主要作用就是创建其对应的 State 类。

输入 RandomWords 作为有有状态 widget 的名称后, IDE 会自动更新其对应的 State 类,并将其命名为 _RandomWordsState。默认情况下,State 类的名称带有下划线前缀。
Dart 语言中,给标识符加上下划线前缀可以 增强隐私性,并且这也是针对 State 对象推荐的最佳实践写法。

IDE 也会自动将状态类继承自 State<RandomWords>,这表示专门用于 RandomWords 的通用 State 类。
该应用程序的大多数逻辑都位于此处—它维护 RandomWords widget 的状态。
该类会保存生成的单词对的列表,该列表随用户滚动而无限增长,在第 2 部分中,用户可以通过点击心形图标,添加或删除列表中收藏的单词对。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class RandomWords extends StatefulWidget {
    const RandomWords({super.key});

    @override
    State<RandomWords> createState() => _RandomWordsState();
  }

  class _RandomWordsState extends State<RandomWords> {
    @override
    Widget build(BuildContext context) {
      return Container();
    }
  }
  1. 更新 _RandomWordsState 中的 build() 方法:
1
2
3
4
5
6
7
class _RandomWordsState extends State<RandomWords> {
    @override
    Widget build(BuildContext context) {
      final wordPair = WordPair.random();
      return Text(wordPair.asPascalCase);
    }
  }
  1. 通过以下差异所示的更改,删除 MyApp 中单词生成的代码:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @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: RandomWords(),
        ),
      ),
    );
  }
}
  1. 重启应用。应用应该像之前一样运行,每次热重载或保存应用程序时都会显示一个单词对。

第四步:创建一个无限滚动的 ListView

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

  1. 向 _RandomWordsState 类中添加一个 _suggestions 列表以保存建议的单词对,同时,添加一个 _biggerFont 变量来增大字体大小。
1
2
3
4
5
class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _biggerFont = const TextStyle(fontSize: 18);
  // ···
}
  1. 接下来,我们将向 _RandomWordsState 类添加一个 _buildSuggestions() 方法,此方法构建显示建议单词对的 ListView。

ListView 类提供了一个名为 itemBuilder 的 builder 属性,这是一个工厂匿名回调函数,接受两个参数 BuildContext 和行迭代器 i。
迭代器从 0 开始,每调用一次该函数 i 就会自增,每次建议的单词对都会让其递增两次,一次是 ListTile,另一次是 Divider。
它用于创建一个在用户滚动时候无限增长的列表。

  1. 使用 ListView.builder 构造函数,从 _RandomWordsState 类的 build 方法中返回一个 ListView widget。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
return ListView.builder(
  padding: const EdgeInsets.all(16.0),
  itemBuilder: /*1*/ (context, i) {
    if (i.isOdd) return const Divider(); /*2*/

    final index = i ~/ 2; /*3*/
    if (index >= _suggestions.length) {
      _suggestions.addAll(generateWordPairs().take(10)); /*4*/
    }
    return Text(_suggestions[index].asPascalCase);
  },
);

/*1*/ 对于每个建议的单词对都会调用一次 itemBuilder,然后将单词对添加到 ListTile 行中。在偶数行,该函数会为单词对添加一个 ListTile row,在奇数行,该函数会添加一个分割线的 widget,来分隔相邻的词对。注意,在小屏幕上,分割线可能较难辨别。

/*2*/ 在 ListView 里的每一行之前,添加一个 1 像素高的分隔线 widget。

/*3*/ 语法 i ~/ 2 表示 i 除以 2,但返回值是整型(向下取整),比如 i 为:1, 2, 3, 4, 5 时,结果为 0, 1, 1, 2, 2,这个可以计算出 ListView 中减去分隔线后的实际单词对数量。

/*4*/ 如果是建议列表中最后一个单词对,接着再生成 10 个单词对,然后添加到建议列表。

ListView.builder 的构造函数会为每个单词对创建并显示一个 Text widget,下一步里,你将可以为每个单词对返回一个 ListTile widget,这可以让每一行的显示更漂亮。

  1. 在 _RandomWordsState 类的 ListView.builder 里的 itemBuilder 属性体里,将 Text 替换为 ListTile widget:
1
2
3
4
5
6
return ListTile(
  title: Text(
    _suggestions[index].asPascalCase,
    style: _biggerFont,
  ),
);

ListTile 是包含了文本以及前后位图标 widget 的一行。

  1. 更新 _RandomWordsState 的 build() 方法以使用 _buildSuggestions(),而不是直接调用单词生成库,代码更改后如下:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@override
Widget build(BuildContext context) {
  return ListView.builder(
    padding: const EdgeInsets.all(16.0),
    itemBuilder: /*1*/ (context, i) {
      if (i.isOdd) return const Divider(); /*2*/

      final index = i ~/ 2; /*3*/
      if (index >= _suggestions.length) {
        _suggestions.addAll(generateWordPairs().take(10)); /*4*/
      }
      return ListTile(
        title: Text(
          _suggestions[index].asPascalCase,
          style: _biggerFont,
        ),
      );
    },
  );
}
  1. 更新 MyApp 的 build() 方法,修改 title 的值来改变标题,修改 home 的值为 RandomWords widget。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Startup Name Generator',
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Startup Name Generator'),
        ),
        body: const Center(
          child: RandomWords(),
        ),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  const RandomWords({super.key});

  @override
  State<RandomWords> createState() => _RandomWordsState();
}

class _RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _biggerFont = const TextStyle(fontSize: 18);
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      padding: const EdgeInsets.all(16.0),
      itemBuilder: /*1*/ (context, i) {
        if (i.isOdd) return const Divider(); /*2*/

        final index = i ~/ 2; /*3*/
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10)); /*4*/
        }
        return ListTile(
          title: Text(
            _suggestions[index].asPascalCase,
            style: _biggerFont,
          ),
        );
      },
    );
  }
}
  1. 重新启动你的项目工程应用,你应该看到一个单词对列表。尽可能地向下滚动,你将继续看到新的单词对。

构建简单应用第2部分

https://codelabs.flutter-io.cn/codelabs/first-flutter-app-pt2/#0