flutter-add-widget-preview

flutter-add-widget-preview

热门

使用 `previews.dart` 系统为项目添加交互式 Widget 预览。适用于创建新 UI 组件或更新现有界面时,以确保设计一致性并进行交互式测试。

2783Star
163Fork
更新于 2026/8/5
SKILL.md
只读
名称
flutter-add-widget-preview
描述

使用 `previews.dart` 系统为项目添加交互式 Widget 预览。适用于创建新 UI 组件或更新现有界面时,以确保设计一致性并进行交互式测试。

预览 Flutter Widget

目录

预览指南

使用 Flutter Widget Previewer 实时渲染 Widget,脱离完整的应用上下文独立运行。

  • 目标元素:@Preview 注解应用于顶级函数、类中的静态方法,或没有必填参数且返回 WidgetWidgetBuilder 的公有 Widget 构造函数/工厂构造函数。
  • 导入模块: 务必导入 package:flutter/widget_previews.dart 以使用预览注解。
  • 自定义注解: 继承 Preview 类创建自定义注解,以便在多个 Widget 间注入通用属性(如主题、包装器等)。
  • 多重配置: 在同一个目标上添加多个 @Preview 注解,生成多个预览实例;或者继承 MultiPreview 来封装常用的多预览配置。
  • 运行时转换: 重写自定义 PreviewMultiPreview 类中的 transform() 方法,以在运行时动态修改预览配置(例如根据动态值生成名称,这在 const 上下文中是无法实现的)。

限制与注意事项

由于 Widget Previewer 在 Web 环境中运行,编写可预览的 Widget 时需遵循以下限制:

  • 禁止原生 API: 切勿使用来自 dart:iodart:ffi 的原生插件或 API。间接依赖 dart:iodart:ffi 的 Widget 在调用时会抛出异常。请在预览模式下使用条件导入(conditional imports)进行 Mock 或绕过它们。
  • 资源路径: 对于通过 dart:uifromAsset API 加载的资源,需使用基于 package 的路径(例如 packages/my_package_name/assets/my_image.png,而非 assets/my_image.png)。
  • 公有回调函数: 确保提供给预览注解的所有回调函数参数都是公有且为常量(const),以满足代码生成的要求。
  • 尺寸约束: 如果你的 Widget 本身未受尺寸约束,请在 @Preview 注解中使用 size 参数显式指定约束,因为预览器默认会将其约束为大约半个视口的大小。

工作流

创建 Widget 预览

实现新的 Widget 预览时,请对照并勾选此检查清单:

  • [ ] 导入 package:flutter/widget_previews.dart
  • [ ] 确定有效的目标(顶级函数、静态方法或无必填参数的公有构造函数)。
  • [ ] 为目标元素添加 @Preview 注解。
  • [ ] 根据需要配置预览参数(如 namegroupsizethemebrightness 等)。
  • [ ] 若需将相同的配置应用于多个 Widget,可将配置提取为继承自 Preview 的自定义类。

交互与调试预览

根据你使用的开发环境,选择相应的流程启动并体验 Widget Previewer:

使用受支持的 IDE(Android Studio、IntelliJ、VS Code,且 Flutter 3.38+):

  1. 启动 IDE,Widget Previewer 会自动启动。
  2. 打开侧边栏中的 "Flutter Widget Preview" 标签页。
  3. 如果想查看当前激活文件之外的预览,可切换左下角的“仅按选中文件过滤预览 (Filter previews by selected file)”选项。

使用命令行:

  1. 切换至 Flutter 项目的根目录。
  2. 运行 flutter widget-preview start
  3. 在自动打开的 Chrome 浏览器环境中查看预览。

反馈循环:预览迭代

  1. 修改 Widget 代码或预览配置。
  2. 在 Widget Previewer 中实时观察自动更新。
  3. 如果修改了全局状态(例如静态初始化代码):点击右下角的全局 Hot Restart 按钮。
  4. 如果仅需重置局部 Widget 状态:点击对应预览卡片上的独立 Hot Restart 按钮。
  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')));