mcpField
F
flutter-add-widget-preview

flutter-add-widget-preview

github.com/flutter開発3.1k37.2k 回インストール2 日前に更新
リポジトリ

flutter-add-widget-preview について

Flutter Widget Previewer でウィジェットを単独表示し、サイズやテーマなどを設定するためのスキルです。アノテーション、カスタムプレビュー、Web 実行環境の制約を扱います。

主な機能

  1. 01@Preview によるウィジェット指定
  2. 02サイズ・テーマ・ローカライズ設定
  3. 03カスタム・複数構成プレビュー
  4. 04Web プレビューの制約への対応

活用シーン

  1. 01アプリ全体から切り離して確認
  2. 02テーマや文字サイズの比較
  3. 03複数ウィジェットで設定を再利用

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/flutter/agent-plugins --skill flutter-add-widget-preview

インストール後、Claude Code で/flutter-add-widget-previewを入力して読み込まれたか確認します。

詳細

Flutter ウィジェットのプレビュー

目次

プレビューのガイドライン

Flutter Widget Previewer を使用すると、アプリケーション全体のコンテキストから切り離して、ウィジェットをリアルタイムでレンダリングできます。

  • 対象要素: @Preview アノテーションは、トップレベル関数、クラス内の静的メソッド、または必須引数がなく、Widget または WidgetBuilder を返すパブリックなウィジェットのコンストラクター/ファクトリーに適用します。
  • インポート: プレビュー用アノテーションを使用するには、必ず package:flutter/widget_previews.dart をインポートします。
  • カスタムアノテーション: Preview クラスを拡張して、複数のウィジェットに共通のプロパティ(カスタムテーマ、ラッパー、サイズなど)を適用するカスタムアノテーションを作成します。
  • 複数の構成: 1 つの対象に複数の @Preview アノテーションを適用すると、プレビューインスタンスを複数生成できます。または、MultiPreview を拡張して、共通の複数プレビュー構成をカプセル化できます。
  • 実行時の変換: カスタムの Preview クラスまたは MultiPreview クラスで transform() メソッドをオーバーライドすると、実行時にプレビュー構成を動的に変更できます(たとえば、const コンストラクターでは不可能な、パラメーターに基づいた動的な名前の生成など)。

プレビューのパラメーター

@Preview アノテーションでは、プレビュー環境を調整するために次のパラメーターを指定できます。

  • name:プレビューインスタンスを説明する名前です。
  • group:プレビューアー内で関連するプレビューをグループ化するための名前です。
  • size:Size オブジェクトを使用して指定する人工的な制約です。
  • textScaleFactor:カスタムのフォントスケール係数です。
  • wrapper:プレビュー対象のウィジェットを特定のウィジェットツリーでラップする関数です(状態や継承ウィジェットの注入など)。
  • theme:PreviewThemeData のサブクラスのインスタンスを返す関数です。テーマを順番に重ねる機能を使って、カスタムのテーマを提供します。
  • brightness:初期テーマの Brightness です(Brightness.light または Brightness.dark)。
  • localizations:ローカライズ設定を適用する関数です。

制限事項への対応

Widget Previewer はウェブ環境で実行されるため、プレビュー可能なウィジェットを作成する際は次の制約を守ってください。

  • ネイティブ API: ネイティブプラグインや dart:io、dart:ffi の API は使用しないでください。dart:io または dart:ffi に推移的に依存するウィジェットは、呼び出し時に例外をスローします。条件付きインポートまたはラッパーを使用して、プレビューモードではこれらをモックするか回避してください。
  • アセットパス: dart:ui の fromAsset API で読み込むアセットには、パッケージベースのパスを使用してください(例:assets/my_image.png ではなく packages/my_package_name/assets/my_image.png)。
  • パブリックなコールバック: コード生成の要件を満たすため、プレビューアノテーションに渡すすべてのコールバック引数がパブリックで定数であることを確認してください。
  • 制約: ウィジェットに制約がない場合は、@Preview アノテーションの size パラメーターを使って明示的な制約を適用してください。制約のないウィジェットは、デフォルトでビューポートのおよそ半分のサイズになります。
  • ビルドキャッシュ: プレビューアーは、プロジェクトルートにある .widget_preview/ フォルダーにビルドをキャッシュします。

ワークフロー

ウィジェットプレビューの作成

新しいウィジェットプレビューを実装するときは、このチェックリストをコピーして進捗を記録してください。

  • package:flutter/widget_previews.dart をインポートします。
  • 有効な対象(トップレベル関数、静的メソッド、またはパラメーターのないパブリックコンストラクター)を特定します。
  • 対象に @Preview アノテーションを適用します。
  • 必要に応じてプレビューのパラメーター(name、group、size、textScaleFactor、wrapper、theme、brightness、localizations)を設定します。
  • 複数のウィジェットに同じ構成を適用する場合は、その構成を Preview または MultiPreview を拡張したカスタムクラスに切り出します。
プレビューの操作

Widget Previewer を起動して操作するには、適切な条件分岐ワークフローに従います。

サポート対象の IDE(Android Studio、IntelliJ、Flutter 3.47+ を使用した VS Code)を使用する場合:

  1. IDE を起動します。Widget Previewer が自動的に起動します。
  2. サイドバーの「Flutter Widget Preview」タブを開きます。
  3. 現在アクティブなファイル以外のプレビューを表示する場合は、左下にある「Filter previews by selected file」を切り替えます。

コマンドラインを使用する場合:

  1. Flutter プロジェクトのルートディレクトリに移動します。
  2. flutter widget-preview start を実行します。
  3. 自動的に開く Chrome 環境を確認します。

フィードバックループ:プレビューの反復

  1. ウィジェットのコードまたはプレビュー設定を変更します。
  2. Widget Previewer で自動更新を確認します。
  3. グローバル状態(静的初期化子など)を変更した場合:右下にあるグローバルなホットリスタートボタンをクリックします。
  4. ローカルウィジェットの状態のみをリセットする場合:対象のプレビューカードにある個別のホットリスタートボタンをクリックします。
  5. IDE/CLI コンソールでエラーを確認し、修正して、繰り返します。

例

基本的なプレビュー
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart';

@Preview(name: 'Submit Button', group: 'Form Controls')
Widget submitButtonPreview() => const ElevatedButton(
      onPressed: null,
      child: Text('Submit'),
    );
PreviewThemeData と変換を使用したカスタムプレビュー
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart';

final class ThemedPreview extends Preview {
  const ThemedPreview({
    super.name,
    super.group,
    super.brightness,
  });

  PreviewThemeData _themeBuilder() =>
      CustomThemeData(brightness: brightness);

  @override
  Preview transform() {
    final originalPreview = super.transform();
    final themeVariant = switch (brightness) {
      null => 'Responsive',
      final b => b.name,
    };

    final builder = originalPreview.toBuilder();
    final baseName = originalPreview.name ?? 'Preview';
    builder
      ..name = '$baseName [$themeVariant]'
      ..theme = _themeBuilder;

    return builder.build();
  }
}

final class CustomThemeData extends PreviewThemeData {
  const CustomThemeData({this.brightness});

  final Brightness? brightness;

  @override
  Widget apply(BuildContext context, Widget child) {
    return Theme(
      data: ThemeData.from(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.teal,
          brightness: brightness ??
              MediaQuery.maybePlatformBrightnessOf(context) ??
              Brightness.light,
        ),
      ),
      child: child,
    );
  }
}

@ThemedPreview(name: 'Themed Action Button', brightness: Brightness.dark)
Widget themedButtonPreview() => const ElevatedButton(
      onPressed: null,
      child: Text('Action'),
    );
MultiPreview の実装
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart';

/// Creates light and dark mode previews automatically.
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'),
      ),
    );

コメント