跳到主內容

Flutter 小部件預覽器

瞭解如何使用 Flutter Widget 預覽器即時檢視 Widget 渲染效果,無需執行整個應用程式。

在本指南中,你將學習如何使用 Flutter Widget 預覽器。

概述

#

藉助 Flutter Widget 預覽器,你可以在 Chrome 瀏覽器中獨立於完整應用程式即時檢視 Widget 的渲染效果。要啟動預覽器、在其中顯示 Widget 以及自定義預覽,請參閱以下章節。

開啟預覽器

#

IDE

#

從 Flutter 3.38 開始,Android Studio、IntelliJ 和 Visual Studio Code 會在啟動時自動執行 Flutter Widget 預覽器。

Android Studio 和 IntelliJ

#

要在 Android Studio 或 IntelliJ 中開啟 Widget 預覽器,請開啟側邊欄中的“Flutter Widget Preview”標籤頁。

Flutter Widget Previewer in Android Studio

Visual Studio Code

#

要在 Visual Studio Code 中開啟 Widget 預覽器,請開啟側邊欄中的“Flutter Widget Preview”標籤頁。

Flutter Widget Previewer in Visual Studio Code

命令列

#

要啟動 Flutter Widget 預覽器,請導航至 Flutter 專案的根目錄,並在終端中執行以下命令。這將啟動一個本地伺服器,並在 Chrome 中開啟一個 Widget 預覽環境,該環境會根據專案更改自動更新。

shell
flutter widget-preview start

預覽 Widget

#

啟動預覽器後,要檢視某個 Widget,必須使用定義在 package:flutter/widget_previews.dart 中的 @Preview 註解。此註解可應用於:

  • 頂層函式:返回 WidgetWidgetBuilder 的函式。
  • 類中的靜態方法:返回 WidgetWidgetBuilder 的靜態方法。
  • 公共 Widget 建構函式和工廠方法:沒有必需引數的建構函式和工廠。

以下是如何使用 @Preview 註解預覽 Text Widget 的基本示例:

dart
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart'; // For Material widgets

@Preview(name: 'My Sample Text')
Widget mySampleText() {
  return const Text('Hello, World!');
}

Flutter Widget 預覽器中的 Widget 示例 每個預覽例項都提供了多種用於與預覽中的 Widget 進行互動的控制元件。從左到右依次為:

  • 放大: 放大預覽中的 Widget。

  • 縮小: 縮小預覽中的 Widget。

  • 重置縮放: 將 Widget 預覽恢復到預設縮放級別。

  • 切換淺色/深色模式: 在淺色和深色配色方案之間切換預覽主題。

  • 對單個預覽執行熱重啟: 僅重啟特定的 Widget 預覽,從而無需重啟整個應用程式即可快速應用更改。

如果全域性狀態已修改(例如,靜態初始化程式已更改),則可以透過環境右下角的按鈕指示整個 Widget 預覽器執行熱重啟。

按所選檔案過濾預覽

#

在 IDE 中檢視預覽時,Widget 預覽器被配置為根據當前選定的檔案過濾預覽集。

Filter by previews selected file in Flutter Widget Previewer

要停用此行為,請切換環境左下角的“按所選檔案過濾預覽 (Filter previews by selected file)”選項。

自定義預覽

#

@Preview 註解有多個引數,可用於自定義預覽:

  • name:預覽的描述性名稱。

  • group:用於在 Widget 預覽器中將相關預覽歸組在一起的名稱。

  • size:使用 Size 物件設定的人為尺寸約束。

  • textScaleFactor:自定義字型縮放比例。

  • wrapper:一個用於將預覽 Widget 包裝在特定 Widget 樹中的函式(例如,透過 InheritedWidget 將應用程式狀態注入到 Widget 樹中)。

  • theme:用於提供 Material 和 Cupertino 主題資料的函式。

  • brightness:初始主題亮度。

  • localizations:用於應用本地化配置的函式。

建立自定義預覽註解

#

為了減少定義具有通用屬性的預覽所需的樣板程式碼,可以擴充套件 Preview 註解類,以建立針對專案定製的自定義預覽註解。

以下是一個提供主題資料的自定義預覽註解示例:

dart
final class MyCustomPreview extends Preview {
  const MyCustomPreview({
    super.name,
    super.group,
    super.size,
    super.textScaleFactor,
    super.wrapper,
    super.brightness,
    super.localizations,
  }) : super(theme: MyCustomPreview.themeBuilder);

  static PreviewThemeData themeBuilder() {
    return PreviewThemeData(
      materialLight: ThemeData.light(),
      materialDark: ThemeData.dark(),
    );
  }
}

擴充套件 Preview 註解類還允許覆蓋 Preview.transform() 方法。此方法由 Widget 預覽器呼叫,可用於在執行時修改預覽,從而實現那些在 const 上下文中無法實現的預覽配置。

dart
final class TransformativePreview extends Preview {
  const TransformativePreview({
    super.name,
    super.group,
    super.size,
    super.textScaleFactor,
    super.wrapper,
    super.brightness,
    super.localizations,
  });

  // Note: this is no longer public or static as it's injected
  // at runtime when transform() is invoked.
  PreviewThemeData _themeBuilder() {
    return PreviewThemeData(
      materialLight: ThemeData.light(),
      materialDark: ThemeData.dark(),
    );
  }

  @override
  Preview transform() {
    final originalPreview = super.transform();
    // Create's a PreviewBuilder that can be used to modify
    // the preview contents.
    final builder = originalPreview.toBuilder();
    builder
      ..name = 'Transformed - ${originalPreview.name}'
      ..theme = _themeBuilder;

    // Return the updated Preview instance.
    return builder.toPreview();
  }
}

建立多個預覽配置

#

建立具有不同配置的多個預覽非常簡單,只需將多個 @Preview 註解應用於單個函式或建構函式即可。

dart
@Preview(
  group: 'Brightness',
  name: 'Example - light',
  brightness: Brightness.light,
)
@Preview(
  group: 'Brightness',
  name: 'Example - dark',
  brightness: Brightness.dark,
)
Widget buttonPreview() => const ButtonShowcase();

Multiple previews in Flutter Widget Previewer

為了簡化建立具有通用配置的多個預覽,可以擴充套件 MultiPreview 來建立自定義註解。以下 MultiPreview 建立了與前一個示例相同的兩個預覽:

dart
/// Creates light and dark mode previews.
final class MultiBrightnessPreview extends MultiPreview {
  const MultiBrightnessPreview();

  @override
  List<Preview> get previews => const [
        Preview(
          group: 'Brightness',
          name: 'Example - light',
          brightness: Brightness.light,
        ),
        Preview(
          group: 'Brightness',
          name: 'Example - dark',
          brightness: Brightness.dark,
        ),
      ];
}

@MultiBrightnessPreview()
Widget buttonPreview() => const ButtonShowcase();

Preview 一樣,MultiPreview 也提供了 MultiPreview.transform() 方法,用於在執行時對每個預覽執行轉換。

dart
/// Creates light and dark mode previews.
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'
        // Building names based on values provided to the annotation
        // isn't possible within a constant constructor. However,
        // there's no such restriction when building a Preview at
        // runtime.
        ..name = '$name - ${preview.brightness!.name}';
      return builder.toPreview();
    }).toList();
  }
}

@MultiBrightnessPreview(name: 'Example')
Widget buttonPreview() => const ButtonShowcase();

限制與侷限性

#

Flutter Widget 預覽器存在一些你應該注意的限制:

  • 公共回撥名稱:提供給預覽註解的所有回撥引數都必須是公共且常量(constant)的。這是預覽器程式碼生成實現能夠正常工作所必需的。

  • 不受支援的 API:不支援原生外掛以及來自 dart:iodart:ffi 庫的任何 API。這是因為 Widget 預覽器是使用 Flutter Web 構建的,它無法訪問底層的原生平臺 API。雖然在使用 Chrome 時 Web 外掛可能有效,但不能保證它們在其他環境(如嵌入 IDE 時)中也能正常工作。

    具有對 dart:iodart:ffi 傳遞依賴的 Widget 可以正常載入,但在呼叫這些庫中的任何 API 時都會丟擲異常。

    有關如何構造應用程式以在目標多個平臺時乾淨地支援特定於平臺的庫的詳細資訊,請參閱 Dart 關於條件匯入的文件

  • 資源路徑:使用 dart:ui 中的 fromAsset API 載入資源時,必須使用基於包(package-based)的路徑,而不是直接的本地路徑。這確保了資源能夠在預覽器的 Web 環境中被正確定位和載入。例如,使用 'packages/my_package_name/assets/my_image.png' 而不是 'assets/my_image.png'

  • 無約束 Widget:無約束的 Widget 會自動被限制在大約 Widget 預覽器高度和寬度的一半。此行為將來可能會改變,因此應儘可能使用 size 引數來應用約束。

  • IDE 中的多專案支援:Widget 預覽器目前僅支援顯示包含在單個專案或 Pub 工作區內的預覽。我們正在積極調研支援包含多個 Flutter 專案的 IDE 會話的方案 (#173550)。