flutter-add-widget-preview

flutter-add-widget-preview

熱門

使用 previews.dart 系統為專案新增互動式 Widget 預覽功能。在建立新 UI 元件或更新現有畫面時使用,以確保設計一致性並進行互動測試。

2783星標
163分支
更新於 2026/8/5
SKILL.md
唯讀
名稱
flutter-add-widget-preview
描述

使用 previews.dart 系統為專案新增互動式 Widget 預覽功能。在建立新 UI 元件或更新現有畫面時使用,以確保設計一致性並進行互動測試。

預覽 Flutter Widget

目錄

預覽指引

使用 Flutter Widget Previewer 可以在獨立於完整應用程式上下文的環境中,即時轉譯(render)Widget。

  • 目標元素: 可將 @Preview 註解套用至頂層函式(top-level functions)、類別內的靜態方法,或是無需必填引數且回傳 WidgetWidgetBuilder 的公用 Widget 建構子/工廠建構子。
  • 匯入檔: 務必匯入 package:flutter/widget_previews.dart 以存取預覽註解。
  • 自訂註解: 繼承 Preview 類別來建立自訂註解,藉此跨多個 Widget 注入常用屬性(例如主題、包裝器 wrappers)。
  • 多重設定: 可對單一目標套用多個 @Preview 註解以產生多個預覽實例。此外,也可繼承 MultiPreview 來封裝常見的多重預覽設定。
  • 執行階段轉換: 在自訂的 PreviewMultiPreview 類別中覆寫 transform() 方法,即可在執行階段動態修改預覽設定(例如根據動態數值產生名稱,這在 const 上下文中是無法實現的)。

限制事項與處理方式

撰寫支援預覽的 Widget 時,請留意以下限制,因為 Widget Previewer 是在 Web 環境中執行:

  • 禁止使用原生 API: 請勿使用來自 dart:iodart:ffi 的原生外掛程式或 API。若 Widget 間接相依於 dart:iodart:ffi,在呼叫時將會拋出例外。請使用條件式匯入(conditional imports)在預覽模式下進行 Mock 或避開這些 API。
  • 資源路徑(Asset Paths): 透過 dart:uifromAsset API 載入資源時,請使用基於 Package 的路徑(例如:packages/my_package_name/assets/my_image.png,而非 assets/my_image.png)。
  • 公用回呼函式: 傳入預覽註解的所有回呼函式引數都必須是公用且為常數(constant),以滿足程式碼產生的需求。
  • 尺寸約束: 若你的 Widget 沒有設定約束,請在 @Preview 註解中使用 size 參數套用明確的約束,因為預覽器預設會將其限制在大約半個視埠(viewport)的大小。

工作流程

建立 Widget 預覽

實作新的 Widget 預覽時,請複製並追蹤以下檢查清單:

  • [ ] 匯入 package:flutter/widget_previews.dart
  • [ ] 確認合法的目標(頂層函式、靜態方法,或無引數的公用建構子)。
  • [ ] 對目標套用 @Preview 註解。
  • [ ] 依需求設定預覽參數(namegroupsizethemebrightness 等)。
  • [ ] 若要將相同設定套用到多個 Widget,請將設定抽離為繼承自 Preview 的自訂類別。

與預覽互動

請依照對應的條件工作流程來啟動並使用 Widget Previewer:

若使用受支援的 IDE(Android Studio、IntelliJ、包含 Flutter 3.38+ 的 VS Code):

  1. 啟動 IDE,Widget Previewer 會自動啟動。
  2. 開啟側邊欄的 "Flutter Widget Preview" 頁籤。
  3. 若想檢視目前開啟檔案以外的預覽,可切換左下角的 "Filter previews by selected file"(依選取檔案篩選預覽)。

若使用命令列(Command Line):

  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')));