flutter-add-widget-preview

flutter-add-widget-preview

热门

使用 previews.dart 系统为项目添加交互式 widget 预览。在创建新的 UI 组件或更新现有屏幕时使用,以确保一致的设计和交互式测试。

2525Star
152Fork
更新于 2026/6/18
SKILL.md
只读
名称
flutter-add-widget-preview
描述

使用 previews.dart 系统为项目添加交互式 widget 预览。在创建新的 UI 组件或更新现有屏幕时使用,以确保一致的设计和交互式测试。

预览 Flutter Widget

目录

预览指南

使用 Flutter Widget 预览器实时渲染 widget,与完整的应用程序上下文隔离。

  • 目标元素:@Preview 注解应用于顶级函数、类中的静态方法,或没有必需参数且返回 WidgetWidgetBuilder 的公共 widget 构造函数/工厂。
  • 导入: 始终导入 package:flutter/widget_previews.dart 以访问预览注解。
  • 自定义注解: 扩展 Preview 类以创建自定义注解,为多个 widget 注入公共属性(例如主题、包装器)。
  • 多个配置: 对单个目标应用多个 @Preview 注解以生成多个预览实例。或者,扩展 MultiPreview 以封装常见的多预览配置。
  • 运行时转换: 在自定义 PreviewMultiPreview 类中重写 transform() 方法,以在运行时动态修改预览配置(例如,基于动态值生成名称,这在 const 上下文中是不可能的)。

处理限制

在编写可预览的 widget 时,请遵守以下约束,因为 Widget 预览器在 Web 环境中运行:

  • 无原生 API: 不要使用来自 dart:iodart:ffi 的原生插件或 API。具有对 dart:iodart:ffi 传递依赖的 widget 在调用时会抛出异常。使用条件导入在预览模式下模拟或绕过这些依赖。
  • 资源路径: 对于通过 dart:uifromAsset API 加载的资源,使用基于包的路径(例如 packages/my_package_name/assets/my_image.png 而不是 assets/my_image.png)。
  • 公共回调: 确保提供给预览注解的所有回调参数都是公共且常量的,以满足代码生成要求。
  • 约束: 如果您的 widget 是无约束的,请使用 @Preview 注解中的 size 参数应用显式约束,因为预览器默认将它们约束为大约视口的一半。

工作流程

创建 Widget 预览

在实现新的 widget 预览时,复制并跟踪此检查清单:

  • [ ] 导入 package:flutter/widget_previews.dart
  • [ ] 确定一个有效的目标(顶级函数、静态方法或无参数的公共构造函数)。
  • [ ] 将 @Preview 注解应用于目标。
  • [ ] 根据需要配置预览参数(namegroupsizethemebrightness 等)。
  • [ ] 如果要将相同的配置应用于多个 widget,请将配置提取到扩展 Preview 的自定义类中。

与预览交互

按照适当的条件工作流程启动并与 Widget 预览器交互:

如果使用支持的 IDE(Android Studio、IntelliJ、VS Code 与 Flutter 3.38+):

  1. 启动 IDE。Widget 预览器会自动启动。
  2. 在侧边栏中打开“Flutter Widget Preview”选项卡。
  3. 如果您想查看当前活动文件之外的预览,请切换左下角的“Filter previews by selected file”。

如果使用命令行:

  1. 导航到 Flutter 项目的根目录。
  2. 运行 flutter widget-preview start
  3. 查看自动打开的 Chrome 环境。

反馈循环:预览迭代

  1. 修改 widget 代码或预览配置。
  2. 观察 Widget 预览器中的自动更新。
  3. 如果修改了全局状态(例如静态初始化器):单击右下角的全局热重启按钮。
  4. 如果只需要重置本地 widget 状态:单击特定预览卡片上的单个热重启按钮。
  5. 检查 IDE/CLI 控制台中的错误 -> 修复 -> 重复。

示例

基本预览

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

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

带运行时转换的自定义预览

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

final class TransformativePreview extends Preview {
  const TransformativePreview({
    super.name,
    super.group,
  });

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

  @override
  Preview transform() {
    final originalPreview = super.transform();
    final builder = originalPreview.toBuilder();
    
    builder
      ..name = 'Transformed - ${originalPreview.name}'
      ..theme = _themeBuilder;

    return builder.toPreview();
  }
}

@TransformativePreview(name: 'Custom Themed Button')
Widget myButton() => const ElevatedButton(onPressed: null, child: Text('Click'));

MultiPreview 实现

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

/// 自动创建亮色和暗色模式预览。
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'
        ..name = '$name - ${preview.brightness!.name}';
      return builder.toPreview();
    }).toList();
  }
}

@MultiBrightnessPreview(name: 'Primary Card')
Widget cardPreview() => const Card(child: Padding(padding: EdgeInsets.all(8.0), child: Text('Content')));