在Flutter动画中更新动画值的正确方法是什么?

SEG*_*tra 5 animation dart flutter flutter-animation

因此,我正在尝试在Flutter中创建一个动画,每次用户按下按钮时都需要不同的结果.

我根据Flutter Animations教程实现了以下代码,并创建了一个更新它的函数.

class _RoulettePageWidgetState extends State<RoulettePageWidget>
with SingleTickerProviderStateMixin {
   Animation<double> _animation;
   Tween<double> _tween;
   AnimationController _animationController;

   int position = 0;

   @override
   void initState() {
      super.initState();
      _animationController =
          AnimationController(duration: Duration(seconds: 2), vsync: this);
      _tween = Tween(begin: 0.0, end: 100.0);
      _animation = _tween.animate(_animationController)
          ..addListener(() {
              setState(() {});
          });
   }

   void setNewPosition(int newPosition) {
      _tween = Tween(
        begin: 0.0,
        end: math.pi*2/25*newPosition);
      _animationController.reset();
      _tween.animate(_animationController);
      _animationController.forward();
   }

   @override
   Widget build(BuildContext context) {
      return Container(
         child: Column(
            children: <Widget>[
               Center(
                  child: Transform.rotate(
                     angle: _animationController.value,
                     child: Icon(
                        Icons.arrow_upward,
                     size: 250.0,
                  ),
               )),
               Expanded(
                  child: Container(),
               ),
               RaisedButton(
                  child: Text('SPIN'),
                  onPressed: () {
                     setState(() {
                        setNewPosition(math.Random().nextInt(25));
                     });
                  },
               )
            ],
         )
      );
   }
}
Run Code Online (Sandbox Code Playgroud)

你可以看到我正在更新_tween's begin:,end:但这似乎并没有改变动画.

那么每次用户按下按钮时,我应该怎么做才能创建一个"不同的"动画呢?

一般的想法是使动画以随机的新值相互构建,例如:

  • 第一次旋转:0 - > 10
  • 第二次旋转:10 - > 13
  • 第三次旋转:13 - > 18
  • ......等

所以我想知道我是否可以更新动画,还是每次都要创建一个新的动画?我能想到的另一件事是跟踪位置并每次使用相同的动画(0.0 - > 100.0)作为转移的百分比.

因此,不是从10 - > 15创建一个新的动画,我会做类似的事情: currentValue = 10 + (15-10)/100*_animationController.value

fil*_*aos 13

我将稍微跳过你的代码,并专注于你真正的问题:

一般的想法是使动画以随机的新值相互构建,例如:

  • 第一次旋转:0 - > 10

  • 第二次旋转:10 - > 13

  • 第三次旋转:13 - > 18

  • ......等

使用这样的显式动画,您有三个感兴趣的对象:

  • 一个控制器,它是一种特殊的动画的,简单地线性地产生的值从下到它的上界(包括双打,典型地在0.0和1.0).您可以控制动画的流程 - 向前发送,反向,停止或重置动画.

  • 一个补间,这是不是一个动画,而是一个可动画.补间定义了两个值之间的插值,这些值甚至不必是数字.它实现了一个transform引擎盖下的方法,它接收动画的当前值并吐出你想要使用的实际值:另一个数字,一个颜色,一个线性渐变,甚至整个小部件.这是您应该用来生成旋转角度的.

  • 一个动画,它是你实际要使用它的值的动画(所以这是你要用它来获取值的地方).你可以通过给你的补间父变换动画得到这个 - 这可能是你的控制器直接但也可以是你在它上面构建的其他类型的动画(如CurvedAnimation,它会给你缓和或弹性/弹性曲线和等等).Flutter的动画就是这样的高度可组合.

您的代码在很大程度上是失败的,因为你没有实际使用您在构建方法创建的顶级动画你每次调用时创建一个新的补间和动画setNewPosition.您可以对多个动画"循环"使用相同的补间和动画 - 只需更改现有补间的beginend属性,它就会冒泡到动画.最终结果如下:

class _RoulettePageWidgetState extends State<RoulettePageWidget>
with SingleTickerProviderStateMixin {
   Animation<double> _animation;
   Tween<double> _tween;
   AnimationController _animationController;
   math.Random _random = math.Random();

   int position = 0;

   double getRandomAngle() {
      return math.pi * 2 / 25 * _random.nextInt(25);
   }

   @override
   void initState() {
      super.initState();
      _animationController =
          AnimationController(duration: Duration(seconds: 2), vsync: this);
      _tween = Tween(begin: 0.0, end: getRandomAngle());
      _animation = _tween.animate(_animationController)
          ..addListener(() {
              setState(() {});
          });
   }

   void setNewPosition() {
      _tween.begin = _tween.end;
      _animationController.reset();
      _tween.end = getRandomAngle();
      _animationController.forward();
   }

   @override
   Widget build(BuildContext context) {
      return Container(
         child: Column(
            children: <Widget>[
               Center(
                  child: Transform.rotate(
                     angle: _animation.value,
                     child: Icon(
                        Icons.arrow_upward,
                     size: 250.0,
                  ),
               )),
               Expanded(
                  child: Container(),
               ),
               RaisedButton(
                  child: Text('SPIN'),
                  onPressed: setNewPosition,
               )
            ],
         )
      );
   }
}
Run Code Online (Sandbox Code Playgroud)

希望有所帮助!

  • 这不仅回答了我的问题,而且您的解释确实帮助我理解了整个概念。有趣的是,在我尝试创建新的“Tween”之前,我基本上已经有了这个,除了读出正确的动画对象! (3认同)

Rém*_*let 6

在工作时,在任何情况下您都不会真正想要布局中制作这些动画,如 @filleduchaos 所解释的。

这是优化不足的,因为您重建的内容远远超出了动画应有的程度。而且自己写也很痛苦。

你会想利用AnimatedWidget家庭来做到这一点。它们分为两种:

  • XX过渡
  • 动画XX

第一个是一个低层,它消耗Animation并监听它,这样你就不需要做那么难看的事情:

..addListener(() {
  setState(() {});
});
Run Code Online (Sandbox Code Playgroud)

第二个处理剩余部分:AnimationControllerTickerProviderTween

这使得使用动画变得更加容易,因为它几乎是完全自动的。

在您的情况下,旋转示例如下:

class RotationExample extends StatefulWidget {
  final Widget child;

  const RotationExample({
    Key key,
    this.child,
  }) : super(key: key);

  @override
  RotationExampleState createState() {
    return new RotationExampleState();
  }
}

class RotationExampleState extends State<RotationExample> {
  final _random = math.Random();
  double rad = 0.0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _rotate,
      child: AnimatedTransform(
        duration: const Duration(seconds: 1),
        alignment: Alignment.center,
        transform: Matrix4.rotationZ(rad),
        child: Container(
          color: Colors.red,
          height: 42.0,
          width: 42.0,
        ),
      ),
    );
  }

  void _rotate() {
    setState(() {
      rad = math.pi * 2 / 25 * _random.nextInt(25);
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

更容易吧?

具有讽刺意味的是,Flutter 忘记提供一个AnimatedTransform(尽管我们还有很多其他的!)。但不用担心,我为你做的!

实现AnimatedTransform如下:

class AnimatedTransform extends ImplicitlyAnimatedWidget {
  final Matrix4 transform;
  final AlignmentGeometry alignment;
  final bool transformHitTests;
  final Offset origin;
  final Widget child;

  const AnimatedTransform({
    Key key,
    @required this.transform,
    @required Duration duration,
    this.alignment,
    this.transformHitTests = true,
    this.origin,
    this.child,
    Curve curve = Curves.linear,
  })  : assert(transform != null),
        assert(duration != null),
        super(
          key: key,
          duration: duration,
          curve: curve,
        );

  @override
  _AnimatedTransformState createState() => _AnimatedTransformState();
}

class _AnimatedTransformState
    extends AnimatedWidgetBaseState<AnimatedTransform> {
  Matrix4Tween _transform;

  @override
  void forEachTween(TweenVisitor<dynamic> visitor) {
    _transform = visitor(_transform, widget.transform,
        (dynamic value) => Matrix4Tween(begin: value));
  }

  @override
  Widget build(BuildContext context) {
    return Transform(
      alignment: widget.alignment,
      transform: _transform.evaluate(animation),
      transformHitTests: widget.transformHitTests,
      origin: widget.origin,
      child: widget.child,
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

我将提交一个拉取请求,以便将来您不需要这段代码。