Rive动画使用介绍(Flutter)

感觉官方的案例对我这种level的开发者来说感觉的真的很晦涩,也没什么像样的中文文档,所以做了些尝试,大致理解了一些基本的使用方式。有些较为复杂的素材琢磨不明白,然后把自己所知的做一个总结吧。

首先是通过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

  State<StatefulWidget> createState() {

    return _SimepleAnimationExampleState();

  }

}

class _SimepleAnimationExampleState extends State<SimpleAnimationExample> {

  String pathRive = '';

  @override

  void initState() {

    super.initState();

    getFile().then((value) {

      setState(() {

        pathRive = value;

      });

    });

  }

  Future<String> getFile() 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<PlayPauseAnimationExample> {

  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<PlayOneShotExample> {

  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<SpeedControllExample> {

  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<DownloadProgressExample> {

  // SMIInput<bool> start;

  // SMIInput<double> progeress;



  Artboard? _riveArtboard;



  // SMIInput<bool>? _start;

  SMITrigger? _start;

  SMIInput<double>? _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 Animatable<double> sizeTween = Tween<double>(

    begin: _disabledThumbSize,

    end: _thumbSize,

  );



  @override

  void paint(PaintingContext context, Offset center,

      {required Animation<double> activationAnimation,

      required Animation<double> enableAnimation,

      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 Animatable<double> sizeTween = Tween<double>(

    begin: _disabledIndicatorSize,

    end: _indicatorSize,

  );



  @override

  void paint(PaintingContext context, Offset center,

      {required Animation<double> activationAnimation,

      required Animation<double> enableAnimation,

      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 Tween<double> slideUpTween = Tween<double>(

      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<MoreTypeStateChangeExample> {

  Artboard? _riveWomenArtboard;

  SMIInput<double>? _levelInput;



  Artboard? _riveMenArtboard;

  SMITrigger? _crossInput;

  SMITrigger? _jabInput;

  SMITrigger? _kickInput;



  SMIInput<bool>? _runInput;

  SMIInput<double>? _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'],

    ),

  ),

),

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值