跳到主内容

Flutter 小部件预览器

了解如何使用 Flutter Widget 预览器实时查看 Widget 渲染效果,无需运行整个应用程序。

在本指南中,你将学习如何使用 Flutter Widget 预览器。

概述

#

借助 Flutter Widget 预览器,你可以在 Chrome 浏览器中独立于完整应用程序实时查看 Widget 的渲染效果。要启动预览器、在其中显示 Widget 以及自定义预览,请参阅以下章节。

打开预览器

#

IDE

#

从 Flutter 3.38 开始,Android Studio、IntelliJ 和 Visual Studio Code 会在启动时自动运行 Flutter Widget 预览器。

Android Studio 和 IntelliJ

#

要在 Android Studio 或 IntelliJ 中打开 Widget 预览器,请打开侧边栏中的“Flutter Widget Preview”标签页。

Flutter Widget Previewer in Android Studio

Visual Studio Code

#

要在 Visual Studio Code 中打开 Widget 预览器,请打开侧边栏中的“Flutter Widget Preview”标签页。

Flutter Widget Previewer in Visual Studio Code

命令行

#

要启动 Flutter Widget 预览器,请导航至 Flutter 项目的根目录,并在终端中运行以下命令。这将启动一个本地服务器,并在 Chrome 中打开一个 Widget 预览环境,该环境会根据项目更改自动更新。

shell
flutter widget-preview start

预览 Widget

#

启动预览器后,要查看某个 Widget,必须使用定义在 package:flutter/widget_previews.dart 中的 @Preview 注解。此注解可应用于:

  • 顶层函数:返回 WidgetWidgetBuilder 的函数。
  • 类中的静态方法:返回 WidgetWidgetBuilder 的静态方法。
  • 公共 Widget 构造函数和工厂方法:没有必需参数的构造函数和工厂。

以下是如何使用 @Preview 注解预览 Text Widget 的基本示例:

dart
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart'; // For Material widgets

@Preview(name: 'My Sample Text')
Widget mySampleText() {
  return const Text('Hello, World!');
}

Flutter Widget 预览器中的 Widget 示例 每个预览实例都提供了多种用于与预览中的 Widget 进行交互的控件。从左到右依次为:

  • 放大: 放大预览中的 Widget。

  • 缩小: 缩小预览中的 Widget。

  • 重置缩放: 将 Widget 预览恢复到默认缩放级别。

  • 切换浅色/深色模式: 在浅色和深色配色方案之间切换预览主题。

  • 对单个预览执行热重启: 仅重启特定的 Widget 预览,从而无需重启整个应用程序即可快速应用更改。

如果全局状态已修改(例如,静态初始化程序已更改),则可以通过环境右下角的按钮指示整个 Widget 预览器执行热重启。

按所选文件过滤预览

#

在 IDE 中查看预览时,Widget 预览器被配置为根据当前选定的文件过滤预览集。

Filter by previews selected file in Flutter Widget Previewer

要禁用此行为,请切换环境左下角的“按所选文件过滤预览 (Filter previews by selected file)”选项。

自定义预览

#

@Preview 注解有多个参数,可用于自定义预览:

  • name:预览的描述性名称。

  • group:用于在 Widget 预览器中将相关预览归组在一起的名称。

  • size:使用 Size 对象设置的人为尺寸约束。

  • textScaleFactor:自定义字体缩放比例。

  • wrapper:一个用于将预览 Widget 包装在特定 Widget 树中的函数(例如,通过 InheritedWidget 将应用程序状态注入到 Widget 树中)。

  • theme:用于提供 Material 和 Cupertino 主题数据的函数。

  • brightness:初始主题亮度。

  • localizations:用于应用本地化配置的函数。

创建自定义预览注解

#

为了减少定义具有通用属性的预览所需的样板代码,可以扩展 Preview 注解类,以创建针对项目定制的自定义预览注解。

以下是一个提供主题数据的自定义预览注解示例:

dart
final class MyCustomPreview extends Preview {
  const MyCustomPreview({
    super.name,
    super.group,
    super.size,
    super.textScaleFactor,
    super.wrapper,
    super.brightness,
    super.localizations,
  }) : super(theme: MyCustomPreview.themeBuilder);

  static PreviewThemeData themeBuilder() {
    return PreviewThemeData(
      materialLight: ThemeData.light(),
      materialDark: ThemeData.dark(),
    );
  }
}

扩展 Preview 注解类还允许覆盖 Preview.transform() 方法。此方法由 Widget 预览器调用,可用于在运行时修改预览,从而实现那些在 const 上下文中无法实现的预览配置。

dart
final class TransformativePreview extends Preview {
  const TransformativePreview({
    super.name,
    super.group,
    super.size,
    super.textScaleFactor,
    super.wrapper,
    super.brightness,
    super.localizations,
  });

  // Note: this is no longer public or static as it's injected
  // at runtime when transform() is invoked.
  PreviewThemeData _themeBuilder() {
    return PreviewThemeData(
      materialLight: ThemeData.light(),
      materialDark: ThemeData.dark(),
    );
  }

  @override
  Preview transform() {
    final originalPreview = super.transform();
    // Create's a PreviewBuilder that can be used to modify
    // the preview contents.
    final builder = originalPreview.toBuilder();
    builder
      ..name = 'Transformed - ${originalPreview.name}'
      ..theme = _themeBuilder;

    // Return the updated Preview instance.
    return builder.toPreview();
  }
}

创建多个预览配置

#

创建具有不同配置的多个预览非常简单,只需将多个 @Preview 注解应用于单个函数或构造函数即可。

dart
@Preview(
  group: 'Brightness',
  name: 'Example - light',
  brightness: Brightness.light,
)
@Preview(
  group: 'Brightness',
  name: 'Example - dark',
  brightness: Brightness.dark,
)
Widget buttonPreview() => const ButtonShowcase();

Multiple previews in Flutter Widget Previewer

为了简化创建具有通用配置的多个预览,可以扩展 MultiPreview 来创建自定义注解。以下 MultiPreview 创建了与前一个示例相同的两个预览:

dart
/// Creates light and dark mode previews.
final class MultiBrightnessPreview extends MultiPreview {
  const MultiBrightnessPreview();

  @override
  List<Preview> get previews => const [
        Preview(
          group: 'Brightness',
          name: 'Example - light',
          brightness: Brightness.light,
        ),
        Preview(
          group: 'Brightness',
          name: 'Example - dark',
          brightness: Brightness.dark,
        ),
      ];
}

@MultiBrightnessPreview()
Widget buttonPreview() => const ButtonShowcase();

Preview 一样,MultiPreview 也提供了 MultiPreview.transform() 方法,用于在运行时对每个预览执行转换。

dart
/// Creates light and dark mode previews.
final class MultiBrightnessPreview extends MultiPreview {
  const MultiBrightnessPreview({required this.name});

  final String name;

  @override
  List<Preview> get previews => const [
        Preview(brightness: Brightness.light),
        Preview(brightness: Brightness.dark),
      ];

  @override
  List<Preview> transform() {
    final previews = super.transform();
    return previews.map((preview) {
      final builder = preview.toBuilder()
        ..group = 'Brightness'
        // Building names based on values provided to the annotation
        // isn't possible within a constant constructor. However,
        // there's no such restriction when building a Preview at
        // runtime.
        ..name = '$name - ${preview.brightness!.name}';
      return builder.toPreview();
    }).toList();
  }
}

@MultiBrightnessPreview(name: 'Example')
Widget buttonPreview() => const ButtonShowcase();

限制与局限性

#

Flutter Widget 预览器存在一些你应该注意的限制:

  • 公共回调名称:提供给预览注解的所有回调参数都必须是公共且常量(constant)的。这是预览器代码生成实现能够正常工作所必需的。

  • 不受支持的 API:不支持原生插件以及来自 dart:iodart:ffi 库的任何 API。这是因为 Widget 预览器是使用 Flutter Web 构建的,它无法访问底层的原生平台 API。虽然在使用 Chrome 时 Web 插件可能有效,但不能保证它们在其他环境(如嵌入 IDE 时)中也能正常工作。

    具有对 dart:iodart:ffi 传递依赖的 Widget 可以正常加载,但在调用这些库中的任何 API 时都会抛出异常。

    有关如何构造应用程序以在目标多个平台时干净地支持特定于平台的库的详细信息,请参阅 Dart 关于条件导入的文档

  • 资源路径:使用 dart:ui 中的 fromAsset API 加载资源时,必须使用基于包(package-based)的路径,而不是直接的本地路径。这确保了资源能够在预览器的 Web 环境中被正确定位和加载。例如,使用 'packages/my_package_name/assets/my_image.png' 而不是 'assets/my_image.png'

  • 无约束 Widget:无约束的 Widget 会自动被限制在大约 Widget 预览器高度和宽度的一半。此行为将来可能会改变,因此应尽可能使用 size 参数来应用约束。

  • IDE 中的多项目支持:Widget 预览器目前仅支持显示包含在单个项目或 Pub 工作区内的预览。我们正在积极调研支持包含多个 Flutter 项目的 IDE 会话的方案 (#173550)。