SKILL.md
只读
名称
flutter-add-widget-preview
描述
使用 `previews.dart` 系统为项目添加交互式 Widget 预览。适用于创建新 UI 组件或更新现有界面时,以确保设计一致性并进行交互式测试。
预览 Flutter Widget
目录
预览指南
使用 Flutter Widget Previewer 实时渲染 Widget,脱离完整的应用上下文独立运行。
- 目标元素: 将
@Preview注解应用于顶级函数、类中的静态方法,或没有必填参数且返回Widget或WidgetBuilder的公有 Widget 构造函数/工厂构造函数。 - 导入模块: 务必导入
package:flutter/widget_previews.dart以使用预览注解。 - 自定义注解: 继承
Preview类创建自定义注解,以便在多个 Widget 间注入通用属性(如主题、包装器等)。 - 多重配置: 在同一个目标上添加多个
@Preview注解,生成多个预览实例;或者继承MultiPreview来封装常用的多预览配置。 - 运行时转换: 重写自定义
Preview或MultiPreview类中的transform()方法,以在运行时动态修改预览配置(例如根据动态值生成名称,这在const上下文中是无法实现的)。
限制与注意事项
由于 Widget Previewer 在 Web 环境中运行,编写可预览的 Widget 时需遵循以下限制:
- 禁止原生 API: 切勿使用来自
dart:io或dart:ffi的原生插件或 API。间接依赖dart:io或dart:ffi的 Widget 在调用时会抛出异常。请在预览模式下使用条件导入(conditional imports)进行 Mock 或绕过它们。 - 资源路径: 对于通过
dart:ui的fromAssetAPI 加载的资源,需使用基于 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注解。 - [ ] 根据需要配置预览参数(如
name、group、size、theme、brightness等)。 - [ ] 若需将相同的配置应用于多个 Widget,可将配置提取为继承自
Preview的自定义类。
交互与调试预览
根据你使用的开发环境,选择相应的流程启动并体验 Widget Previewer:
使用受支持的 IDE(Android Studio、IntelliJ、VS Code,且 Flutter 3.38+):
- 启动 IDE,Widget Previewer 会自动启动。
- 打开侧边栏中的 "Flutter Widget Preview" 标签页。
- 如果想查看当前激活文件之外的预览,可切换左下角的“仅按选中文件过滤预览 (Filter previews by selected file)”选项。
使用命令行:
- 切换至 Flutter 项目的根目录。
- 运行
flutter widget-preview start。 - 在自动打开的 Chrome 浏览器环境中查看预览。
反馈循环:预览迭代
- 修改 Widget 代码或预览配置。
- 在 Widget Previewer 中实时观察自动更新。
- 如果修改了全局状态(例如静态初始化代码):点击右下角的全局 Hot Restart 按钮。
- 如果仅需重置局部 Widget 状态:点击对应预览卡片上的独立 Hot Restart 按钮。
- 在 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')));






