了JavaFx。怎么恢复?

时间:2015-05-27 17:33:52

标签: java javafx

我正在为文字撰写时间表:

用法:

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();不能正常工作以及如何恢复动画?

1 个答案:

答案 0 :(得分:2)

Timeline工作原理

Timeline表示执行动画的一段时间。 Timeline包含KeyFrames的集合。每个KeyFrame

  • 必须指定Timeline(您传递的Duration对象)的某个时间点
  • 可能也可以选择指定KeyValues的集合 包含WritableValue s(例如,Property s)和目标 那个时间点WritableValue的值
  • 可以选择性地指定要执行的操作 EventHandler<ActionEvent>

TimelinecurrentTime属性,当Timeline正在播放时,(当然)随着时间的推移向前推进。 pause()会停止currentTime的进展,并将其固定为当前值。 stop()会停止currentTime的进展,并将currentTime设置为零。

如果Timeline具有指定KeyFrame的{​​{1}}个,那么随着KeyValue的更改,currentTimeWritableValue中指定} s将根据KeyValue设置为值。 (具体来说,如果currentTime s是可插值的,则该值将在WritableValue的两个相邻KeyFrames指定KeyValue之间进行插值。否则该值将设置为&#34;最近的&#34; WritableValue指定KeyFrame的值。)

如果WritableValueTimeline个指定操作(KeyFrame s),那么随着EventHandler<ActionEvent>超过该currentTime指定的时间,该动作被调用。

为什么您的代码无法使用KeyFramestop()

在您的情况下,您的playFromStart()指定了一项操作,该操作会将新的转换添加到节点的转换列表中。请注意,这完全不依赖于KeyFrame,除了每次currentTime到达currentTime时,都会添加一个新的转换(加上其实现的0.04 seconds方法你没有表现出来)。因此,如果您shiftAndScale时间轴,stop()将重置为零,但由于此原因,节点没有任何反应。 (实际上,currentTime仅在currentTime0秒之间变化。)

您的代码的其他问题

您的代码存在问题,因为您有内存泄漏。 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()

注意,通过使用这种技术,节点只应用了一个变换;我们只是在进步时更新该变换。舍入误差仍然存在,但至少代数是可行的:)。