我正在为文字撰写时间表:
用法:
Text text = new Text();
......
group.getChildren().addAll(text);
root.getChildren().addAll(group);
tl.play();
这很好用。如果我想暂停并继续动画,tl.pause();
和tl.play();
可以做到这一点。
现在,我想让动画从头开始重新开始,我使用tl.stop();
和tl.playFromStart();
但这种组合的效果与tl.pause();
&的效果相同。 tl.play();
。
我的问题是,为什么tl.playFromStart();不能正常工作以及如何恢复动画?
答案 0 :(得分:2)
Timeline
工作原理
Timeline
表示执行动画的一段时间。 Timeline
包含KeyFrame
s的集合。每个KeyFrame
Timeline
(您传递的Duration
对象)的某个时间点KeyValue
s的集合
包含WritableValue
s(例如,Property
s)和目标
那个时间点WritableValue
的值EventHandler<ActionEvent>
Timeline
有currentTime
属性,当Timeline
正在播放时,(当然)随着时间的推移向前推进。 pause()
会停止currentTime
的进展,并将其固定为当前值。 stop()
会停止currentTime
的进展,并将currentTime
设置为零。
如果Timeline
具有指定KeyFrame
的{{1}}个,那么随着KeyValue
的更改,currentTime
在WritableValue
中指定} s将根据KeyValue
设置为值。 (具体来说,如果currentTime
s是可插值的,则该值将在WritableValue
的两个相邻KeyFrames
指定KeyValue
之间进行插值。否则该值将设置为&#34;最近的&#34; WritableValue
指定KeyFrame
的值。)
如果WritableValue
有Timeline
个指定操作(KeyFrame
s),那么随着EventHandler<ActionEvent>
超过该currentTime
指定的时间,该动作被调用。
为什么您的代码无法使用KeyFrame
或stop()
在您的情况下,您的playFromStart()
指定了一项操作,该操作会将新的转换添加到节点的转换列表中。请注意,这完全不依赖于KeyFrame
,除了每次currentTime
到达currentTime
时,都会添加一个新的转换(加上其实现的0.04 seconds
方法你没有表现出来)。因此,如果您shiftAndScale
时间轴,stop()
将重置为零,但由于此原因,节点没有任何反应。 (实际上,currentTime
仅在currentTime
和0
秒之间变化。)
您的代码的其他问题
您的代码存在问题,因为您有内存泄漏。 0.04
维持Node
ObservableList
秒。您正在添加到此列表(非常频繁),但从不删除任何内容。 Transform
非常聪明:它保留了一个隐藏的矩阵,这是所有变换的净效果;当您添加新转换时,它会将其存储在列表中,然后更新&#34; net&#34;矩阵与简单的矩阵乘法。因此,您不会在这里看到任何计算性能问题:从这个角度来看,它可以很好地扩展。但是,它确实存储了所有单独的转换(因为,例如,它支持稍后删除它们),因此如果你让它运行得足够长,你将最终耗尽内存。
您的代码的另一个(可能是次要的)问题是,当您组合所有这些转换时,您正在执行大量浮点运算。任何舍入错误最终都会累积。您应该尝试找到一种避免累积误差累积的技术。
修改代码的方法
要解决此问题,您可以选择以下几种方法:
如果动画是&#34;自然是周期性的&#34; (意味着它在某个固定时间后返回其起始状态,如旋转),然后根据该自然持续时间定义Node
。仅使用旋转作为一个简单示例,您可以执行以下操作:
Timeline
现在double secondsPerCompleteCycle = (360.0 / 0.75) * 0.04 ;
Rotate rotation = new Rotate(0, new Point3D(1, 0, 0));
group.getTransforms().add(rotation);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(secondsPerCompleteCycle),
new KeyValue(rotation.angleProperty(), 360, Interpolator.LINEAR)));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
会将timeline.stop()
设置为零,这样可以将旋转角度设置回初始值(也为零)。
如果动画不是自然重复,我会使用(整数类型)计数器来跟踪&#34;当前帧&#34;在您选择的任何时间单位中,然后将变换的值绑定到计数器。使用相同的示例,您可以执行
currentTime
您还可以考虑使用AnimationTimer
,具体取决于您的具体要求。不过,我会首先尝试其中一种技术。
在你的情况下,代数变得相当复杂(无论如何对我来说过于复杂)。每个动作都向节点添加三个变换;围绕x轴的平移,缩放和旋转。这些的4x4矩阵表示是:
double degreesPerFrame = 0.75 ;
LongProperty frameCount = new SimpleLongProperty();
Rotate rotation = new Rotate(0, new Point3D(1, 0, 0));
group.getTransforms().add(rotation);
rotation.angleProperty().bind(frameCount.multiply(degreesPerFrame));
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.04), e ->
frameCount.set(frameCount.get() + 1)));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
// to reset to the beginning:
timeline.stop();
frameCount.set(0L);
进行翻译,
1 0 0 tx
0 1 0 ty
0 0 1 0
0 0 0 1
表示比例,
sx 0 0 0
0 sy 0 0
0 0 1 0
0 0 0 1
轮换。
虽然计算这三者的净效果并不太难(只是将它们相乘),计算你应用这些任意次数得到的净矩阵超出了我(也许......) 。此外,您在x方向上翻译的数量正在发生变化,这几乎是不可能的。
因此,解决此问题的另一种方法是定义单个转换并将其应用于节点,然后在每个事件上对其进行修改。这看起来像
1 0 0 0
0 cos(t) -sin(t) 0
0 sin(t) cos(t) 0
0 0 0 1
如上所述,Affine transform = new Affine() ; // creates identity transform
node.getTransforms().add(transform);
Timeline timeline = new Timeline(Duration.seconds(0.04), event -> {
double shiftX = ... ;
double shiftY = ... ;
double scaleX = ... ;
double scaleY = ... ;
double angle = 0.75 ;
Affine change = new Affine();
change.append(new Translate(shiftX, shiftY));
change.append(new Scale(scaleX, scaleY));
change.append(new Rotate(angle, new Point3D(1, 0, 0)));
transform.append(change);
});
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
和stop()
将具有(几乎)相同的效果。 (唯一的区别是再次播放时第一次新更新的时间,pause()
它将是0.04秒,stop()
它会更少 - 直到下次更新暂停时才会保留。)但是要重置&#34;动画,你只是做
pause()
注意,通过使用这种技术,节点只应用了一个变换;我们只是在进步时更新该变换。舍入误差仍然存在,但至少代数是可行的:)。