SKILL.md
readonlyread-only
name
flutter-add-widget-preview
description
使用 previews.dart 系統為專案新增互動式 Widget 預覽。在建立新的 UI 元件或更新現有畫面時使用,以確保一致的設計與互動測試。
預覽 Flutter Widget
目錄
預覽指南
使用 Flutter Widget 預覽器,在脫離完整應用程式上下文的環境中即時渲染 Widget。
- 目標元素: 將
@Preview註解套用於頂層函式、類別中的靜態方法,或無必要參數且回傳Widget或WidgetBuilder的公開 Widget 建構子/工廠方法。 - 匯入: 務必匯入
package:flutter/widget_previews.dart以使用預覽註解。 - 自訂註解: 繼承
Preview類別來建立自訂註解,以注入共用屬性(例如主題、包裝器)至多個 Widget。 - 多重設定: 對單一目標套用多個
@Preview註解以產生多個預覽實例。或者,繼承MultiPreview來封裝常見的多重預覽設定。 - 執行時期轉換: 在自訂的
Preview或MultiPreview類別中覆寫transform()方法,以在執行時期動態修改預覽設定(例如根據動態值產生名稱,這在const上下文中無法做到)。
處理限制
撰寫可預覽的 Widget 時,請遵守以下限制,因為 Widget 預覽器在網頁環境中執行:
- 無原生 API: 請勿使用來自
dart:io或dart:ffi的原生外掛或 API。對dart:io或dart:ffi有傳遞相依性的 Widget 在呼叫時會擲回例外。請使用條件匯入在預覽模式下模擬或繞過這些相依性。 - 資源路徑: 對於透過
dart:ui的fromAssetAPI 載入的資源,請使用套件式路徑(例如packages/my_package_name/assets/my_image.png而非assets/my_image.png)。 - 公開回呼: 確保提供給預覽註解的所有回呼引數都是公開且常數的,以滿足程式碼生成需求。
- 約束條件: 如果您的 Widget 未受約束,請在
@Preview註解中使用size參數明確指定約束,因為預覽器預設會將其約束為大約半個視窗。
工作流程
建立 Widget 預覽
在實作新的 Widget 預覽時,請複製並追蹤以下檢查清單:
- [ ] 匯入
package:flutter/widget_previews.dart。 - [ ] 識別有效的目標(頂層函式、靜態方法或無參數的公開建構子)。
- [ ] 將
@Preview註解套用於目標。 - [ ] 根據需要設定預覽參數(
name、group、size、theme、brightness等)。 - [ ] 如果將相同設定套用於多個 Widget,請將設定提取到繼承
Preview的自訂類別中。
與預覽互動
請依照適當的條件工作流程來啟動並與 Widget 預覽器互動:
如果使用支援的 IDE(Android Studio、IntelliJ、VS Code 搭配 Flutter 3.38+):
- 啟動 IDE。Widget 預覽器會自動啟動。
- 在側邊欄中開啟「Flutter Widget Preview」分頁。
- 如果您想檢視目前作用中檔案以外的預覽,請切換左下角的「Filter previews by selected file」。
如果使用命令列:
- 導覽至 Flutter 專案的根目錄。
- 執行
flutter widget-preview start。 - 檢視自動開啟的 Chrome 環境。
回饋迴圈:預覽迭代
- 修改 Widget 程式碼或預覽設定。
- 觀察 Widget 預覽器中的自動更新。
- 如果修改了全域狀態(例如靜態初始化器):點擊右下角的全域熱重新啟動按鈕。
- 如果只需要重設本機 Widget 狀態:點擊特定預覽卡片上的個別熱重新啟動按鈕。
- 在 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')));






