关于 preview-widget
用于在 Flutter Widget Previewer 中单独渲染组件的技能,适合需要快速迭代单个 Widget 外观的开发者。它提供预览服务器管理、无头截图和现有预览扫描脚本,无需启动完整应用。
主要功能
- 01检查 Flutter 版本是否满足要求
- 02启动并停止预览服务器
- 03通过无头 Chrome 截取预览
- 04扫描项目中的预览注解
- 05支持单组件多变体预览
适用场景
- 01调整单个组件的视觉样式
- 02比较浅色、深色或平板尺寸变体
- 03在完整应用启动前检查组件渲染
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ chunkytofustudios/ flutter-skills --skill preview-widget安装后在 Claude Code 中输入/preview-widget确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/multi_preview.dart.templatepreview_wrapper.dart.templatereferences/limitations.mdpreview_annotations.mdwrapper_patterns.mdscripts/check_preview_support.shlist_previews.shscreenshot_preview.shstart_preview.shtests/check.batshelp.batshelpers.bashlist_previews.batsscreenshot.batsstart.batstests/stubs/chromeflutter详细介绍
独立预览 Flutter widget
在 Flutter 的 Widget Previewer 中渲染单个 widget,这样你和用户就可以对它反复调整——修改设计、查看浅色/深色变体、调整平板尺寸等,无需启动完整应用。Agent 会在无头模式下为实时预览截图;用户也可以选择在自己的 Chrome 中打开同一个 localhost URL,实时查看。
适用场景
以下三项必须全部满足——任意一项不满足,这项技能就不适用:
- 项目是 Flutter 项目(当前工作目录中有
pubspec.yaml,且存在lib/)。 - Flutter ≥ 3.35 已在 PATH 中(或通过
fvm flutter调用)。Widget Previewer 在稳定版中仍属实验性功能,但已足以用于设计;更早版本的 Flutter 没有此功能。 - 用户想要调整的是单个 widget,而不是整个屏幕或端到端流程。完整屏幕的视觉设计应使用
design-polish+android-emulator。
环境准备(调用者的机器)
| 要求 | 满足方式 |
|---|---|
| Flutter ≥ 3.35 | 运行 bash scripts/check_preview_support.sh——如果版本过旧,会明确报错并提示升级。使用 FLUTTER_BIN=… 覆盖 Flutter 二进制文件(默认:flutter,找不到时回退到 fvm flutter)。 |
| Google Chrome / Chromium | screenshot_preview.sh 会使用它在无头模式下截取预览。macOS 会自动找到 /Applications/Google Chrome.app;在 Linux 上,只要 PATH 中有 google-chrome、chromium 或 chromium-browser 即可。使用 CHROME_BIN=/path/to/chrome 覆盖。 |
bash 3.2+(macOS 默认)、awk、grep、find | 标准工具。无需 Python 或 Node。 |
无需为每台机器单独配置身份验证、账号或令牌——所有操作都在 localhost 上运行。
随附脚本
scripts/check_preview_support.sh— 验证 Flutter ≥ 3.35,并输出解析后的二进制文件路径和版本。退出码 0 = 已就绪,退出码 1 = 版本过旧,退出码 127 = 未找到 Flutter。scripts/start_preview.sh— 在后台启动flutter widget-preview start,从日志中解析 URL,并写入一个小型 JSON 状态文件。支持幂等操作:重复运行会返回现有 URL。使用--stop关闭服务。默认状态目录:./.preview-widget/(使用PREVIEW_WIDGET_STATE_DIR覆盖)。scripts/screenshot_preview.sh— 使用无头 Chrome 截取运行中的预览器。参数:--out <path>、--size WxH(默认1600x2400)、--wait <ms>(虚拟时间预算,默认10000)、--url <url>(覆盖状态文件中的 URL)。在标准输出中打印输出文件的绝对路径。scripts/list_previews.sh— 在lib/中搜索@Preview(...)、@<Custom>Preview(...)和extends (Multi)?Preview定义,避免重复添加已有定义。
这四个脚本都支持 --help。自行尝试参数前,请先查看帮助。
每个 Flutter 项目的一次性设置
在为任何 widget 添加注解之前,项目需要一个预览包装器——一个顶层的 Widget previewWrapper(Widget child) 函数,用于重建真实 widget 所依赖的全局祖先链(theme cubit / ProviderScope / MaterialApp / 本地化 / MediaQuery 覆盖等)。对于任何非简单应用,仅靠预览器的 theme: 参数并不足够:如果 widget 在其树中没有 cubit,直接读取 BlocBuilder<ThemeCubit, …> 或 Theme.of(context) 的 widget 会抛出异常或渲染错误。
步骤:
- 阅读项目的
lib/main.dart和lib/app.dart(或等效根文件)。找出所有包裹MaterialApp的内容——MultiBlocProvider、ProviderScope、MultiRepositoryProvider、自定义InheritedWidget、MediaQuery覆盖以及 locale/router 配置。 - 将
assets/preview_wrapper.dart.template复制到lib/preview_wrapper.dart。 - 填入 imports 和 provider 列表,使其与真实根树相符,并为需要运行时状态的 provider 提供桩实现(例如,
UxCubit使用默认的DeviceType.mobile;repository cubit 使用虚假/空状态)。包装器是一个编译期常量 tearoff,因此所有回调都必须是顶层公开函数。 - 配方请参阅
references/wrapper_patterns.md(BLoC、Riverpod、GoRouter、Localizations、MediaQuery、字体)。
如果不确定 widget 实际需要哪些 provider,请在 widget 主体中搜索 Theme.of、BlocBuilder<、context.watch<、context.read<、ProviderScope.containerOf、MediaQuery.of、Localizations.of、GoRouter.of——这些都是承载依赖的祖先节点。
单个 widget 的工作流
1. 选择注解目标
@Preview 和 @MultiPreview 可用于:
- 返回
Widget或WidgetBuilder的顶层函数 - 类的公开静态方法
- 没有必需参数的公开 widget 构造函数 / 工厂方法
传给注解的所有值都必须是编译期常量,任何回调(wrapper:、theme:、localizations:)都必须是公开的顶层(或静态)函数——预览器不接受闭包捕获私有状态。
2. 添加注解
使用项目的包装器进行单个预览:
import 'package:flutter/material.dart';
import 'package:flutter/widget_previews.dart';
import '../preview_wrapper.dart';
@Preview(name: 'MyButton — default', wrapper: previewWrapper)
Widget myButtonPreview() => const MyButton();
通过内置的 assets/multi_preview.dart.template → lib/preview_variants.dart 创建多预览矩阵(浅色 + 深色 + 平板):
@StandardPreview()
Widget myButtonPreview() => const MyButton();
完整的 @Preview 参数列表和目标限制规则,请参阅 references/preview_annotations.md。
3. 启动预览服务
bash scripts/start_preview.sh
# → Preview: http://localhost:51530
操作具有幂等性——如果服务已运行,再次执行只会重新打印 URL。.preview-widget/server.json 中的状态文件包含 { "url", "pid", "log" }。将 .preview-widget/ 添加到 .gitignore。
4. 为运行中的预览截图
bash scripts/screenshot_preview.sh
# → /tmp/preview-widget/preview-001.png
然后用 Read 查看 PNG 中的实时预览。默认截图视口为 1600×2400(可容纳约 3 个堆叠的预览平铺);传入 --size 1920x4000 可截取更高画面,或传入 --out /tmp/foo.png 控制路径。如果首次绘制结果为空白,请增大 --wait 20000(20 秒)——这是 canvas/Skia 的启动等待时间。
5. 迭代
edit Dart → sleep 2 → screenshot → read → reason → repeat
预览器会在保存文件时自动热重启,因此无需手动重启。等待 2 秒可让热重启完成,然后再截取下一张截图。
6. 每次操作结束时始终打印 URL
每条涉及预览器的助手消息都必须以这一行结束:
Preview: http://localhost:<port>
这样人类可以随时进入查看,也可以稍后再回来——服务会一直运行,直到执行 start_preview.sh --stop。
7. 完成后清理
bash scripts/start_preview.sh --stop
此操作会终止服务并移除状态文件。此步骤可选——在会话间让服务继续运行也没问题。
查找现有预览
添加新的 @Preview 之前,先查看项目中已有的预览:
bash scripts/list_previews.sh
# lib/widgets/foo.dart:42 @Preview(name: 'Foo - light', wrapper: previewWrapper)
# lib/widgets/bar.dart:17 @StandardPreview()
# lib/preview_variants.dart:9 final class StandardPreview extends MultiPreview {
传入明确的根目录以限定范围:bash scripts/list_previews.sh lib/src/widgets。
注意事项
- 包装器是关键所在。 如果没有使用
wrapper: previewWrapper,简单的@Preview在任何会在 widget 内容中读取 providers/cubits 的应用里都会崩溃或渲染错误。如果 widget 使用了BlocBuilder<ThemeCubit>,而包装器没有在它上方提供ThemeCubit,预览就会抛出ProviderNotFoundException——这不是预览器的 bug。 size:限制的是 widget,而不是画布。 未受约束且没有设置size:的 widget 会自动缩放到预览器画布的大约一半大小,这对于单个组件来说通常不是理想效果。按钮大小的组件可传入size: Size(360, 80),平板布局可传入size: Size(1024, 1366)。- 所有注解值都必须是 const。 包括
wrapper:、theme:和localizations:回调——它们必须是公开的顶层(或静态)函数 tear-off,不能是 lambda。任何捕获外部内容的函数都会导致编译失败。 - 预览 widget 中的资源路径必须是
packages/<name>/...。在实际应用中,使用Image.asset('assets/foo.png')的 widget 可以正常工作,但在预览器中会出错;要在两种环境中都正常工作,请改用Image.asset('packages/<your_pkg>/assets/foo.png')。 - 不能使用
dart:io、dart:ffi或原生插件。 预览器运行在 Chrome 中——任何访问File、Platform、原生通道或平台插件的代码,都会在预览尝试渲染时抛出异常。条件导入(如if (dart.library.html)等)可以避开此问题。 - 首次绘制较慢。 Flutter web 首次加载预览时需要引导 JS/Skia,通常需要 5–10 秒,磁盘较慢时会更久。默认的
--wait 10000通常足够;在认定截图出错之前,可以调高此值(例如--wait 20000)。 - 只能截取整页。 预览器不会公开每个预览的 URL,因此
screenshot_preview.sh会截取整个页面(所有预览卡片依次排列)。Agent 会根据卡片上方显示的name:标签判断每张卡片的内容。如果只想显示一个预览,可以暂时注释掉其他预览;也可以使用较大的--size,并依靠group:参数将相关变体排在一起。 - 热重启会自动进行,但并非即时完成。 编辑
.dart文件后,等待预览器约 1–2 秒再截图。修改包装器本身需要更长时间;如果sleep 5后截图仍显示旧状态,请重启服务(先运行--stop,再重新运行)。 - 每个服务只支持一个项目。 截至 Flutter 3.35,IDE 工作流中的预览器不支持多项目工作区;CLI 服务只识别当前项目。如果项目使用 Pub 工作区,请从工作区根目录运行服务。
flutter widget-preview是实验性的。 不同 Flutter 版本中的 API 和 CLI 标志可能会变动。如果此技能使用的标志不再有效,请检查flutter widget-preview --help,并与start_preview.sh传入的参数进行对照。
快速参考
# 0. (one-time) Verify Flutter is recent enough.
bash skills/preview-widget/scripts/check_preview_support.sh
# 1. (one-time per project) Copy the wrapper template, fill in providers.
cp skills/preview-widget/assets/preview_wrapper.dart.template lib/preview_wrapper.dart
# …edit lib/preview_wrapper.dart, then add `.preview-widget/` to .gitignore
# 2. Annotate the widget (in its own file or alongside it).
# @Preview(name: 'MyWidget', wrapper: previewWrapper)
# Widget myWidgetPreview() => const MyWidget();
# 3. Start server, capture URL.
bash skills/preview-widget/scripts/start_preview.sh
# → http://localhost:51530
# 4. Iterate: edit → sleep 2 → screenshot → read → repeat.
bash skills/preview-widget/scripts/screenshot_preview.sh
# 5. Stop when done.
bash skills/preview-widget/scripts/start_preview.sh --stop
每次助手回合中只要使用过预览器,都要以 Preview: http://localhost:<port> 结尾,方便用户跟进。