在 Flutter 动画中更新动画值的正确方法是啥?
Posted
技术标签:
【中文标题】在 Flutter 动画中更新动画值的正确方法是啥?【英文标题】:What would be the proper way to update animation values in a Flutter animation?在 Flutter 动画中更新动画值的正确方法是什么? 【发布时间】:2019-04-14 01:08:21 【问题描述】:所以我试图在 Flutter 中创建一个动画,每次用户按下按钮时都需要不同的结果。
我根据Flutter Animations tutorial实现了以下代码,并创建了一个函数来更新它。
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));
);
,
)
],
)
);
如您所见,我正在更新_tween
的begin:
和end:
,但这似乎并没有改变动画。
那么我应该怎么做才能在用户每次按下按钮时创建一个“不同”的动画?
一般的想法是让动画彼此建立一个随机的新值,例如:
第一次旋转:0 -> 10 第二次旋转:10 -> 13 第三次旋转:13 -> 18 ...等所以我想知道是否可以更新动画,还是应该每次都创建一个新动画? 我能想到的另一件事是跟踪位置并每次使用相同的动画 (0.0 -> 100.0) 作为传输的百分比。
因此,我不会从 10 -> 15 创建新动画,而是执行以下操作:
currentValue = 10 + (15-10)/100*_animationController.value
【问题讨论】:
你能分享一下你的 setNewPosition 电话吗? 我的意思是你的构建方法会非常有用,因为现在,我觉得你的要求有点抽象。 我已经添加了构建实现。 它确实重置了动画,它也再次播放,但使用初始值而不是新的一次。 【参考方案1】:我将略过你的代码,专注于你真正要问的问题:
一般的想法是让动画彼此建立一个随机的新值,例如:
第一次旋转:0 -> 10
第二次旋转:10 -> 13
第三次旋转:13 -> 18
...等
通过这样的显式动画,您对三个对象感兴趣:
一个控制器,它是一种特殊的动画,它简单地从其下限线性生成值到其上限(均为双精度值,通常为 0.0 和 1.0)。您可以控制动画的流程 - 使其向前运行、反转、停止或重置。
tween,不是动画,而是Animatable。补间定义了两个值之间的插值,甚至不必是数字。它在底层实现了一个transform
方法,该方法接收动画的当前值并输出您想要使用的实际值:另一个数字、颜色、线性渐变,甚至是整个小部件。这就是您应该用来生成旋转角度的方法。
一个 animation,它是您实际要使用其值的动画(所以这是您获取要构建的值的地方)。你可以通过给你的补间动画一个父动画来转换 - 这可能是你的控制器直接,但也可以是你在它上面构建的其他类型的动画(比如一个 CurvedAnimation,它会给你缓动或有弹性/弹性曲线和很快)。 Flutter 的动画以这种方式高度可组合。
您的代码失败主要是因为您实际上并没有使用您在构建方法中创建的***动画并且您每次调用 setNewPosition
时都会创建一个新的补间和动画.您可以对多个动画“周期”使用相同的补间和动画 - 只需更改现有补间的 begin
和 end
属性,它就会冒泡到动画。最终是这样的:
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,
)
],
)
);
希望有帮助!
【讨论】:
这不仅回答了我的问题,而且您的解释确实有助于我理解整个概念。有趣的是,在我尝试创建一个新的Tween
之前,我基本上已经有了这个,除了读出正确的动画对象!
不客气!一旦你很好地掌握了 Flutter 如何处理动画,你应该看看AnimatedBuilder
小部件:)
我会的!我看到了各种有趣的小部件来试验。【参考方案2】:
在工作时,在任何情况下,您都不会真正想要在您的布局中制作这些动画,正如@filluchaos 所解释的那样。
这未得到优化,因为您为动画重建的内容远远超过了应有的内容。而且自己写也很痛苦。
您需要为此使用AnimatedWidget
系列。它们分为两个
种类:
第一个是低层,它消耗Animation
并监听它,这样你就不需要做那些丑陋的事情了:
..addListener(()
setState(() );
);
第二个处理剩余部分:AnimationController
、TickerProvider
和 Tween
。
这使得使用动画更加更容易,因为它几乎是完全自动的。
在您的情况下,轮换示例如下:
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);
);
比较简单吧?
具有讽刺意味的是,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,
);
我会提交一个拉取请求,这样你以后就不需要这段代码了。
【讨论】:
嗯,他们显然在努力理解 explicit 动画。您并不总是希望小部件隐式地为自己设置动画,最好弄清楚 Animation/Animatable 堆栈如何与像双精度一样简单的东西一起工作。 但在生产中,这是您想要使用的,而不是之前的代码。他正在学习的事实并不能证明部分答案是正确的。 他们正在关注一个明确关于显式动画的官方教程,并寻求帮助。我不明白为什么解释他们为什么坚持他们关于显式动画的教程是部分答案。 教程的其余部分就在那里,也涉及到 AnimatedWidget 和 AnimatedBuilder。 你不只是重建Image
。您实际上重建了_RoulettePageWidgetState
中的所有内容,包括按钮和所有其他小部件。使用我提供的代码,动画重建仅变换,没有别的。【参考方案3】:
如果您想使用不同的路径(返回/返回方式)反转动画。试试这个。
在您的setNewPosition
函数中,只需为_tween
定义新的begin/end
值。
void setNewPosition()
_tween.begin = 0; //new begin int value
_tween.end = 1; //new end int value
_animationController.reverse();
【讨论】:
以上是关于在 Flutter 动画中更新动画值的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章