跳转至正文

Flutter Widget 预览器

学习如何使用 Flutter Widget 预览器,在全应用之外实时查看 widget 渲染效果。

本指南将介绍如何使用 Flutter Widget 预览器。

概览

#

利用 Flutter Widget Previewer,你可以在 IDE 或浏览器中,独立于完整应用,实时渲染、检查和迭代单个 UI 组件。

以下将介绍如何打开预览器、预览 widget、搜索和筛选预览结果,以及自定义预览配置。

打开预览器

#

IDE

#

Android Studio、IntelliJ 和 Visual Studio Code 在启动时会自动启动 Flutter Widget Previewer。

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 Previewer,请先导航至 Flutter 项目的根目录,然后运行以下命令:

shell
flutter widget-preview start

This launches a local server and opens a real-time preview environment in your browser.

To optimize startup times, the previewer automatically caches project builds in a .widget_preview/ folder in your project root.

预览 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!');
}

Sample widget in Flutter Widget Previewer

每个预览实例提供多种控件,用于与预览中的 widget 交互。从左到右:

  • Zoom in(放大): 放大预览中的 widget。

  • Zoom out(缩小): 减小预览中的放大倍数。

  • Reset zoom(重置缩放): 将 widget 预览恢复为默认缩放级别。

  • Toggle between light and dark mode(切换浅色/深色模式): 在浅色与深色配色方案之间切换预览主题。

  • Perform a hot restart for the individual preview(对单个预览执行热重启): 仅重启该 widget 预览,可快速应用更改而无需重启整个应用。

若已修改全局状态(例如静态初始化器已更改),可使用右下角的按钮让整个 widget 预览器热重启。

Search and filter previews

#

The Widget Previewer environment includes search and filtering capabilities to help navigate projects with many annotated previews.

Search bar and filtering criteria

#

Use the search bar at the top of the environment to filter previews in real-time. Click the filter dropdown next to the search field to choose which criteria to match against:

  • Preview name: Filters by the descriptive name set in the @Preview annotation.
  • Group name: Filters by the group name specified in the @Preview(group: ...) parameter.
  • Containing script: Filters by the URI of the Dart file containing the preview.
  • Containing package: Filters by the package name containing the preview.

For example, the parameters in the following preview annotation match against the Preview name ('Submit Button') and Group name ('Form Controls') filters:

dart
@Preview(name: 'Submit Button', group: 'Form Controls')
Widget submitButtonPreview() => const SubmitButton();

Filter previews by selected file in IDEs

#

When viewing previews within an IDE, the widget previewer can also filter previews based on the currently selected file:

Filter previews by selected file in Flutter Widget Previewer

To toggle this behavior, select the Filter previews by selected file option at the bottom of the environment.

自定义预览

#

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

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

  • group:在 widget 预览器中将相关预览分组在一起的名称。

  • size:使用 Size 对象施加的人工尺寸约束。

  • textScaleFactor:自定义字体缩放。

  • wrapper:将预览 widget 包裹在特定 widget 树中的函数(例如通过 InheritedWidget 向 widget 树注入应用状态)。

  • theme:一个返回 PreviewThemeData 实例的函数,用于提供自定义和通用的主题数据,并支持顺序主题分层来满足复杂的矩阵测试需求。

  • 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();
    // Creates a PreviewBuilder that can be used to modify
    // the preview contents.
    final builder = originalPreview.toBuilder();
    builder
      ..name = 'Transformed - ${originalPreview.name}'
      ..theme = _themeBuilder;

    // Returns 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 预览器时应注意以下限制:

  • 公共回调名称:提供给预览注解的所有回调参数必须是 public 且为 constant。预览器的代码生成实现需要如此才能正常工作。

  • 不支持的 API:不支持原生插件以及 dart:iodart:ffi 库中的任何 API。因为 widget 预览器基于 Flutter Web 构建,无法访问底层原生平台 API。在你的浏览器中 Web 插件可能可用,但不保证在其他环境(例如嵌入 IDE 时)也能工作。

    dart:iodart:ffi 有传递依赖的 widget 可以正常加载,但调用这些库中的 API 时会抛出异常。

    有关如何在面向多平台时整洁地支持平台特定库,请参阅 Dart 条件导入文档

  • 资源路径:使用 dart:uifromAsset API 加载资源时,必须使用 基于 package 的路径,而非直接本地路径。这样资源才能在预览器的 Web 环境中正确定位和加载。例如使用 'packages/my_package_name/assets/my_image.png',而不是 'assets/my_image.png'

    在预览 Web widget 时,预览器会自动从主项目的 web/ 目录同步资源,并应用自定义主题或 index.html 的自定义设置。

  • 无约束 widget:无约束 widget 会自动约束为约为 widget 预览器高度和宽度的一半。此行为未来可能变更,因此尽可能使用 size 参数施加约束。

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