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,并可按需搭配 RiveWidgetBuilder、RivePanel,或手动管理的共享纹理。
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
步骤:
- 将需要绘制到同一纹理的
RiveWidget用单个继承式RivePanel包裹。 - 在每个需要绘制到共享纹理的
RiveWidget中设置useSharedTexture: true。 - (可选)在每个
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,
)
},
);
}
}
使用手动共享纹理
步骤:
- 使用
SharedRenderTexture.create()创建一个SharedRenderTexture。 - 使用
RiveSurface将它的原生 surface 放到组件树中。 - 使用
sharedTexture将同一个纹理传给每个RiveWidget。 - 使用完毕后释放该纹理。
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);