跳转至正文

创建 widget

了解无状态 widget 以及如何构建你自己的 widget。

学习创建自定义 widget,并使用最常见的 SDK widget,例如 Container、Center 和 Text。

你将完成的内容

创建自定义 StatelessWidget
通过构造函数参数使 widget 可复用
使用 Container 和 BoxDecoration 为 widget 设置样式

步骤

1

开始之前

本应用依赖一些与 UI 无关的游戏逻辑,因此不在本教程范围内。在继续之前,你需要将此逻辑添加到你的应用中。

  1. 下载以下 Dart 文件,并将其保存为项目目录中的 lib/game.dart

  2. 要访问 game.dart 库中定义的类型,请在你的 lib/main.dart 文件中添加对它的 import:

    main.dart
    dart
    import 'package:flutter/material.dart';
    
    import 'game.dart';
    
2

StatelessWidget 的结构

Widget 是一个继承 Flutter widget 类之一的 Dart 类,此处为 StatelessWidget

打开 main.dart 文件,在 MainApp 类下方添加以下代码,其中定义了一个名为 Tile 的新 widget。

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

构造函数

#

Tile 类有一个 constructor,用于定义渲染 widget 时需要传入哪些数据。此处,构造函数接受两个参数:

  • 表示方块猜测字母的 String

  • 表示猜测结果的 HitType enum value,用于确定方块颜色。例如,HitType.hit 会得到绿色方块。

向 widget 构造函数传入数据是使 widget 可复用的核心。

Build 方法

#

最后,还有至关重要的 build 方法,每个 widget 都必须定义它,且它始终返回另一个 widget。

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    // TODO: Replace Container with widgets.
    return Container();
  }
}
3

使用自定义 widget

应用完成后,屏幕上将有 25 个此 widget 的实例。不过现在只显示一个,以便你能看到每次更新。在 MainApp.build 方法中,将 Text widget 替换为以下内容:

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Tile('A', HitType.hit), // NEW
        ),
      ),
    );
  }
}

目前,你的应用会是空白的,因为 Tile widget 返回空的 Container,默认情况下不会显示任何内容。

4

Container widget

Tile widget 由三个最常见的核心 widget 组成: ContainerCenterTextContainer 是一个便捷 widget,封装了多个核心样式 widget,例如 PaddingColoredBoxSizedBoxDecoratedBox

因为完成的 UI 包含 25 个整齐排列的 Tile widget,它应有明确尺寸。在 Container 上设置 width 和 height 属性。(你也可以用 SizedBox widget 实现,但接下来你会用到 Container 的更多属性。)

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    // NEW
    return Container(
      width: 60,
      height: 60,
      // TODO: Add needed widgets
    );
  }
}
5

BoxDecoration 装饰

接下来,使用以下代码为方块添加 Border

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    // NEW
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        // TODO: add background color
      ),
    );
  }
}

BoxDecoration 是一个知道如何为 widget 添加多种装饰的对象,从背景色到边框、盒子阴影等。此处,你添加了边框。热重载后,白色方块周围应出现浅色边框。

游戏完成后,方块颜色将取决于用户的猜测。用户猜对时方块为绿色,字母正确但位置错误时为黄色,字母和位置都错误时为灰色。

下图展示了三种情况。

A screenshot of a green, yellow, and grey tile.

要在 UI 中实现这一点,使用 switch expression 设置 BoxDecorationcolor

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
        // TODO: add children
      ),
    );
  }
}
6

子 widget

最后,将 CenterText widget 添加到 Container.child 属性。

Flutter SDK 中的大多数 widget 都有 childchildren 属性,分别用于传入 widget 或 widget 列表。在自己的自定义 widget 中使用相同的命名约定是最佳实践。

dart
class Tile extends StatelessWidget {
  const Tile(this.letter, this.hitType, {super.key});

  final String letter;
  final HitType hitType;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        color: switch (hitType) {
          HitType.hit => Colors.green,
          HitType.partial => Colors.yellow,
          HitType.miss => Colors.grey,
          _ => Colors.white,
        },
      ),
      child: Center(
        child: Text(
          letter.toUpperCase(),
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}

热重载后会出现绿色方块。要切换颜色,更新并热重载传入你创建的 TileHitType

dart
// main.dart line ~16
// green
Tile('A', HitType.hit);
// grey
Tile('A', HitType.miss);
// yellow
Tile('A', HitType.partial);

很快,这个小方块将成为屏幕上众多 widget 之一。在下一课中,你将开始构建游戏网格本身。

7

回顾

你完成的内容

以下是你本课构建与学习内容的摘要。
构建了自定义 StatelessWidget

你通过扩展 StatelessWidget 创建了新的 Tile widget。每个 widget 都有用于接收数据的构造函数和返回其他 widget 的 build 方法。此模式是 Flutter 构建用户界面的基础。

通过构造函数参数使 widget 可复用

通过将 letterhitType 作为构造函数参数,你的 Tile widget 可以显示不同内容和颜色。通过构造函数传递数据是创建灵活、可复用组件的方式。

使用 Container 和 BoxDecoration 为 widget 设置样式

你使用 Container 设置 widget 尺寸,使用 BoxDecoration 添加边框和背景色。然后通过对 hitType 值使用 switch 表达式有条件地设置方块颜色。

8

自测

Widget 基础测验

1 / 2
每个 Flutter widget 的 build 方法必须返回什么?
  1. 描述 widget 的 String。

    不正确。

    build 方法返回 widget,而非 String。

  2. 另一个 widget。

    正确!

    build 方法始终返回另一个 widget,该 widget 构成 widget 树的一部分。

  3. 表示成功或失败的 boolean。

    不正确。

    widget 不表示成功与否;它们返回要渲染的其他 widget。

  4. 若无内容可显示则返回 null。

    不正确。

    build 方法不能返回 null;它必须返回有效的 widget。

哪个对象用于向 Container 添加边框、背景色和阴影等装饰?
  1. ThemeData

    不正确。

    ThemeData 用于应用级样式,而非单个 container 的装饰。

  2. TextStyle

    不正确。

    TextStyle 用于文本格式,而非 container 装饰。

  3. BoxDecoration

    正确!

    BoxDecoration 可为 Container 添加边框、背景色、渐变、阴影等。

  4. EdgeInsets

    不正确。

    EdgeInsets 用于指定内边距或外边距,而非视觉装饰。