说到 rive ,非 Flutter 开发者可能会感觉比较生疏,而做过 Flutter 开发的可能对 rive 会有所耳闻,由于 rive 在一开端叫 flare ,是 2dimensions 公司的开源动画产品,在发布之初由于和 Flutter 团队有深入合作,所以在初期一直是 Flutter 官方引荐的动画框架之一。
前言
rive 作为一个面向规划师的动画框架,他支撑在 Web Editor 里进行 UI 编列和动画制作,当然现在他也支撑 PC 客户端开发,整体开发环境需求上相对 Lottie 会轻量化很多。
另外, rive 是经过导出矢量的动画数据文件(也能够包括一些静态资源),然后运用渠道的 Canvas
来完成动画作用,所以它的资源占用体积也不会很大。
当然,rive 其实并不是只针对 Flutter, rive 现在也是全渠道支撑, Android、 iOS、Web、Desktop、Flutter 、React、Vue、C++ 等等都在支撑规模之内。
关于 rive 的规划端的简略运用,能够看我之前的 《给 Logo 快速添加动画作用》 ,其实关于程序员来说,rive 其实很好上手,翻开一个 WebEdit 就能够编辑调整。
PS,第二代 rive 和第一代 flare 存在断档不兼容,并且根本能够疏忽迁移的可能,当然, flare 和 rive 其实能够一起存在一个项目不会抵触,所以也不需求留神旧动画的晋级问题。
Rive Flutter
开端进入主题,其实 rive 比 flare 运用起来愈加简略,如下代码所示,只需求经过 RiveAnimation.asset
就能够完成一个下图里炫酷的动画作用,
dependencies:
rive: 0.9.0
import 'package:rive/rive.dart';
RiveAnimation.asset('static/file/launch.riv'),
当然,除了上面的 asset
,还能够经过 file
还有 network
等方法这加载,这也算是比较惯例的集成方法。
那么运用 rive ,作为开发者端,需求简略知道的几个概念:
- Artboards:画布,rive 里至少会有一块画布,当然一个 riv 动画文件能够有多个画布
- animations:需求播映的动画
- StateMachine:状况机,能够将动画连接在一起并定义切换条件的支撑
- Inputs:StateMachine 的输入,然后可用于与 StateMachine 交互并修改动画切换的状况
如下代码所示,一般状况下咱们不需求关怀上述设定,由于只要在规划时考虑好默许状况,那么只需求简略引入就能够播映动画。
RiveAnimation.asset('assets/33333.riv')
可是假如你需求更灵敏的操控时,就需求理解上述这些设定的作用,后续才干和动画规划师进行有效的交流和对接。
如下图所示便是对应的设定解读,例如:
- 知道了画布称号,就能够经过
artboard
切换画布 - 知道动画称号,就能够经过
animations
指定动画 - 知道了状况机称号,就能够经过
stateMachines
切换状况机 - 知道了状况条件,就能够经过
findInput
来切换条件变量
animations
咱们先看 animations ,默许状况下 33333.riv
这个 riv 动画播映的是 Shaking
作用,从上图左下角能够看到 Shaking
是一个有循环♻️标识的动画,所以如下图所示车辆动画处于都懂状况。
RiveAnimation.asset('assets/33333.riv')
接着咱们更新代码,添加了 animations
选择播映 "Jump"
,能够看到,车辆播映到了 Jump 作用的动画,并停留不动,由于 Jump 不是循环动画,所以只会播映一次,然后能够看到 Shaking
也没有了,由于咱们只选中了 Jump
。
RiveAnimation.asset(
'assets/33333.riv',
animations: [
"Jump",
],
)
相同,假如咱们多选中一个 Wheel
动画,能够看到车轮开端动起来,由于 Wheel
也是一个循环♻️动画,所以车轮能够一直滚动。
RiveAnimation.asset(
'assets/33333.riv',
animations: [
"Jump",
"Wheel",
],
)
所以经过 animations 咱们能够方便组合需求播映的动画作用。
stateMachines & Inputs
前面咱们知道了能够经过 animations
装备动画,那么接下来再看看怎么经过 stateMachines
来操控动画作用。
和 animations
相同,stateMachines
相同是一个List<String>
,也便是能够装备多个状况,例如经过前面编辑器咱们知道,此刻 33333.riv
的状况机只有一个 State Machine 1
,所以咱们只需求装备上对应的 stateMachines
,就能够看到此刻车辆动起来,进入状况机动画形式,也便是 Entry
。
RiveAnimation.asset(
'assets/33333.riv',
stateMachines: [
"State Machine 1"
],
那装备 stateMachines
仅仅进入 Entry
,假如要操控状况改动该怎么办?这就要说到 Inputs
。
获取 Inputs
咱们需求在 _onRiveInit
回调里去获取,如下代码所示:
- 首先经过
StateMachineController.fromArtboard
获取到状况机的操控器,这样咱们运用的是默许画板,所以直接运用初始化时传入的artboard
即可 -
fromArtboard
时经过State Machine 1
指定了状况机,然后经过onStateChange
监听状况机改动 - 经过
addController
将获取到的状况机操控器添加到画布 - 经过
findInput
找到对应的操控状况SMIBool
- 调用
change
改动SMIBool
的 value 来切换动画状况
RiveAnimation.asset(
'assets/33333.riv',
onInit: _onRiveInit,
)
SMIBool? _skin;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1',
onStateChange: _onStateChange,
);
artboard.addController(controller!);
_skin = controller.findInput<bool>('Boolean 1') as SMIBool;
}
void _onStateChange(String stateMachineName, String stateName) {
print("stateMachineName $stateMachineName stateName $stateName");
}
void _swapSkin() {
_skin?.change(!_skin!.value);
}
为什么这儿是 SMIBool
? 由于在该状况机设定里用的是 Bool 类型条件。
当然,除了 Bool 还能够用数字作为判断条件,对应的 Type 类型也会变成 SMINumber
。
另外还有 SMITrigger
类型, SMITrigger
只需求经过 fire
和 advance
去操控动画的前后切换,改动也只能单途径形式一个一个切换。
回到开端的设定里,经过 _skin?.change(!_skin!.value);
切换 Bool 值的改动,能够看到此刻车辆开端在 Jump 和 Down 进行改动,这便是最简略的状况机和 Input 的示例作用。
当然,如下图变高变胖的人便是经过 SMINumber
随意切换状况的作用,而小黑人换皮肤,便是经过 SMITrigger
单途径形式一个一个切换的动画作用。
当然,动画里也可能会包括多个不同类型的 Input ,你能够经过 StateMachineController
的 Inputs
参数去获取所有你需求的 Input 去操控动画作用。
其他
布局调整
其实了解上面哪些,大致上你就根本学会完美运用 rive 了,剩下的一些参数支撑就都是小事,例如:
RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
fit: BoxFit.fitWidth,
alignment: Alignment.topCenter,
);
这儿会用到 fit
和 alignment
,他们都是 Flutter 里常见的装备支撑,这儿就不多赘述,默许状况下是 BoxFit.Contain
和 Alignment.Center
。
文本支撑
新版的 rive 支撑运转过程中替换动画文件里的文本内容,条件是运用新版导出,然后需求编辑器中手动设置称号的文本才干支撑该才能。
代码上简略说来说,便是在 onInit
的时分经过自定义的文本称号,然后经过 artboard
获取该节点,从而修改文本内容。
extension _TextExtension on Artboard {
TextValueRun? textRun(String name) => component<TextValueRun>(name);
}
RiveAnimation.asset(
'assets/hello_world_text.riv',
animations: const ['Timeline 1'],
onInit: (artboard) {
final textRun = artboard.textRun('MyRun')!; // find text run named "MyRun"
print('Run text used to be ${textRun.text}');
textRun.text = 'Hi Flutter Runtime!';
},
)
播映操控
现在的 rive 自带的 RiveAnimationController
对比 flare 弱化了很多,根本上便是用来完成简略的 play
、pause
和 stop
等,默许官方供给了 SimpleAnimation
和 OneShotAnimation
两种 RiveAnimationController
默许完成。
一般用不上自定义。
SimpleAnimation
首要是供给单个动画的简略播映操控,如 play、 pause (isActive
) 和 reset ,以下是官方 Demo 的示例,
class PlayPauseAnimation extends StatefulWidget {
const PlayPauseAnimation({Key? key}) : super(key: key);
@override
State<PlayPauseAnimation> createState() => _PlayPauseAnimationState();
}
class _PlayPauseAnimationState extends State<PlayPauseAnimation> {
/// Controller for playback
late RiveAnimationController _controller;
/// Toggles between play and pause animation states
void _togglePlay() =>
setState(() => _controller.isActive = !_controller.isActive);
/// Tracks if the animation is playing by whether controller is running
bool get isPlaying => _controller.isActive;
@override
void initState() {
super.initState();
_controller = SimpleAnimation('idle');
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Animation Example'),
),
body: RiveAnimation.asset(
'assets/off_road_car.riv',
fit: BoxFit.cover,
controllers: [_controller],
// Update the play state when the widget's initialized
onInit: (_) => setState(() {}),
),
floatingActionButton: FloatingActionButton(
onPressed: _togglePlay,
tooltip: isPlaying ? 'Pause' : 'Play',
child: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}
首要便是经过
isActive
来操控动画的暂停或许播映。
OneShotAnimation
首要用于在播映完一次动画后自动中止并重置动画,以下是官方 Demo 的示例,其实 OneShotAnimation
便是继承了 SimpleAnimation
,然后在其基础上增加了监听,在播映结束时调用 reset
重制动画而已。
/// Demonstrates playing a one-shot animation on demand
class PlayOneShotAnimation extends StatefulWidget {
const PlayOneShotAnimation({Key? key}) : super(key: key);
@override
State<PlayOneShotAnimation> createState() => _PlayOneShotAnimationState();
}
class _PlayOneShotAnimationState extends State<PlayOneShotAnimation> {
/// Controller for playback
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: const Text('One-Shot Example'),
),
body: Center(
child: RiveAnimation.asset(
'assets/vehicles.riv',
animations: const ['idle', 'curves'],
fit: BoxFit.cover,
controllers: [_controller],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _isPlaying ? null : _controller.isActive = true,
tooltip: 'Bounce',
child: const Icon(Icons.arrow_upward),
),
);
}
}
上述代码便是在行进过程中,点击是触发 'bounce'
的一次性跳跃作用,OneShotAnimation
首要便是用在类似的一次性动画场景上,
最终
能够看到 Rive 的运用其实很简略,可是由于状况机的完成,它又能够很灵敏地去操控不同动画的作用。
一个 riv 文件内能够包括多个画板,画板里能够包括多个动画,多个状况机和输入条件,从而完成多样化的动画作用,乃至完成 Rive 版别的 Flutter 小游戏场景。
并且 Rive 并不仅仅支撑 Flutter ,它如今几乎支撑所有你能想到的渠道,那么这样的一个优异的渠道有什么缺陷呢?
那便是 Rive 最近开端收费了,彻底的商业化产品, 其实不给钱你也能够用,仅仅 Free 形式下现已不是以前那个眉清目秀的 Rive 了。
Free 形式的 Rive 会有多个如下图所示的 Make with Rive
的水印,一起现在 Free 形式不支撑 Share links 了,也便是你自己体验一下,要投入生产运用还是得付费。
那么有机智的小伙伴可能就要说了, Rive 不是开源的吗?那咱们能够自己弄一套免费的吗?
答案是能够,可是本钱无疑巨大,由于 Rive 的门槛不在于它开源的端侧 SDK ,而是在于规划端和产出端,目前的水印是在导出时强制加上的,所以关于运用 Rive 的用户来说,自己搭一套明显不现实。
那么,最终,你会愿意为这样一套产品而付费吗?横竖我是现已付费ing了。