跳到主要内容

Flutter

概述

本指南介绍了如何使用 Rive Flutter 运行时,在 Flutter 应用中轻松集成 Rive 图形。

最新版本的 Rive Flutter 目前以开发版本 0.14.0-dev.x 发布。这意味着虽然该包已稳定并可投入生产使用,但我们仍在积极开发新功能和改进。建议使用最新的开发版本以获取最新功能和修复。

快速开始

查看我们的示例应用

入门指南

按照以下步骤将 Rive 集成到你的 Flutter 应用中。

添加 Rive 包依赖

查看 Rive 的 pub.dev 页面获取最新版本。

# pubspec.yaml
dependencies:
rive: ^0.14.0-dev.6 # 或最新的开发版本

导入 Rive 包

在你想要集成 Rive 动画的文件中导入 Rive 运行时库。

import 'package:rive/rive.dart';

建议使用命名导入以避免与其他库冲突:

import 'package:rive/rive.dart' as rive;

初始化 Rive

建议在应用启动时或使用 Rive 之前调用 await RiveNative.init()。例如在 main.dart 中。首次加载 Rive 文件时会自动调用此方法,但如果你想在显示第一个图形之前确保 Rive 已加载,可以手动调用。

import 'package:rive/rive.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// 在使用 Rive 之前调用 init
await RiveNative.init();
runApp(const MyApp());
}

添加 Rive 组件

在 Flutter 中有多种方式渲染 Rive 图形。我们推荐使用 RiveWidget,并可按需搭配 RiveWidgetBuilderRivePanel,或手动管理的共享纹理。

  • RiveWidget 负责渲染图形并暴露常见的视图配置。
  • RiveWidgetBuilder 自动处理文件加载、错误状态和资源管理。
  • RivePanel 是一个更高级别的继承式组件,用于创建共享纹理以绘制多个 RiveWidget。仅在使用 Rive 渲染器(Factory.rive)时可用。在同时显示多个 Rive 图形时,可以通过减少纹理数量来大幅提升性能,同时避免 Web 上的 WebGL 上下文限制。
  • SharedRenderTexture.create()RiveSurface 允许你显式创建并放置共享纹理,然后将其传给 RiveWidget.sharedTexture。当绘制到同一纹理的组件没有共同的 RivePanel 祖先时(例如兄弟组件、覆盖层、弹窗、底部弹窗,或不同路由),可以使用这种方式。

使用 RiveWidgetBuilder

class ExampleRiveBuilder extends StatefulWidget {
const ExampleRiveBuilder({super.key});

@override
State<ExampleRiveBuilder> createState() => _ExampleRiveBuilderState();
}

class _ExampleRiveBuilderState extends State<ExampleRiveBuilder> {
late final fileLoader = FileLoader.fromAsset("assets/vehicles.riv", riveFactory: Factory.rive);

@override
void dispose() {
fileLoader.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return RiveWidgetBuilder(
fileLoader: fileLoader,
builder: (context, state) => switch (state) {
RiveLoading() => const Center(child: CircularProgressIndicator()),
RiveFailed() => ErrorWidget.withDetails(
message: state.error.toString(),
error: FlutterError(state.error.toString()),
),
RiveLoaded() => RiveWidget(
controller: state.controller,
fit: Fit.cover,
)
},
);
}
}

直接使用 RiveWidget

class ExampleBasic extends StatefulWidget {
const ExampleBasic({super.key});

@override
State<ExampleBasic> createState() => _ExampleBasicState();
}

class _ExampleBasicState extends State<ExampleBasic> {
late File file;
late RiveWidgetController controller;
bool isInitialized = false;

@override
void initState() {
super.initState();
initRive();
}

void initRive() async {
file = (await File.asset("assets/vehicles.riv", riveFactory: Factory.rive))!;
controller = RiveWidgetController(file);
setState(() => isInitialized = true);
}

@override
void dispose() {
file.dispose();
controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
if (!isInitialized) {
return const Center(child: CircularProgressIndicator());
}
return RiveWidget(
controller: controller,
fit: Fit.cover,
);
}
}

使用 RivePanel

步骤:

  1. 将需要绘制到同一纹理的 RiveWidget 用单个继承式 RivePanel 包裹。
  2. 在每个需要绘制到共享纹理的 RiveWidget 中设置 useSharedTexture: true
  3. (可选)在每个 RiveWidget 中设置 drawOrder 来控制绘制顺序。数字越小越先绘制。
 class ExampleRivePanel extends StatelessWidget {
const ExampleRivePanel({super.key});

@override
Widget build(BuildContext context) {
return const RivePanel(
backgroundColor: Colors.red,
child: ListViewExample(),
);
}
}
class ListViewExample extends StatefulWidget {
const ListViewExample({super.key});

@override
State<ListViewExample> createState() => _ListViewExampleState();
}

class _ListViewExampleState extends State<ListViewExample> {
late final fileLoader = FileLoader.fromAsset(
'assets/rating.riv',
riveFactory: Factory.rive,
);

@override
void dispose() {
fileLoader.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return MyRiveWidget(fileLoader: fileLoader);
},
);
}
}
class MyRiveWidget extends StatelessWidget {
const MyRiveWidget({super.key, required this.fileLoader});
final FileLoader fileLoader;

@override
Widget build(BuildContext context) {
return RiveWidgetBuilder(
fileLoader: fileLoader,
builder: (context, state) => switch (state) {
RiveLoading() => const Center(
child: Center(child: CircularProgressIndicator()),
),
RiveFailed() => ErrorWidget.withDetails(
message: state.error.toString(),
error: FlutterError(state.error.toString()),
),
RiveLoaded() => RiveWidget(
controller: state.controller,
fit: Fit.contain,
// 设置为 true 以绘制到最近的 RivePanel
useSharedTexture: true,
)
},
);
}
}

使用手动共享纹理

步骤:

  1. 使用 SharedRenderTexture.create() 创建一个 SharedRenderTexture
  2. 使用 RiveSurface 将它的原生 surface 放到组件树中。
  3. 使用 sharedTexture 将同一个纹理传给每个 RiveWidget
  4. 使用完毕后释放该纹理。
class ExampleManualSharedTexture extends StatefulWidget {
const ExampleManualSharedTexture({super.key});

@override
State<ExampleManualSharedTexture> createState() =>
_ExampleManualSharedTextureState();
}

class _ExampleManualSharedTextureState
extends State<ExampleManualSharedTexture> {
late final SharedRenderTexture sharedTexture = SharedRenderTexture.create(
backgroundColor: Colors.transparent,
);

late final fileLoader = FileLoader.fromAsset(
'assets/rating.riv',
riveFactory: Factory.rive,
);

@override
void dispose() {
fileLoader.dispose();
sharedTexture.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Stack(
children: [
Row(
children: List.generate(
3,
(index) => Expanded(
child: RiveWidgetBuilder(
fileLoader: fileLoader,
builder: (context, state) => switch (state) {
RiveLoading() => const Center(
child: CircularProgressIndicator(),
),
RiveFailed() => ErrorWidget.withDetails(
message: state.error.toString(),
error: FlutterError(state.error.toString()),
),
RiveLoaded() => RiveWidget(
controller: state.controller,
fit: Fit.contain,
sharedTexture: sharedTexture,
drawOrder: index,
),
},
),
),
),
),
Positioned.fill(
child: RiveSurface(sharedTexture: sharedTexture),
),
],
);
}
}

从不同来源加载

从资源包加载:

确保将 Rive 文件添加到资源包中并在 pubspec.yaml 中引用:

# pubspec.yaml
assets:
- assets/vehicles.riv
// 使用 FileLoader(配合 RiveWidgetBuilder)
final fileLoader = FileLoader.fromAsset("assets/vehicles.riv", riveFactory: Factory.rive);

// 直接使用 File
final file = await File.asset("assets/vehicles.riv", riveFactory: Factory.rive);

从 URL 加载:

// 使用 FileLoader(配合 RiveWidgetBuilder)
final fileLoader = FileLoader.fromUrl("https://cdn.rive.app/animations/vehicles.riv", riveFactory: Factory.rive);

// 直接使用 File
final file = await File.url("https://cdn.rive.app/animations/vehicles.riv", riveFactory: Factory.rive);

从 Rive File 加载:

// 使用 FileLoader(配合 RiveWidgetBuilder)
final fileLoader = FileLoader.fromFile(existingFile, riveFactory: Factory.rive);

核心组件

RiveWidget

RiveWidget 负责显示 Rive 图形。

属性:

  • controller [必填]:管理 Rive 图形的 RiveWidgetController
  • fit:画板在组件内的适配方式(默认:contain
  • alignment:画板在组件内的对齐方式(默认:center
  • hitTestBehavior:指针事件的处理方式(默认:opaque
  • cursor:悬停在组件上时显示的光标(默认:defer
  • layoutScaleFactor:使用 Fit.layout 时的缩放因子(默认:1.0
  • useSharedTexture:是否使用来自最近 RivePanel 的继承共享纹理。默认 false。当提供 sharedTexture 时会被忽略。
  • sharedTexture:显式指定要绘制到的 SharedRenderTexture,从而绕过基于祖先组件的 RivePanel 查找。可与 SharedRenderTextureRiveSurface 一起使用,在组件树的任意部分之间共享纹理。
  • drawOrder:画板的绘制顺序。仅在使用 Factory.rive 并绘制到共享纹理时有效,无论共享纹理来自 useSharedTextureRivePanel,还是来自显式传入的 sharedTexture。默认为 1。

RiveWidgetBuilder

RiveWidgetBuilder 是一个更高级别的组件,自动处理文件加载、错误状态和资源管理。

属性:

  • fileLoader [必填]:用于加载 Rive 文件的 FileLoader
  • builder [必填]:根据状态构建组件的函数
  • artboardSelector:选择使用哪个画板(默认:ArtboardDefault()
  • stateMachineSelector:选择使用哪个状态机(默认:StateMachineDefault()
  • dataBind:如何绑定视图模型数据(可选)
  • controller:可选的自定义控制器构建器
  • onLoaded:Rive 状态加载成功时的回调
  • onFailed:Rive 状态加载失败时的回调

RivePanel

RivePanel 是一个创建共享纹理以绘制多个 RiveWidget 的组件。在使用 Factory.rive 时非常有用,可以在特定条件下显著提升性能。

何时使用 RivePanel:

  • 当应用中显示多个 RiveWidget 且它们可以绘制到同一纹理时
  • 当需要编程方式合成包含多个 Rive 图形(来自多个 Rive 文件/画板)的场景时
  • 当使用 Factory.rive(使用 Factory.flutter 时会报错)并希望提升性能时
  • 当希望减少绘制的纹理数量时
  • 当针对 Web 平台以避免通过 Factory.rive 达到 WebGL 上下文限制时

性能考虑:

  • 优势:将多个 RiveWidget 绘制到同一纹理可以通过减少纹理分配开销来大幅提升性能
  • 内存成本:分配更大的纹理会带来内存成本,但这可能被减少的独立纹理数量所抵消
  • 渲染限制:绘制到同一表面意味着无法将 Rive 绘制命令与 Flutter 绘制命令交错执行
  • 建议基准测试:性能特性因使用场景而异——适合某个场景的方案可能不适合另一个

用法:

RivePanel(
backgroundColor: Colors.red, // 可选的背景颜色
child: YourWidgetWithMultipleRiveWidgets(),
)

重要提示:

  • 仅适用于 Factory.rive——使用 Factory.flutter 时无效
  • RiveWidget 中设置 useSharedTexture: true 以启用共享纹理渲染
  • 如果需要将 Rive 内容与 Flutter 内容交错,请考虑使用单独的 RivePanelFactory.flutter
  • 对于复杂场景,对两种方法进行基准测试以确定最佳性能策略

SharedRenderTextureRiveSurface

SharedRenderTexture.create() 会创建一个由用户持有的共享纹理,可直接传给 RiveWidget.sharedTexture。与 RivePanel 不同,它不依赖继承式组件查找,因此适用于 RiveWidget 互为兄弟组件、位于不同子树,或通过 OverlayEntry、dialog、bottom sheet、其他 route 挂载的场景。

使用 RiveSurface 将原生纹理放到组件树中。接收同一个 SharedRenderTextureRiveWidget 会绘制到该 surface 中,即使它们不是这个 surface 的后代。

late final SharedRenderTexture sharedTexture = SharedRenderTexture.create(
backgroundColor: Colors.transparent,
);

@override
void dispose() {
sharedTexture.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Stack(
children: [
RiveWidget(
controller: controller,
sharedTexture: sharedTexture,
),
Positioned.fill(
child: RiveSurface(sharedTexture: sharedTexture),
),
],
);
}

重要提示:

  • 此 API 仍处于实验阶段,并且仅适用于 Factory.rive
  • 同时设置 RiveWidget.sharedTextureuseSharedTexture 时,RiveWidget.sharedTexture 优先。
  • 你必须对通过 SharedRenderTexture.create() 创建的纹理调用 dispose(),以释放原生纹理。
  • 默认情况下,RiveSurface 会包裹在 IgnorePointer 中,因此指针事件由各个 RiveWidget 处理。如果希望指针事件直接到达原生纹理组件,请设置 ignorePointer: false

RiveWidgetController

RiveWidgetController 管理图形。

创建控制器:

// 使用默认画板和状态机
final controller = RiveWidgetController(file);

// 指定画板和状态机
final controller = RiveWidgetController(
file,
artboardSelector: ArtboardSelector.byName("MyArtboard"),
stateMachineSelector: StateMachineSelector.byName("MyStateMachine"),
);

数据绑定:

// 使用默认视图模型实例自动绑定
final viewModelInstance = controller.dataBind(DataBind.auto());

// 通过特定实例绑定
final viewModelInstance = controller.dataBind(DataBind.byInstance(myInstance));

// 通过名称绑定
final viewModelInstance = controller.dataBind(DataBind.byName("MyViewModel"));

文件加载

FileLoader 类提供了从不同来源加载 Rive 文件的统一方式。

从资源加载:

final fileLoader = FileLoader.fromAsset(
"assets/vehicles.riv",
riveFactory: Factory.rive,
);

从 URL 加载:

final fileLoader = FileLoader.fromUrl(
"https://example.com/animation.riv",
riveFactory: Factory.rive,
);

从已有文件加载:

final fileLoader = FileLoader.fromFile(
existingFile,
riveFactory: Factory.rive,
);

或者你也可以直接使用 File 类加载文件:

// 从资源加载
final file = await File.asset("assets/vehicles.riv", riveFactory: Factory.rive);
// 从 URL 加载
final file = await File.url("https://example.com/animation.riv", riveFactory:
Factory.rive);
// 从路径加载
final file = await File.path("/path/to/animation.riv", riveFactory: Factory.rive);
// 从字节加载
final file = await File.decode(bytes, riveFactory: Factory.rive);

错误处理

Rive Flutter 包为不同的错误场景提供了特定的异常类型:

  • RiveFileLoaderException:文件加载失败时抛出
  • RiveArtboardException:画板选择失败时抛出
  • RiveStateMachineException:状态机选择失败时抛出
  • RiveDataBindException:数据绑定失败时抛出

资源管理

手动资源管理(RiveWidget

直接使用 RiveWidget 时,你需要负责管理所有资源:

@override
void dispose() {
// 按创建顺序的逆序释放资源
viewModelInstance.dispose();
controller.dispose();
file.dispose();
super.dispose();
}

自动资源管理(RiveWidgetBuilder

使用 RiveWidgetBuilder 时,组件会自动管理大部分资源。你只需要释放文件加载器:

@override
void dispose() {
fileLoader.dispose();
super.dispose();
}

由于资源由 RiveWidgetBuilder 管理,因此在组件被释放后你将无法访问 RiveWidgetController(及其他状态)。如果你需要在组件释放后访问控制器,请考虑自行创建文件和控制器。

唯一的例外是 FileLoader,它由你控制。此加载器可以在多个 RiveWidgetBuilder 实例之间复用。底层的 File 只会加载一次。当 FileLoader 被释放时,File 也会被释放。

指定渲染器

在创建 Rive FileFileLoader 时,需要指定要使用的工厂:

  • Factory.rive 用于 Rive 渲染器
  • Factory.flutter 用于 Flutter 渲染器(Skia 或 Impeller)

你可以在应用中为不同的图形使用不同的渲染器。

选择渲染器时的一些考虑:

  • 如果你计划显示许多绘制到不同 Rive 组件的 Rive 图形,请考虑使用 RivePanel 配合 Factory.rive 将多个图形绘制到同一纹理,以减少分配原生渲染目标和纹理的开销。或者使用 Factory.flutter
  • 如果你显示的是复杂图形,请考虑使用 Factory.rive 以利用 Rive 渲染器的优化。
  • 矢量羽化仅在使用 Factory.rive 时可用,如果需要该功能,请使用 Rive 渲染器。

更多信息请参见选择渲染器

⚠️ 警告:Rive 渲染器在 Linux 上通过 Flutter 尚不支持。在 Linux 上,它会自动回退到 Factory.flutter

Flutter 渲染说明

Impeller 正在取代 Skia 成为所有平台的默认渲染器。因此,在使用 Rive Flutter 运行时的 Impeller 渲染器平台时,可能会出现之前没有暴露的渲染和性能差异。如果你在运行时遇到与 Rive 编辑器中预期行为不同的视觉或性能错误,建议尝试以下步骤进行排查:

  1. 尝试使用 --no-enable-impeller 标志运行 Flutter 应用以使用 Skia 渲染器。如果使用 Skia 时视觉差异消失,则可能是 Impeller 的渲染 bug。但在向 Flutter 团队提交 bug 之前,请先尝试下面的第二点👇
flutter run --no-enable-impeller
  1. 尝试在最新的 master 频道上运行 Flutter 应用。视觉 bug 可能在最新的 Flutter 提交中已修复,但尚未发布到 betastable 频道。
  2. 如果你在最新 master 分支上仍然仅在 Impeller 渲染器下看到视觉差异,建议向 Flutter GitHub 仓库提交详细 issue,附带可复现示例和其他有助于团队调试问题的相关细节。

故障排查

如果你在 Flutter 中使用 Rive 时遇到问题,请考虑以下事项:

  • 确保在使用任何 Rive 功能之前已调用 await RiveNative.init()
  • 检查控制台是否有与 Rive 相关的错误消息。
  • 确保 Rive 文件在 pubspec.yaml 中正确引用并存在于指定路径。
  • 如果使用 RiveWidgetBuilder,请确保在 builder 函数中处理所有可能的状态(加载中、已加载、失败)。

构建错误

如果遇到与 Rive 相关的构建错误,请确保:

  • pubspec.yaml 中的 Rive 包版本正确。
  • 已运行 flutter pub get 获取最新依赖。
  • 已查阅 Flutter 常见问题 了解常见问题和解决方案。

如果仍有问题,请参阅 Rive Native 文档中的故障排查部分

手动构建 Rive 原生库

Rive 会自动为你下载原生库,作为 rive_native 插件的一部分。

但是,如果你需要手动构建原生库,请参阅 Rive Native 文档中的构建部分

资源

Rive Flutter:

Rive Native: