flutter-add-widget-preview

flutter-add-widget-preview

熱門

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

2525星標
152分支
更新於 2026/6/18
SKILL.md
readonlyread-only
name
flutter-add-widget-preview
description

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

預覽 Flutter Widget

目錄

預覽指南

使用 Flutter Widget 預覽器,在脫離完整應用程式上下文的環境中即時渲染 Widget。

  • 目標元素:@Preview 註解套用於頂層函式、類別中的靜態方法,或無必要參數且回傳 WidgetWidgetBuilder 的公開 Widget 建構子/工廠方法。
  • 匯入: 務必匯入 package:flutter/widget_previews.dart 以使用預覽註解。
  • 自訂註解: 繼承 Preview 類別來建立自訂註解,以注入共用屬性(例如主題、包裝器)至多個 Widget。
  • 多重設定: 對單一目標套用多個 @Preview 註解以產生多個預覽實例。或者,繼承 MultiPreview 來封裝常見的多重預覽設定。
  • 執行時期轉換: 在自訂的 PreviewMultiPreview 類別中覆寫 transform() 方法,以在執行時期動態修改預覽設定(例如根據動態值產生名稱,這在 const 上下文中無法做到)。

處理限制

撰寫可預覽的 Widget 時,請遵守以下限制,因為 Widget 預覽器在網頁環境中執行:

  • 無原生 API: 請勿使用來自 dart:iodart:ffi 的原生外掛或 API。對 dart:iodart:ffi 有傳遞相依性的 Widget 在呼叫時會擲回例外。請使用條件匯入在預覽模式下模擬或繞過這些相依性。
  • 資源路徑: 對於透過 dart:uifromAsset API 載入的資源,請使用套件式路徑(例如 packages/my_package_name/assets/my_image.png 而非 assets/my_image.png)。
  • 公開回呼: 確保提供給預覽註解的所有回呼引數都是公開且常數的,以滿足程式碼生成需求。
  • 約束條件: 如果您的 Widget 未受約束,請在 @Preview 註解中使用 size 參數明確指定約束,因為預覽器預設會將其約束為大約半個視窗。

工作流程

建立 Widget 預覽

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

  • [ ] 匯入 package:flutter/widget_previews.dart
  • [ ] 識別有效的目標(頂層函式、靜態方法或無參數的公開建構子)。
  • [ ] 將 @Preview 註解套用於目標。
  • [ ] 根據需要設定預覽參數(namegroupsizethemebrightness 等)。
  • [ ] 如果將相同設定套用於多個 Widget,請將設定提取到繼承 Preview 的自訂類別中。

與預覽互動

請依照適當的條件工作流程來啟動並與 Widget 預覽器互動:

如果使用支援的 IDE(Android Studio、IntelliJ、VS Code 搭配 Flutter 3.38+):

  1. 啟動 IDE。Widget 預覽器會自動啟動。
  2. 在側邊欄中開啟「Flutter Widget Preview」分頁。
  3. 如果您想檢視目前作用中檔案以外的預覽,請切換左下角的「Filter previews by selected file」。

如果使用命令列:

  1. 導覽至 Flutter 專案的根目錄。
  2. 執行 flutter widget-preview start
  3. 檢視自動開啟的 Chrome 環境。

回饋迴圈:預覽迭代

  1. 修改 Widget 程式碼或預覽設定。
  2. 觀察 Widget 預覽器中的自動更新。
  3. 如果修改了全域狀態(例如靜態初始化器):點擊右下角的全域熱重新啟動按鈕。
  4. 如果只需要重設本機 Widget 狀態:點擊特定預覽卡片上的個別熱重新啟動按鈕。
  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')));