FLUTTER - 如何防止 RiveAnimationController 在播放结束时重置?

发布于 2025-01-09 05:34:28 字数 1810 浏览 1 评论 0原文

我使用 Rive 包是为了在我的 Flutter 应用程序中拥有一些漂亮的动画,但我有两个疑问:

我有一个简单的动画,其中一些文档会被动画化。我想在点击时播放此动画,因此我使用 OneShotAnimation。点击播放可以正常工作,但是当动画结束时,它会立即重置为第一帧。
此外,当我加载页面时,动画是从最后一帧加载的。
如何避免这2个问题呢?

我的代码:

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

class Square extends StatefulWidget {
  final Widget page;
  final String title;

  const Square({
    required this.page,
    required this.title,
    Key? key,
  }) : super(key: key);

  @override
  State<Square> createState() => _SquareState();
}

class _SquareState extends State<Square> {
  late RiveAnimationController _esamiController;
  bool _isPlaying = false;

  @override
  void initState() {
    _esamiController = OneShotAnimation(
      'Animation 1',
      autoplay: false,
      onStart: () => setState(() => _isPlaying = true),
      onStop: () => setState(() => _isPlaying = false),
    );
    super.initState();
  }

  @override
  void dispose() {
    _esamiController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () => _isPlaying ? null : _esamiController.isActive = true,
      child: SizedBox(
        width: 192,
        height: 192,
        child: Card(
          color: Colors.black26,
          elevation: 10,
          child: Center(
            child: RiveAnimation.asset(
              'assets/animations/esami.riv',
              controllers: [_esamiController],
              onInit: (_) => setState(() {}),
            ),
          ),
        ),
      ),
    );
  }
}

“动画不起作用"正如您所看到的,工作表应该开始无序并结束有序,而在这里我得到相反的结果。

I am using the Rive package in order to have some nice animations within my Flutter application and I have 2 doubts:

I have a simple animation where some docs gets animated. I want to play this animation on Tap of it, so I'm using OneShotAnimation. The play on tap works, however when the animation ends, it immediately gets reset to the first frame.
When I load the page, in addition, the animation is loaded from the last frame.
How to avoid those 2 problems?

My code:

import 'package:flutter/material.dart';
import 'package:rive/rive.dart';

class Square extends StatefulWidget {
  final Widget page;
  final String title;

  const Square({
    required this.page,
    required this.title,
    Key? key,
  }) : super(key: key);

  @override
  State<Square> createState() => _SquareState();
}

class _SquareState extends State<Square> {
  late RiveAnimationController _esamiController;
  bool _isPlaying = false;

  @override
  void initState() {
    _esamiController = OneShotAnimation(
      'Animation 1',
      autoplay: false,
      onStart: () => setState(() => _isPlaying = true),
      onStop: () => setState(() => _isPlaying = false),
    );
    super.initState();
  }

  @override
  void dispose() {
    _esamiController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () => _isPlaying ? null : _esamiController.isActive = true,
      child: SizedBox(
        width: 192,
        height: 192,
        child: Card(
          color: Colors.black26,
          elevation: 10,
          child: Center(
            child: RiveAnimation.asset(
              'assets/animations/esami.riv',
              controllers: [_esamiController],
              onInit: (_) => setState(() {}),
            ),
          ),
        ),
      ),
    );
  }
}

Animation not working
As you can see the sheets should start unordered and end ordered, while here I get the opposite.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

白云不回头 2025-01-16 05:34:28

我使用了您的代码和示例 rive 的社区制作的动画来重现该问题。如果我正确理解您的需求,这里有两个解决方案:

1-如果您查看您在应用程序中使用的 rive 控制器(OneShotAnimation)的源代码:

one_shot_controller.dart

import 'package:flutter/widgets.dart';
import 'package:rive/src/controllers/simple_controller.dart';

/// Controller tailered for managing one-shot animations
class OneShotAnimation extends SimpleAnimation {
  /// Fires when the animation stops being active
  final VoidCallback? onStop;

  /// Fires when the animation starts being active
  final VoidCallback? onStart;

  OneShotAnimation(
    String animationName, {
    double mix = 1,
    bool autoplay = true,
    this.onStop,
    this.onStart,
  }) : super(animationName, mix: mix, autoplay: autoplay) {
    isActiveChanged.addListener(onActiveChanged);
  }

  /// Dispose of any callback listeners
  @override
  void dispose() {
    super.dispose();
    isActiveChanged.removeListener(onActiveChanged);
  }

  /// Perform tasks when the animation's active state changes
  void onActiveChanged() {
    // If the animation stops and it is at the end of the one-shot, reset the
    // animation back to the starting time
    if (!isActive) {
      reset();
    }
    // Fire any callbacks
    isActive
        ? onStart?.call()
        // onStop can fire while widgets are still drawing
        : WidgetsBinding.instance?.addPostFrameCallback((_) => onStop?.call());
  }
}

正如我们在源代码中看到的,它在动画结束时重置动画。因此,如果您想使用OneShotAnimation,则无需执行任何操作。唯一的方法是您可以分叉源代码并更改相关行。并将修改版本添加到您的项目中。

one_shot_controller.dart

import 'package:flutter/widgets.dart';
import 'package:rive/src/controllers/simple_controller.dart';

/// Controller tailered for managing one-shot animations
class OneShotAnimation extends SimpleAnimation {
  /// Fires when the animation stops being active
  final VoidCallback? onStop;

  /// Fires when the animation starts being active
  final VoidCallback? onStart;

  OneShotAnimation(
    String animationName, {
    double mix = 1,
    bool autoplay = true,
    this.onStop,
    this.onStart,
  }) : super(animationName, mix: mix, autoplay: autoplay) {
    isActiveChanged.addListener(onActiveChanged);
  }

  /// Dispose of any callback listeners
  @override
  void dispose() {
    super.dispose();
    isActiveChanged.removeListener(onActiveChanged);
  }

  /// Perform tasks when the animation's active state changes
  void onActiveChanged() {
    // Fire any callbacks
    isActive
        ? onStart?.call()
        // onStop can fire while widgets are still drawing
        : WidgetsBinding.instance?.addPostFrameCallback((_) => onStop?.call());
  }
}

我已经尝试过了,它可以按照你的要求工作。但第二次运行动画可能会出现问题。为此,请检查第二个解决方案。

2-您可以使用简单动画。请检查以下解决方案:

class Square extends StatefulWidget {
  final String title;

  const Square({
    required this.title,
    Key? key,
  }) : super(key: key);

  @override
  State<Square> createState() => _SquareState();
}

class _SquareState extends State<Square> {
  late SimpleAnimation _esamiController;

  bool get isPlaying => _esamiController.isActive;

  @override
  void initState() {
    _esamiController = SimpleAnimation('bell', autoplay: false);

    super.initState();
  }

  void _reset() {
    if (!isPlaying) {
      _esamiController.reset();
    }
  }

  Future<void> _togglePlay() async {
    if (isPlaying) return;

    _reset();
    _esamiController.isActive = true;

    await Future.delayed(
      const Duration(milliseconds: 20),
    );

    _esamiController.isActive = true;
  }

  @override
  void dispose() {
    _esamiController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: InkWell(
            onTap: _togglePlay,
            child: SizedBox(
              width: 192,
              height: 192,
              child: Card(
                color: Colors.black26,
                elevation: 10,
                child: Center(
                  child: RiveAnimation.asset(
                    'assets/alarm.riv',
                    controllers: [_esamiController],
                    onInit: (_) => setState(() {}),
                  ),
                ),
              ),
            ),
          ),
        ),
      );
}

如您所见,我已将 RiveAnimationController 更改为 SimpleAnimation 来访问 reset 方法。因为另外一种方式,动画一旦运行起来,就没有办法通过RiveAnimationController再次运行它。

如果您还有其他问题,请随时在评论中写下。

注意:不要忘记修改 animationName 并在 RiveAnimation.asset 小部件中提供正确的资源目录。

I have used your code and a sample rive's community-made animation to reproduce the issue. If I understood your needs right, here are the two solutions:

1- If you take a look at the source code of rive's controller(OneShotAnimation) that you use in your application:

one_shot_controller.dart

import 'package:flutter/widgets.dart';
import 'package:rive/src/controllers/simple_controller.dart';

/// Controller tailered for managing one-shot animations
class OneShotAnimation extends SimpleAnimation {
  /// Fires when the animation stops being active
  final VoidCallback? onStop;

  /// Fires when the animation starts being active
  final VoidCallback? onStart;

  OneShotAnimation(
    String animationName, {
    double mix = 1,
    bool autoplay = true,
    this.onStop,
    this.onStart,
  }) : super(animationName, mix: mix, autoplay: autoplay) {
    isActiveChanged.addListener(onActiveChanged);
  }

  /// Dispose of any callback listeners
  @override
  void dispose() {
    super.dispose();
    isActiveChanged.removeListener(onActiveChanged);
  }

  /// Perform tasks when the animation's active state changes
  void onActiveChanged() {
    // If the animation stops and it is at the end of the one-shot, reset the
    // animation back to the starting time
    if (!isActive) {
      reset();
    }
    // Fire any callbacks
    isActive
        ? onStart?.call()
        // onStop can fire while widgets are still drawing
        : WidgetsBinding.instance?.addPostFrameCallback((_) => onStop?.call());
  }
}

As we can see in the source code, it resets the animation when it ends. So there is nothing to do if you want to use OneShotAnimation. The only way is that you can fork the source code and change the related line. And add a modified version to your project.

one_shot_controller.dart

import 'package:flutter/widgets.dart';
import 'package:rive/src/controllers/simple_controller.dart';

/// Controller tailered for managing one-shot animations
class OneShotAnimation extends SimpleAnimation {
  /// Fires when the animation stops being active
  final VoidCallback? onStop;

  /// Fires when the animation starts being active
  final VoidCallback? onStart;

  OneShotAnimation(
    String animationName, {
    double mix = 1,
    bool autoplay = true,
    this.onStop,
    this.onStart,
  }) : super(animationName, mix: mix, autoplay: autoplay) {
    isActiveChanged.addListener(onActiveChanged);
  }

  /// Dispose of any callback listeners
  @override
  void dispose() {
    super.dispose();
    isActiveChanged.removeListener(onActiveChanged);
  }

  /// Perform tasks when the animation's active state changes
  void onActiveChanged() {
    // Fire any callbacks
    isActive
        ? onStart?.call()
        // onStop can fire while widgets are still drawing
        : WidgetsBinding.instance?.addPostFrameCallback((_) => onStop?.call());
  }
}

I have already tried it out, and it works as you wanted. But running animation for the second time could be problematic. For that, please check the second solution.

2- You can use SimpleAnimation. Please check the following solution:

class Square extends StatefulWidget {
  final String title;

  const Square({
    required this.title,
    Key? key,
  }) : super(key: key);

  @override
  State<Square> createState() => _SquareState();
}

class _SquareState extends State<Square> {
  late SimpleAnimation _esamiController;

  bool get isPlaying => _esamiController.isActive;

  @override
  void initState() {
    _esamiController = SimpleAnimation('bell', autoplay: false);

    super.initState();
  }

  void _reset() {
    if (!isPlaying) {
      _esamiController.reset();
    }
  }

  Future<void> _togglePlay() async {
    if (isPlaying) return;

    _reset();
    _esamiController.isActive = true;

    await Future.delayed(
      const Duration(milliseconds: 20),
    );

    _esamiController.isActive = true;
  }

  @override
  void dispose() {
    _esamiController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        body: Container(
          alignment: Alignment.center,
          child: InkWell(
            onTap: _togglePlay,
            child: SizedBox(
              width: 192,
              height: 192,
              child: Card(
                color: Colors.black26,
                elevation: 10,
                child: Center(
                  child: RiveAnimation.asset(
                    'assets/alarm.riv',
                    controllers: [_esamiController],
                    onInit: (_) => setState(() {}),
                  ),
                ),
              ),
            ),
          ),
        ),
      );
}

As you can see, I have changed RiveAnimationController with the SimpleAnimation to access the reset method. Because another way, once the animation runs, there is no way to run it for a second time through RiveAnimationController.

If you have further problems, please don't hesitate to write in the comments.

Note: Don't forget to modify animationName and provide a correct asset directory in RiveAnimation.asset widget.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文