Flare动画简介

Flutter的动画大体能够分为应用AnimationController 和Animation管制的根底动画、应用 Hero的转场动画和应用CustomPainter 的自定义动画三大类。除此之外,Flutter还反对矢量动画,是一种相似Android开发中的Lottie动画。

Flare是一家能够疾速制作矢量动画的网站,提供专门的Flutter组件来承载网站导出的动画文件,应用Flare创立的动画不仅能够无效缩小安装包的体积,还能创立更加简单壮丽的动画体验。Flare动画最早呈现在2019年12月举办的Flutter技术大会上,一经公布立马受到开发者的青睐和追捧。

作为一个业余制作矢量动画的网站,Flare提供了十分丰盛的收费矢量动画。因为Flare并没有提供桌面版的开发工具,所以创立Flare动画之前须要登录Flare官网来制作Flare动画文件,如果还没有Flare账号能够先注册一个。

Flare通常以工程模式来创立和治理动画我的项目,目前Flare反对创立动画我的项目有两类,别离是Flare和Nima,它们的区别如下。

  • Flare:为App和Web构建实时、疾速的动画,同时也反对构建游戏利用动画。
  • Nima:为游戏引擎和利用构建2D动画。

因为Nima次要用于构建2D游戏动画,所以如果是一般的利用开发只须要新建一个Flare我的项目即可。关上Flare官网,而后点击【Your Files】菜单即可新建一个Flare我的项目,如下图所示。

而后,零碎会初始化一个空白的工作区用于开发者创立和制作动画文件,如下图所示。

在工作区的左上角有两个切换按钮,别离是SETUP和ANIMATE,示意两种不同的工作模式。其中,SETUP模式用于导入和绘制矢量元素,而ANIMATE模式则用于解决矢量元素的动画交互,动画交互须要用到的动画节点名称位于工作区的左下角。

通常,制作Flare动画文件是一项业余且简单的工作,如果只是为了体验Flare动画的魅力,那么能够应用Flare提供的收费矢量动画,如下图所示。

制作Flare动画

如果咱们须要创立Flare动画,那么首先须要初始化一个动画我的项目,如下图所示。

如上图所示,在工作区的左上角有两个切换按钮,别离是SETUP和ANIMATE,示意两种不同的工作模式。

  • SETUP:用于导入和绘制矢量元素
  • ANIMATE用于解决矢量元素的动画交互

在SETUP模式下,咱们能够通过Hierarchy树状图来查看所有控件的层级构造关系,个别顶级结点是一个artboard,能够定义scene的尺寸、背景色彩等属性。一个Flare动画能够有多个artboard,并且控件都能够领有本人的子控件,子控件会继承父控件的所有变换。如果要增加矢量元素,能够点击 SETUP模式下工作区的“+”号按钮,如下图所示。

咱们以制作一个按钮为例。首先,咱们抉择菜单中的矩形,而后选中矩形,右侧会呈现属性菜单栏,能够批改地位、大小、色彩、线条等等属性,如下图所示。
当然,咱们也能够按住鼠标右键(或者按住空格拖动鼠标)能够拖动画布,滚轮放大/放大,上下左右键准确调整地位,Shift+上下左右键能够大幅调整地位。接下来,咱们切换到ANIMATE模式增加动画,底下会多出一行动画控制面板,如下图。

首先,关上动画时长区间,将指针拨到00:01:00(mac能够应用快捷键command+shift+左右,windows能够应用快捷键ctrl+shift+左右,一次调整10帧),并在在00:01:00处更改矩形的属性,如下图所示。

而后,点击左下角的播放键,成果如下图。

最初,将制作好的Flare动画文件导出即可。

对于如何创立Flare动画,能够参考官网开源的例子,以及 Flare动画和Flutter动画之Flare的制作与应用。

Flare动画应用

制作Flare动画文件是一项业余且简单的工作,如果只是为了体验Flare动画的魅力,那么能够应用Flare提供的收费矢量动画。首先,关上一个收费的矢量动画,而后点击面板中【OPEN IN RIVE】按钮关上Flare动画文件,如下图所示。

而后,点击工作区右上角的导出图标即可导出Flare动画文件,该文件是一个flr 格局的文件,Flare动画组件操作的就是该文件。

在Flutter中开发Flare动画须要应用到flare_flutter或者smart_flare库。其中,smart_flare库是对flare_flutter库的高度封装,开发者只须要应用大量代码即可实现与Flare动画的交互。关上Flutter工程,并在pubspec.yaml文件中增加如下依赖配置。

dependencies:  flare_flutter: ^2.0.5  smart_flare: ^0.2.9+1

而后,应用flutter packages get命令将依赖的插件拉取到本地。而后,将之前导出的flr动画文件拷贝到assets资源目录下,并在pubspec.yaml配置文件中注册该动画文件,如下所示。

assets:   - assets/button-animation.flr

如果只是单纯的加载动画文件,而不须要解决与动画交互,那么能够应用flare_flutter库提供的FlareActor组件来加载动画文件,如下所示。

FlareActor(    "assets/Shake.flr",    animation: "idle",    alignment: Alignment.center,    fit: BoxFit.contain)

其中,Shake.flr示意Flare动画文件的名称,animation示意动画的初始节点。通常,flr文件会有多个动画节点,能够应用artboard.getNode(String name)办法获取动画的节点,而后通过节点来对动画进行准确地管制。

flare_flutter库应用

应用flare_flutter库执行动画交互操作时,须要咱们继承FlareControls类,并对initialize()、advance()和setViewTransform()三个办法进行重写,如下所示。

  • initialize():个别用于动画的初始化,因为FlareActor控件曾经构建实现,所以能够在此办法中获取动画节点。
  • setViewTransform():每执行一帧动画都会调用此办法。
  • advance():在每一次动画行将被刷新的时候调用。

例如,上面是应用flare_flutter库实现登录的动画,在此登录交互动画中,成果如下图。


在下面的动画交互中,次要蕴含如下6种动画交互事件,别离是:

  • idle:无任何操作时的状态(熊的身材会上下浮动和眨眼睛)
  • test:当咱们在 email 输入框中输出时的状态(熊会看向输入框,且随着你输出的长度旋转头部)
  • hands_up:当咱们在 password 输入框中输出时的状态 (熊会用手蒙上眼睛)
  • hands_down:当咱们在 password 输入框输出实现时的状态 (熊会放下双手)
  • fail:当咱们登录失败时的状态(熊会做出惆怅的表情)
  • success:当咱们登录胜利时的状态(熊会做出快乐的表情)

那么如果要对用户的行为进行精准的响应,那么就须要咱们继承FlareControls,而后通过ActorNode的artboard.getNode(String name)获取节点后执行对于的事件,如下所示。

class FlareSignInController extends FlareControls {  ActorNode _faceControl;  Mat2D _globalToFlareWorld = Mat2D();  Vec2D _caretGlobal = Vec2D();  Vec2D _caretWorld = Vec2D();  Vec2D _faceOrigin = Vec2D();  Vec2D _faceOriginLocal = Vec2D();  bool _hasFocus = false;  String _password;  static const double _projectGaze = 60.0;  @override  bool advance(FlutterActorArtboard artboard, double elapsed) {    super.advance(artboard, elapsed);    Vec2D targetTranslation;    if (_hasFocus) {      Vec2D.transformMat2(_caretWorld, _caretGlobal, _globalToFlareWorld);      _caretWorld[1] +=          sin(new DateTime.now().millisecondsSinceEpoch / 300.0) * 70.0;      Vec2D toCaret = Vec2D.subtract(Vec2D(), _caretWorld, _faceOrigin);      Vec2D.normalize(toCaret, toCaret);      Vec2D.scale(toCaret, toCaret, _projectGaze);      Mat2D toFaceTransform = Mat2D();      if (Mat2D.invert(toFaceTransform, _faceControl.parent.worldTransform)) {        Vec2D.transformMat2(toCaret, toCaret, toFaceTransform);        targetTranslation = Vec2D.add(Vec2D(), toCaret, _faceOriginLocal);      }    } else {      targetTranslation = Vec2D.clone(_faceOriginLocal);    }    Vec2D diff =        Vec2D.subtract(Vec2D(), targetTranslation, _faceControl.translation);    Vec2D frameTranslation = Vec2D.add(Vec2D(), _faceControl.translation,        Vec2D.scale(diff, diff, min(1.0, elapsed * 5.0)));    _faceControl.translation = frameTranslation;    return true;  }  @override  void initialize(FlutterActorArtboard artboard) {    super.initialize(artboard);    _faceControl = artboard.getNode("ctrl_face");    if (_faceControl != null) {      _faceControl.getWorldTranslation(_faceOrigin);      Vec2D.copy(_faceOriginLocal, _faceControl.translation);    }    play("idle");  }  @override  void onCompleted(String name) {    play("idle");  }  @override  void setViewTransform(Mat2D viewTransform) {    Mat2D.invert(_globalToFlareWorld, viewTransform);  }  void lookAt(Offset caret) {    if (caret == null) {      _hasFocus = false;      return;    }    _caretGlobal[0] = caret.dx;    _caretGlobal[1] = caret.dy;    _hasFocus = true;  }  void setPassword(String value) {    _password = value;  }  bool _isCoveringEyes = false;  coverEyes(cover) {    if (_isCoveringEyes == cover) {      return;    }    _isCoveringEyes = cover;    if (cover) {      play("hands_up");    } else {      play("hands_down");    }  }  void submitPassword() {    if (_password == "bears") {      play("success");    } else {      play("fail");    }  }}

因为应用flare_flutter库实现登录动画比较复杂,所以具体的代码就不多解说,有趣味的能够看看源码:登录动画源码。

smart_flare库应用

应用flare_flutter实现的Flare动画,须要开发者编写FlareControls来管制动画交互,须要开发者具备较好的数学和物理根底,实现起来也比较复杂。相比flare_flutter插件库来说,应用smart_flare插件库实现Flare动画要简略许多,只须要调用smart_flare插件库提供的组件,而后传入对应的参数即可。

ActiveArea(    debugArea: true,    area: Rect.fromLTWH(thirdOfWidth*2, 0, thirdOfWidth, animationHeight / 2),animationName: 'image_tapped',onAreaTapped: () {   print('image_tapped…');}),

其中,area示意须要显示的元素在屏幕的地位,animationName示意执行动画交互时动画节点的名称,debugArea示意是否开启调试模式,如果开启调试模式会看到该元素区域有一个暗影,onAreaTapped用于响应用户的点击。

例如,上面是应用smart_flare库提供的SmartFlareActor和ActiveArea组件实现的菜单动画的例子,成果如下。

上面是smart_flare库实现按钮弹出菜单的示例,源码如下。

class FlareAnimPage extends StatefulWidget {  @override  _FlareAnimPageState createState() => _FlareAnimPageState();}class _FlareAnimPageState extends State<FlareAnimPage> {  @override  Widget build(BuildContext context) {    var animW = 295.0;    var animH = 251.0;    var animWThirds = animW / 3;    var halfAnimHeight = animH / 2;    var activeAreas = [      ActiveArea(        area: Rect.fromLTWH(0, 0, animWThirds, halfAnimHeight),        debugArea: false,        guardComingFrom: ['deactivate'],        animationName: 'camera_tapped',      ),      ActiveArea(          area: Rect.fromLTWH(animWThirds, 0, animWThirds, halfAnimHeight),          debugArea: false,          guardComingFrom: ['deactivate'],          animationName: 'pulse_tapped'),      ActiveArea(          area: Rect.fromLTWH(animWThirds * 2, 0, animWThirds, halfAnimHeight),          debugArea: false,          guardComingFrom: ['deactivate'],          animationName: 'image_tapped',          onAreaTapped: () {            print('image_tapped!');          }      ),      ActiveArea(          area: Rect.fromLTWH(0, animH / 2, animW, animH / 2),          debugArea: true,          animationsToCycle: ['activate', 'deactivate'],          onAreaTapped: () {            print('Button tapped!');          })    ];    return Scaffold(      appBar: AppBar(        title: Text('Flare Anim'),      ),      body: Container(        color: Color(0xffcccccc),        child: Align(          alignment: Alignment.bottomCenter,          child: SmartFlareActor(            width: animW,            height: animH,            filename: 'assets/button-animation.flr',            startingAnimation: 'deactivate',            activeAreas: activeAreas,          ),        ),      ),    );  }}

参考:
Flare动画实例教程
Flutter动画之Flare的制作与应用
我的项目源码