flutter-add-widget-preview
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePreviewing Flutter Widgets
Flutter Widget 预览
Contents
目录
Preview Guidelines
预览指南
Use the Flutter Widget Previewer to render widgets in real-time, isolated from the full application context.
- Target Elements: Apply the annotation to top-level functions, static methods within a class, or public widget constructors/factories that have no required arguments and return a
@PrevieworWidget.WidgetBuilder - Imports: Always import to access the preview annotations.
package:flutter/widget_previews.dart - Custom Annotations: Extend the class to create custom annotations that inject common properties (e.g., themes, wrappers) across multiple widgets.
Preview - Multiple Configurations: Apply multiple annotations to a single target to generate multiple preview instances. Alternatively, extend
@Previewto encapsulate common multi-preview configurations.MultiPreview - Runtime Transformations: Override the method in custom
transform()orPreviewclasses to modify preview configurations dynamically at runtime (e.g., generating names based on dynamic values, which is impossible in aMultiPreviewcontext).const
使用Flutter Widget Previewer实时渲染组件,与完整应用环境隔离。
- 目标元素: 将注解应用于顶层函数、类中的静态方法,或无必填参数且返回
@Preview或Widget的公共组件构造函数/工厂方法。WidgetBuilder - 导入: 务必导入以获取预览注解。
package:flutter/widget_previews.dart - 自定义注解: 继承类创建自定义注解,为多个组件注入通用属性(如主题、包装器)。
Preview - 多配置: 为单个目标应用多个注解以生成多个预览实例。或者继承
@Preview封装通用多预览配置。MultiPreview - 运行时转换: 在自定义或
Preview类中重写MultiPreview方法,以在运行时动态修改预览配置(例如基于动态值生成名称,这在transform()上下文无法实现)。const
Handling Limitations
限制处理
Adhere to the following constraints when authoring previewable widgets, as the Widget Previewer runs in a web environment:
- No Native APIs: Do not use native plugins or APIs from or
dart:io. Widgets with transitive dependencies ondart:ffiwill throw exceptions upon invocation, anddart:iodependencies will fail to load. Use conditional imports to mock or bypass these in preview mode.dart:ffi - Asset Paths: Use package-based paths for assets loaded via
dart:uiAPIs (e.g.,fromAssetinstead ofpackages/my_package_name/assets/my_image.png).assets/my_image.png - Public Callbacks: Ensure all callback arguments provided to preview annotations are public and constant to satisfy code generation requirements.
- Constraints: Apply explicit constraints using the parameter in the
sizeannotation if your widget is unconstrained, as the previewer defaults to constraining them to approximately half the viewport.@Preview
编写可预览组件时需遵守以下约束,因为Widget Previewer运行在Web环境中:
- 禁止原生API: 不要使用原生插件或、
dart:io中的API。依赖dart:ffi的组件在调用时会抛出异常,依赖dart:io的组件将无法加载。使用条件导入在预览模式下模拟或绕过这些依赖。dart:ffi - 资源路径: 通过的
dart:uiAPI加载资源时,使用基于包的路径(例如fromAsset而非packages/my_package_name/assets/my_image.png)。assets/my_image.png - 公共回调: 确保提供给预览注解的所有回调参数是公共且常量,以满足代码生成要求。
- 约束设置: 如果你的组件无约束,使用注解中的
@Preview参数设置显式约束,因为预览器默认将其约束为约视口的一半大小。size
Workflows
工作流程
Creating a Widget Preview
创建组件预览
Copy and track this checklist when implementing a new widget preview:
- Import .
package:flutter/widget_previews.dart - Identify a valid target (top-level function, static method, or parameter-less public constructor).
- Apply the annotation to the target.
@Preview - Configure preview parameters (,
name,group,size,theme, etc.) as needed.brightness - If applying the same configuration to multiple widgets, extract the configuration into a custom class extending .
Preview
实现新组件预览时,遵循以下检查清单:
- 导入。
package:flutter/widget_previews.dart - 确定有效目标(顶层函数、静态方法或无参数公共构造函数)。
- 为目标应用注解。
@Preview - 根据需要配置预览参数(、
name、group、size、theme等)。brightness - 如果要为多个组件应用相同配置,将配置提取为继承的自定义类。
Preview
Interacting with Previews
与预览交互
Follow the appropriate conditional workflow to launch and interact with the Widget Previewer:
If using a supported IDE (Android Studio, IntelliJ, VS Code with Flutter 3.38+):
- Launch the IDE. The Widget Previewer starts automatically.
- Open the "Flutter Widget Preview" tab in the sidebar.
- Toggle "Filter previews by selected file" at the bottom left if you want to view previews outside the currently active file.
If using the Command Line:
- Navigate to the Flutter project's root directory.
- Run .
flutter widget-preview start - View the automatically opened Chrome environment.
Feedback Loop: Preview Iteration
- Modify the widget code or preview configuration.
- Observe the automatic update in the Widget Previewer.
- If global state (e.g., static initializers) was modified: Click the global hot restart button at the bottom right.
- If only the local widget state needs resetting: Click the individual hot restart button on the specific preview card.
- Review errors in the IDE/CLI console -> fix -> repeat.
遵循相应的条件工作流程启动并与Widget Previewer交互:
如果使用支持的IDE(Android Studio、IntelliJ、Flutter 3.38+版本的VS Code):
- 启动IDE,Widget Previewer会自动启动。
- 打开侧边栏中的“Flutter Widget Preview”标签页。
- 如果要查看当前活动文件之外的预览,点击左下角的“按所选文件过滤预览”开关。
如果使用命令行:
- 导航到Flutter项目的根目录。
- 运行。
flutter widget-preview start - 查看自动打开的Chrome环境。
反馈循环:预览迭代
- 修改组件代码或预览配置。
- 在Widget Previewer中观察自动更新。
- 如果修改了全局状态(如静态初始化器):点击右下角的全局热重启按钮。
- 如果仅需重置本地组件状态:点击特定预览卡片上的单独热重启按钮。
- 在IDE/CLI控制台中查看错误 -> 修复 -> 重复上述步骤。
Examples
示例
Basic Preview
基础预览
dart
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart';
(name: 'My Sample Text', group: 'Typography')
Widget mySampleText() {
return const Text('Hello, World!');
}dart
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart';
(name: 'My Sample Text', group: 'Typography')
Widget mySampleText() {
return const Text('Hello, World!');
}Custom Preview with Runtime Transformation
带运行时转换的自定义预览
dart
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(),
);
}
Preview transform() {
final originalPreview = super.transform();
final builder = originalPreview.toBuilder();
builder
..name = 'Transformed - ${originalPreview.name}'
..theme = _themeBuilder;
return builder.toPreview();
}
}
(name: 'Custom Themed Button')
Widget myButton() => const ElevatedButton(onPressed: null, child: Text('Click'));dart
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(),
);
}
Preview transform() {
final originalPreview = super.transform();
final builder = originalPreview.toBuilder();
builder
..name = 'Transformed - ${originalPreview.name}'
..theme = _themeBuilder;
return builder.toPreview();
}
}
(name: 'Custom Themed Button')
Widget myButton() => const ElevatedButton(onPressed: null, child: Text('Click'));MultiPreview Implementation
MultiPreview 实现
dart
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;
List<Preview> get previews => const [
Preview(brightness: Brightness.light),
Preview(brightness: Brightness.dark),
];
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();
}
}
(name: 'Primary Card')
Widget cardPreview() => const Card(child: Padding(padding: EdgeInsets.all(8.0), child: Text('Content')));dart
import 'package:flutter/widget_previews.dart';
import 'package:flutter/material.dart';
/// 自动创建亮色和暗色模式预览。
final class MultiBrightnessPreview extends MultiPreview {
const MultiBrightnessPreview({required this.name});
final String name;
List<Preview> get previews => const [
Preview(brightness: Brightness.light),
Preview(brightness: Brightness.dark),
];
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();
}
}
(name: 'Primary Card')
Widget cardPreview() => const Card(child: Padding(padding: EdgeInsets.all(8.0), child: Text('Content')));