跳转至正文

Flutter Widget 预览器

学习如何使用 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 Preview 环境。

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

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 预览器热重启。

按所选文件筛选预览

#

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

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

  • 不支持的 API:不支持原生插件以及 dart:iodart:ffi 库中的任何 API。因为 widget 预览器基于 Flutter Web 构建,无法访问底层原生平台 API。在 Chrome 中 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'

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

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