Rive动画使用介绍(Flutter)-创新互联
感觉官方的案例对我这种level的开发者来说感觉的真的很晦涩,也没什么像样的中文文档,所以做了些尝试,大致理解了一些基本的使用方式。有些较为复杂的素材琢磨不明白,然后把自己所知的做一个总结吧。
保康网站制作公司哪家好,找创新互联建站!从网页设计、网站建设、微信开发、APP开发、响应式网站建设等网站项目制作,到程序开发,运营维护。创新互联建站于2013年成立到现在10年的时间,我们拥有了丰富的建站经验和运维经验,来保证我们的工作的顺利进行。专注于网站建设就选创新互联建站。首先是通过RiveAnimation组件加载的三种方式,分别可以通过asset资源文件、网络url和本地文件进行动画加载。
以及通过RiveAnimationController控制动画的暂停与播放。
还有一种加载方法是通过直接加载文件并解析转化成RivaFile类型,获取一个所谓artboard的画板,然后通过这个画板获取一个StateMachineController的状态器,接下来在状态其中通过findSMI和findInput方法获取到可执行的动画。动画的名称通过rive网站的editor打开自己查看,这里接似懂非懂了,见得的动画基本按照编辑器里的名称来就可以,有些复杂类型的小游戏实在是没明白怎么加载,按常规动画那套搞我没搞出来。有明白的可以指教下。主要还是findSMI可以找到SMITrigger这种类型的动画控制器,过过调用其实例的fire方法执行动画,还有findInput方法获得的SMIInput实例,通过传入泛型,double、bool等控制数值发生变化来触动动画的执行。具体的可以看下下面的一些案例,效果和代码都贴出来了,仅供参考,互相学习。
Flitter rive加载组件 rive: ^0.9.1
rive | Flutter Package
Rive素材
Rive - Community
首先是Rive动画的基本使用下面的代码分别演示Rive加载动画文件的三种方式。
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:rive/rive.dart';
///author: wangzhong
///create: 2022-11-23 23:19
///Description: Rive动画的基本本使用,三种加载方式asset加载、network网络url加载、file文件加载
class SimpleAnimationExample extends StatefulWidget {
@override
StatecreateState() {
return _SimepleAnimationExampleState();
}
}
class _SimepleAnimationExampleState extends State{
String pathRive = '';
@override
void initState() {
super.initState();
getFile().then((value) {
setState(() {
pathRive = value;
});
});
}
FuturegetFile() async {
ByteData bytes = await rootBundle.load("asset/rive/ferris-wheel.riv");
String fileName = "ferris-wheel.riv";
String dir = (await getApplicationSupportDirectory()).path;
String filePath = "$dir/$fileName";
ByteBuffer buffer = bytes.buffer;
File file = await File(filePath).writeAsBytes(buffer.asUint8List());
return file.path;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SimpleAnimationExample'),
),
body: Column(
children: [
Expanded(
child: RiveAnimation.asset(
'asset/rive/halloween-ghost.riv',
fit: BoxFit.cover,
),
),
Expanded(
child: RiveAnimation.network(
'https://public.rive.app/community/runtime-files/3605-7541-payfit-summit-2022.riv'),
),
Expanded(
child: Center(
child: pathRive.isEmpty
? Icon(Icons.confirmation_num_outlined)
: RiveAnimation.file(
pathRive,
fit: BoxFit.cover,
),
),
)
],
),
);
}
}
播放暂停的控制import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
///author: wangzhong
///create: 2022-11-24 08:50
///Description: 动画播放暂停控制
class PlayPauseAnimationExample extends StatefulWidget {
PlayPauseAnimationExample({Key? key}) : super(key: key);
@override
_PlayPauseAnimationExampleState createState() => _PlayPauseAnimationExampleState();
}
class _PlayPauseAnimationExampleState extends State{
late RiveAnimationController _controller;
/// Toggles between play and pause animation states
void _togglePlay() => setState(() =>_controller.isActive = !_controller.isActive);
/// Tracks if the animation is playing by whethe r controller is running
bool get isPlaying =>_controller.isActive;
@override
void initState() {
// TODO: implement initState
_controller = SimpleAnimation('Timeline 1');
super.initState();
}
@override
void dispose() {
// TODO: implement dispose
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PlayPauseAnimationExample'),
),
body: Center(
child: RiveAnimation.asset(
'asset/rive/ferris-wheel.riv',
controllers: [_controller],
onInit: (state) =>setState(() =>print(state)),
)),
floatingActionButton: FloatingActionButton(
onPressed: () =>_togglePlay(),
tooltip: isPlaying ? 'Pause' : 'Play',
child: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
动画中执行单次操作
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
///author: wangzhong
///create: 2022-11-24 20:59
///Description: 动画中执行单次动作
class PlayOneShotExample extends StatefulWidget {
PlayOneShotExample({Key? key}) : super(key: key);
@override
_PlayOneShotExampleState createState() =>_PlayOneShotExampleState();
}
class _PlayOneShotExampleState extends State{
late RiveAnimationController _controller;
/// Is the animation currently playing?
bool _isPlaying = false;
@override
void initState() {
super.initState();
_controller = OneShotAnimation(
'bounce',
autoplay: false,
onStop: () =>setState(() =>_isPlaying = false),
onStart: () =>setState(() =>_isPlaying = true),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PlayOneShotExample'),
// titleTextStyle: TextStyle(fontSize: 20,color: Colors.black),
),
body: Center(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
animations: const ['idle', 'curves'],
controllers: [_controller],
),
),
floatingActionButton: FloatingActionButton(
// disable the button while playing the animation
onPressed: () =>_isPlaying ? null : _controller.isActive = true,
tooltip: 'Bounce',
child: const Icon(Icons.arrow_upward),
),
);
}
}
控制动画速度import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:yhm_app/utils/rive_speed_controller.dart';
///author: wangzhong
///create: 2022-11-25 11:40
///Description: 动画速度控制
class SpeedControllExample extends StatefulWidget {
SpeedControllExample({Key? key}) : super(key: key);
@override
_SpeedControllExampleState createState() =>_SpeedControllExampleState();
}
class _SpeedControllExampleState extends State{
late RiveSpeedController speedController;
@override
void initState() {
// TODO: implement initState
speedController = RiveSpeedController('Timeline 1', speedMultiplier: 1);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SpeedControllExample'),
// titleTextStyle: TextStyle(fontSize: 20,color: Colors.black),
),
body: Container(
child: Column(
children: [
Expanded(
child: RiveAnimation.asset(
'asset/rive/ferris-wheel.riv',
fit: BoxFit.cover,
// animations: const ['Timeline 1'],
controllers: [speedController],
)),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
if (speedController.speedMultiplier >9) {
speedController.speedMultiplier = 0;
}
speedController.speedMultiplier++;
});
},
child: Text('x${speedController.speedMultiplier}'),
),
);
}
}
// class RiveSpeedController extends SimpleAnimation {
// final double speedMultiplier;
//
// RiveSpeedController(
// String animationName, {
// double mix = 1,
// this.speedMultiplier = 1,
// }) : super(animationName, mix: mix);
//
// @override
// void apply(RuntimeArtboard artboard, double elapsedSeconds) {
// if (instance == null || !instance!.keepGoing) {
// isActive = false;
// }
// instance!
// ..animation.apply(instance!.time, coreContext: artboard, mix: mix)
// ..advance(elapsedSeconds * speedMultiplier);
// }
// }
下载进度动画import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
///author: wangzhong
///create: 2022-11-26 17:40
///Description: xxxx
class DownloadProgressExample extends StatefulWidget {
DownloadProgressExample({Key? key}) : super(key: key);
@override
_DownloadProgressExampleState createState() => _DownloadProgressExampleState();
}
class _DownloadProgressExampleState extends State{
// SMIInputstart;
// SMIInputprogeress;
Artboard? _riveArtboard;
// SMIInput? _start;
SMITrigger? _start;
SMIInput? _progress;
@override
void initState() {
rootBundle.load('asset/rive/liquid_download.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'Download');
if (controller != null) {
artboard.addController(controller);
_start = controller.findSMI('Download');
_progress = controller.findInput('Progress');
print(_start);
print(_progress);
}
setState(() =>_riveArtboard = artboard);
},
);
super.initState();
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text('DownloadProgressExample'),
// titleTextStyle: TextStyle(fontSize: 20,color: Colors.black),
),
body: Container(
child: Column(
children: [
Expanded(
child: _riveArtboard == null
? SizedBox()
: GestureDetector(
onTap: () {
_start?.fire();
// _start?.value = true;
},
child: Rive(artboard: _riveArtboard!))),
_riveArtboard == null
? SizedBox()
: SliderTheme(
data: theme.sliderTheme.copyWith(
activeTrackColor: Colors.deepPurple,
inactiveTrackColor: Colors.blue.withAlpha(55),
activeTickMarkColor:
theme.colorScheme.onSurface.withOpacity(0.7),
inactiveTickMarkColor:
theme.colorScheme.surface.withOpacity(0.7),
overlayColor:
theme.colorScheme.onSurface.withOpacity(0.12),
thumbColor: Colors.deepPurple,
valueIndicatorColor: Colors.deepPurpleAccent,
thumbShape: _CustomThumbShape(),
valueIndicatorShape: _CustomValueIndicatorShape(),
valueIndicatorTextStyle: theme.accentTextTheme.bodyText2!
.copyWith(color: theme.colorScheme.onSurface),
),
child: Slider(
value: _progress!.value,
min: 0,
max: 100,
divisions: 10,//加上这个属性才会显示label
activeColor: Colors.orangeAccent,
inactiveColor: Colors.green.withAlpha(99),
thumbColor: Colors.deepPurpleAccent,
label: _progress!.value.toStringAsFixed(2),
onChanged: (value) {
setState(() {
_progress?.value = value;
});
})),
SizedBox(
height: 30,
)
],
),
),
);
}
}
class _CustomThumbShape extends SliderComponentShape {
static const double _thumbSize = 4.0;
static const double _disabledThumbSize = 3.0;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return isEnabled
? const Size.fromRadius(_thumbSize)
: const Size.fromRadius(_disabledThumbSize);
}
static final AnimatablesizeTween = Tween(
begin: _disabledThumbSize,
end: _thumbSize,
);
@override
void paint(PaintingContext context, Offset center,
{required AnimationactivationAnimation,
required AnimationenableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow}) {
final Canvas canvas = context.canvas;
final ColorTween colorTween = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.thumbColor,
);
final double size = _thumbSize * sizeTween.evaluate(enableAnimation);
final Path thumbPath = _downTriangle(size, center);
canvas.drawPath(thumbPath,
Paint()..color = colorTween.evaluate(enableAnimation) ?? Colors.blue);
}
}
Path _upTriangle(double size, Offset thumbCenter) => _downTriangle(size, thumbCenter, invert: true);
Path _downTriangle(double size, Offset thumbCenter, {bool invert = false}) {
final Path thumbPath = Path();
final double height = sqrt(3.0) / 2.0;
final double centerHeight = size * height / 3.0;
final double halfSize = size / 2.0;
final double sign = invert ? -1.0 : 1.0;
thumbPath.moveTo(
thumbCenter.dx - halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.lineTo(thumbCenter.dx, thumbCenter.dy - 2.0 * sign * centerHeight);
thumbPath.lineTo(
thumbCenter.dx + halfSize, thumbCenter.dy + sign * centerHeight);
thumbPath.close();
return thumbPath;
}
class _CustomValueIndicatorShape extends SliderComponentShape {
static const double _indicatorSize = 4.0;
static const double _disabledIndicatorSize = 3.0;
static const double _slideUpHeight = 30.0;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(isEnabled ? _indicatorSize : _disabledIndicatorSize);
}
static final AnimatablesizeTween = Tween(
begin: _disabledIndicatorSize,
end: _indicatorSize,
);
@override
void paint(PaintingContext context, Offset center,
{required AnimationactivationAnimation,
required AnimationenableAnimation,
required bool isDiscrete,
required TextPainter labelPainter,
required RenderBox parentBox,
required SliderThemeData sliderTheme,
required TextDirection textDirection,
required double value,
required double textScaleFactor,
required Size sizeWithOverflow}) {
final Canvas canvas = context.canvas;
final ColorTween enableColor = ColorTween(
begin: sliderTheme.disabledThumbColor,
end: sliderTheme.valueIndicatorColor,
);
final TweenslideUpTween = Tween(
begin: 0.0,
end: _slideUpHeight,
);
final double size = _indicatorSize * sizeTween.evaluate(enableAnimation);
final Offset slideUpOffset =
Offset(0.0, -slideUpTween.evaluate(activationAnimation));
final Path thumbPath = _upTriangle(size, center + slideUpOffset);
final Color paintColor = enableColor
.evaluate(enableAnimation)
?.withAlpha((255.0 * activationAnimation.value).round()) ??
Colors.black;
canvas.drawPath(
thumbPath,
Paint()..color = paintColor,
);
canvas.drawLine(
center,
center + slideUpOffset,
Paint()
..color = paintColor
..style = PaintingStyle.stroke
..strokeWidth = 2.0);
labelPainter.paint(
canvas,
center +
slideUpOffset +
Offset(-labelPainter.width / 2.0, -labelPainter.height - 4.0));
}
}
状态器import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
///author: wangzhong
///create: 2022-11-26 17:51
///Description: xxxx
class MoreTypeStateChangeExample extends StatefulWidget {
MoreTypeStateChangeExample({Key? key}) : super(key: key);
@override
_MoreTypeStateChangeExampleState createState() => _MoreTypeStateChangeExampleState();
}
class _MoreTypeStateChangeExampleState
extends State{
Artboard? _riveWomenArtboard;
SMIInput? _levelInput;
Artboard? _riveMenArtboard;
SMITrigger? _crossInput;
SMITrigger? _jabInput;
SMITrigger? _kickInput;
SMIInput? _runInput;
SMIInput? _turnInput;
@override
void initState() {
rootBundle.load('asset/rive/skills.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'Designer\'s Test');
if (controller != null) {
artboard.addController(controller);
_levelInput = controller.findInput('Level');
}
setState(() =>_riveWomenArtboard = artboard);
},
);
rootBundle.load('asset/rive/character-controller.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'State Machine 1');
if (controller != null) {
artboard.addController(controller);
_crossInput = controller.findSMI('crossPunch');
_jabInput = controller.findSMI('jabPunch');
_kickInput = controller.findSMI('sideKick');
_runInput = controller.findInput('isRunning');
_turnInput = controller.findInput('xDir');
}
setState(() =>_riveMenArtboard = artboard);
},
);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('MoreTypeStateChangeExample'),
// titleTextStyle: TextStyle(fontSize: 20,color: Colors.black),
),
body: Container(
child: Column(
children: [
Expanded(
child: _riveWomenArtboard == null
? SizedBox()
: Rive(
artboard: _riveWomenArtboard!,
)),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
_levelInput?.change(0);
},
child: Text('baby')),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 20),
child: ElevatedButton(
onPressed: () {
_levelInput?.change(1);
},
child: Text('younger')),
),
Expanded(
child: ElevatedButton(
onPressed: () {
_levelInput?.change(2);
},
child: Text('old people')),
),
],
),
),
Expanded(
child: _riveMenArtboard == null
? SizedBox()
: Rive(
artboard: _riveMenArtboard!,
),
),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
_crossInput?.fire();
},
child: Text('cross')),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ElevatedButton(
onPressed: () {
_jabInput?.fire();
},
child: Text('jab')),
),
),
Expanded(
child: ElevatedButton(
onPressed: () {
_kickInput?.fire();
},
child: Text('kick')),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ElevatedButton(
onPressed: () {
bool? flag = _runInput?.value;
_runInput?.value = !flag!;
},
child: Text('run')),
),
),
],
),
Padding(
padding: const EdgeInsets.only(bottom: 20),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
print(_turnInput);
_turnInput?.change(-1);
},
child: Text('turn left')),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: ElevatedButton(
onPressed: () {
_turnInput?.value = 1;
},
child: Text('turn right')),
),
),
],
),
),
],
),
),
);
}
}
如上图所示,状态切换,点击方块时,圆圈会做出一个亮暗的变化,模仿了一个开关灯的操作。
在rive平台编辑器上看到的在这组动画中有Inputs中有开关,Listener中有监听
Expanded(
child: GestureDetector(
// onTap: _hitSwitch,
child: RiveAnimation.asset(
'asset/rive/light_switch.riv',
stateMachines: ['Switch'],
),
),
),
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
文章名称:Rive动画使用介绍(Flutter)-创新互联
网页URL:http://hbruida.cn/article/desooh.html