JavaFX 动画
最后修改于 2023 年 10 月 18 日
在本章中,我们使用 JavaFX 中的动画。 我们使用 AnimationTimer
, Transition
和 Timeline
创建动画。
动画 是图像的快速连续,产生运动的幻觉。 然而,动画并不局限于运动。 随着时间的推移改变节点的背景也被认为是一种动画。
JavaFX 提供了三个基本工具来创建动画
- AnimationTimer
- Transition
- Timeline
AnimationTimer
是创建动画的最简单的工具。 它是一个基本的计时器; 它的 handle
方法在动画的每一帧中被调用。 Transition
是一个定义动画的基本高级框架。 动画由 interpolate
方法的 frac
值控制。 Timeline
是用于进行高级动画的最复杂的工具。 Timeline
动画使用 KeyFrames
定义,它概述了节点在指定时间点的目标值,对于沿 Timeline
插值的变量集。 动画属性由 KeyValues
定义。
Animation 类
Animation
是 JavaFX 中定义高级动画的基本类。 Transition
和 Timeline
都扩展了 Animation
。 动画通过 play
或 playFromStart
方法启动,并通过 stop
方法结束。 可以通过调用 pause
方法暂停动画,并且下一次 play
调用会从暂停的位置恢复动画。 rate
属性定义了动画预计播放的方向和速度。 delay
属性指定动画的初始延迟量。 动画可以循环运行; 循环次数在 cycleCount
属性中定义,cycleDuration
指示一个循环的持续时间。 可以使用 autoReverseProperty
在交替循环中反转动画。
JavaFX AnimationTimer
AnimationTimer
允许创建一个计时器,该计时器在它处于活动状态时在每一帧中被调用。 它是一个抽象类; 因此,我们需要创建一个扩展它的自定义类。 它的 handle
方法(在每一帧中被调用)必须被重写。 AnimationTimer
的 start
方法启动计时器,而 stop
方法停止它。
package com.zetcode; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.layout.StackPane; import javafx.scene.text.Font; import javafx.stage.Stage; public class AnimationTimerEx extends Application { private double opacity = 1; private Label lbl; @Override public void start(Stage stage) { initUI(stage); } private void initUI(Stage stage) { var root = new StackPane(); lbl = new Label("JavaFX"); lbl.setFont(Font.font(48)); root.getChildren().add(lbl); AnimationTimer timer = new MyTimer(); timer.start(); var scene = new Scene(root, 300, 250); stage.setTitle("AnimationTimer"); stage.setScene(scene); stage.show(); } private class MyTimer extends AnimationTimer { @Override public void handle(long now) { doHandle(); } private void doHandle() { opacity -= 0.01; lbl.opacityProperty().set(opacity); if (opacity <= 0) { stop(); System.out.println("Animation stopped"); } } } public static void main(String[] args) { launch(args); } }
该示例使用 AnimationTimer
在节点上创建淡出效果。
lbl = new Label("JavaFX"); lbl.setFont(Font.font(48)); root.getChildren().add(lbl);
我们的动画更改此 Label
控件的属性。
AnimationTimer timer = new MyTimer(); timer.start();
创建一个 AnimationTimer
并调用其 start
方法。
private class MyTimer extends AnimationTimer { @Override public void handle(long now) { doHandle(); } ... }
我们创建了 AnimationTimer
的一个具体子类并重写了它的 handle
方法。
private void doHandle() { opacity -= 0.01; lbl.opacityProperty().set(opacity); if (opacity <= 0) { stop(); System.out.println("Animation stopped"); } }
在 doHandle
方法中,我们减少了 opacity
变量并更新了 opacityProperty
。 如果 opacity
达到其最小值,则计时器会使用其 stop
方法停止。
JavaFX FadeTransition
Transition
动画最适合计划好的动画。 Transition
具有可以用于创建各种动画的具体类,这些动画可以并行或顺序执行; 例如 FadeTransition
, PathTransition
, RotateTransition
或 ScaleTransition
。
FadeTransition
创建一个淡入淡出效果的动画,该动画跨越其持续时间。 这是通过以规则的间隔更新节点的 opacity
变量来完成的。
package com.zetcode; import javafx.animation.Animation; import javafx.animation.FadeTransition; import javafx.application.Application; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.input.MouseEvent; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Duration; public class FadeTransitionEx extends Application { private FadeTransition ft; private Rectangle rect; @Override public void start(Stage stage) { initUI(stage); } private void initUI(Stage stage) { var root = new Group(); rect = new Rectangle(20, 20, 150, 150); rect.setOnMouseClicked(new RectClickHandler()); ft = new FadeTransition(Duration.millis(5000), rect); ft.setFromValue(1.0); ft.setToValue(0.0); root.getChildren().add(rect); var scene = new Scene(root, 300, 250); stage.setTitle("Fading transition"); stage.setScene(scene); stage.show(); } private class RectClickHandler implements EventHandler<MouseEvent> { @Override public void handle(MouseEvent event) { doHandle(); } private void doHandle() { double opa = rect.getOpacity(); if ((int) opa == 0) { return; } Animation.Status as = ft.getStatus(); if (as == Animation.Status.RUNNING) { return; } if (as == Animation.Status.STOPPED) { ft.play(); } } } public static void main(String[] args) { Application.launch(args); } }
此示例使用 FadeTransition
在一个矩形上创建一个淡出效果。 在矩形区域内单击鼠标后,动画开始。
rect = new Rectangle(20, 20, 150, 150); rect.setOnMouseClicked(new RectClickHandler());
将鼠标单击处理程序设置为矩形。
ft = new FadeTransition(Duration.millis(5000), rect);
创建 FadeTransition
。 它的第一个参数是过渡的持续时间。 第二个参数是要更新其 opacity
参数的节点。
ft.setFromValue(1.0); ft.setToValue(0.0);
setFromValue
设置不透明度的起始值,而 setToValue
设置结束不透明度值。
double opa = rect.getOpacity();
当前的不透明度值由 getOpacity
方法确定。
if (opa.intValue() == 0) { return; }
矩形淡出后,我们取消鼠标单击。
Animation.Status as = ft.getStatus(); if (as == Animation.Status.RUNNING) { return; } if (as == Animation.Status.STOPPED) { ft.play(); }
getStatus
方法确定过渡的状态。 如果状态是 Animation.Status.STOPPED
,我们使用 play
方法启动过渡。
PathTransition
PathTransition
沿路径创建动画。 沿路径的平移是通过更新节点的 translateX
和 translateY
变量来完成的。 请注意,我们必须使用支持元素绝对定位的节点。
package com.zetcode; import javafx.animation.PathTransition; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.CubicCurveTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; import javafx.stage.Stage; import javafx.util.Duration; public class PathTransitionEx extends Application { @Override public void start(Stage stage) { initUI(stage); } private void initUI(Stage stage) { var root = new Pane(); var path = new Path(); path.getElements().add(new MoveTo(20, 120)); path.getElements().add(new CubicCurveTo(180, 60, 250, 340, 420, 240)); var circle = new Circle(20, 120, 10); circle.setFill(Color.CADETBLUE); var ptr = new PathTransition(); ptr.setDuration(Duration.seconds(6)); ptr.setDelay(Duration.seconds(2)); ptr.setPath(path); ptr.setNode(circle); ptr.setCycleCount(2); ptr.setAutoReverse(true); ptr.play(); root.getChildren().addAll(path, circle); var scene = new Scene(root, 450, 300); stage.setTitle("PathTransition"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
该示例使用 PathTransition
沿路径移动一个圆。 动画在初始延迟 2 秒后开始。 它由两个循环组成。 动画被反转; 也就是说,圆从起始点移动到结束点,然后返回。
var root = new Pane();
我们使用 Pane
作为我们的根节点。 它支持动画所需的绝对定位。
var path = new Path(); path.getElements().add(new MoveTo(20, 120)); path.getElements().add(new CubicCurveTo(180, 60, 250, 340, 420, 240));
在这里,我们定义了动画对象将沿其移动的 Path
。
var circle = new Circle(20, 120, 10); circle.setFill(Color.CADETBLUE);
这个圆是我们的动画中的移动对象。
var ptr = new PathTransition();
创建一个 PathTransition
对象。
ptr.setDuration(Duration.seconds(6));
setDuration
方法设置动画的持续时间。
ptr.setDelay(Duration.seconds(2));
setDelay
方法设置动画的初始延迟。
ptr.setPath(path); ptr.setNode(circle);
setPath
方法设置路径,setNode
设置动画的目标节点。
ptr.setCycleCount(2);
我们的动画有两个循环。 循环次数由 setCycleCount
方法设置。
ptr.setAutoReverse(true);
使用 setAutoReverse
方法,我们反转动画的方向。 圆圈向后移动到起始位置。
ptr.play();
最后,play
方法启动动画。

ParallelTransition
ParallelTransition
并行播放一系列 Animations
。
package com.zetcode; import javafx.animation.FillTransition; import javafx.animation.ParallelTransition; import javafx.animation.RotateTransition; import javafx.animation.ScaleTransition; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Duration; public class ParallelTransitionEx extends Application { @Override public void start(Stage stage) { initUI(stage); } private void initUI(Stage stage) { var root = new Pane(); var rect = new Rectangle(50, 50, 30, 30); rect.setArcHeight(10); rect.setArcWidth(10); rect.setFill(Color.CADETBLUE); var rottr = new RotateTransition(Duration.millis(2000), rect); rottr.setByAngle(180); rottr.setCycleCount(2); rottr.setAutoReverse(true); var sctr = new ScaleTransition(Duration.millis(2000), rect); sctr.setByX(2); sctr.setByY(2); sctr.setCycleCount(2); sctr.setAutoReverse(true); var fltr = new FillTransition(Duration.millis(2000), rect, Color.CADETBLUE, Color.STEELBLUE); fltr.setCycleCount(2); fltr.setAutoReverse(true); root.getChildren().add(rect); var ptr = new ParallelTransition(); ptr.getChildren().addAll(rottr, sctr, fltr); ptr.play(); Scene scene = new Scene(root, 300, 250); stage.setTitle("ParallelTransition"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
该示例并行播放三个过渡。 有一个矩形对象,它被旋转、缩放,并且其背景颜色发生变化。
var rottr = new RotateTransition(Duration.millis(2000), rect); rottr.setByAngle(180); rottr.setCycleCount(2); rottr.setAutoReverse(true);
RotateTransition
将矩形旋转指定的角度。 旋转发生在两个循环中并被反转。
var sctr = new ScaleTransition(Duration.millis(2000), rect); sctr.setByX(2); sctr.setByY(2);
ScaleTransition
将矩形放大和缩小 2 倍。
var fltr = new FillTransition(Duration.millis(2000), rect, Color.CADETBLUE, Color.STEELBLUE);
FillTransition
将矩形的填充颜色从一个颜色值更改为另一个颜色值。
var ptr = new ParallelTransition(); ptr.getChildren().addAll(rottr, sctr, fltr); ptr.play();
三种类型的过渡被放置在 ParallelTransition
中,它并行播放它们,即同时播放它们。
SequentialTransition
SequentialTransition
以顺序播放一系列 Animations
。
package com.zetcode; import javafx.animation.FillTransition; import javafx.animation.RotateTransition; import javafx.animation.ScaleTransition; import javafx.animation.SequentialTransition; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Duration; public class SequentialTransitionEx extends Application { @Override public void start(Stage stage) { initUI(stage); } private void initUI(Stage stage) { var root = new Pane(); var rect = new Rectangle(50, 50, 30, 30); rect.setArcHeight(10); rect.setArcWidth(10); rect.setFill(Color.CADETBLUE); var rottr = new RotateTransition(Duration.millis(2000), rect); rottr.setByAngle(180); rottr.setCycleCount(2); rottr.setAutoReverse(true); var sctr = new ScaleTransition(Duration.millis(2000), rect); sctr.setByX(2); sctr.setByY(2); sctr.setCycleCount(2); sctr.setAutoReverse(true); var fltr = new FillTransition(Duration.millis(2000), rect, Color.CADETBLUE, Color.STEELBLUE); fltr.setCycleCount(2); fltr.setAutoReverse(true); root.getChildren().add(rect); var str = new SequentialTransition(); str.getChildren().addAll(rottr, sctr, fltr); str.play(); var scene = new Scene(root, 300, 250); stage.setTitle("SequentialTransition"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
该示例按顺序播放三个过渡,一个接一个。
var str = new SequentialTransition(); str.getChildren().addAll(rottr, sctr, fltr); str.play();
这三个过渡被添加到 SequentialTransition
。
Timeline
Timeline
是在 JavaFX 中创建动画的最复杂工具。 动画使用 KeyFrames
定义,其中包含更改的节点属性。 这些属性被封装在 KeyValues
中。 Timeline
插入属性的变化。
package com.zetcode; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.effect.Lighting; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.stage.Stage; import javafx.util.Duration; public class TimelineEx extends Application { @Override public void start(Stage stage) { initUI(stage); } private void initUI(Stage stage) { var root = new Pane(); var rect = new Rectangle(20, 20, 60, 60); rect.setEffect(new Lighting()); rect.setFill(Color.CADETBLUE); var tl = new Timeline(); tl.setCycleCount(2); tl.setAutoReverse(true); var kv = new KeyValue(rect.translateXProperty(), 200); var kf = new KeyFrame(Duration.millis(2000), kv); tl.getKeyFrames().addAll(kf); tl.play(); root.getChildren().addAll(rect); var scene = new Scene(root, 350, 250); stage.setTitle("Timeline"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
该示例使用 Timeline
为矩形设置动画。
var rect = new Rectangle(20, 20, 60, 60); rect.setEffect(new Lighting()); rect.setFill(Color.CADETBLUE);
这个矩形是动画中的移动对象。
var tl = new Timeline();
创建一个 Timeline
对象。
tl.setCycleCount(2); tl.setAutoReverse(true);
动画由两个循环组成,并被反转。 矩形前后移动。
var kv = new KeyValue(rect.translateXProperty(), 200);
KeyValue
包含随时间变为 200 的 translateX
属性。
var kf = new KeyFrame(Duration.millis(2000), kv);
实例化一个 KeyFrame
。 第一个参数是它的持续时间,第二个是 KeyValue
。 动画持续 2 秒,在此期间其 translateX
属性更改为 200。
tl.getKeyFrames().addAll(kf);
关键帧被添加到帧列表中。
顺序 Timeline 动画
我们没有在时间轴中定义所有关键帧。 我们定义了一些帧,其余的被插值。 关键帧提供指定时间点的目标值,用于在时间轴中插值的一组变量。 为了按顺序执行关键帧,我们使用 SequentialTransition
类。
package com.zetcode; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.SequentialTransition; import javafx.animation.Timeline; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.stage.Stage; import javafx.util.Duration; public class SequentialTimelineEx extends Application { @Override public void start(Stage stage) { initUI(stage); } private void initUI(Stage stage) { var root = new Pane(); var c = new Circle(50, 100, 10); c.setFill(Color.CADETBLUE); var kv1 = new KeyValue(c.scaleXProperty(), 4); var kv2 = new KeyValue(c.scaleYProperty(), 4); var kf1 = new KeyFrame(Duration.millis(3000), kv1, kv2); var scale = new Timeline(); scale.getKeyFrames().add(kf1); var kv3 = new KeyValue(c.centerXProperty(), 250); var kf2 = new KeyFrame(Duration.millis(5000), kv3); var move = new Timeline(); move.getKeyFrames().add(kf2); var kv4 = new KeyValue(c.scaleXProperty(), 1); var kv5 = new KeyValue(c.scaleYProperty(), 1); var kf3 = new KeyFrame(Duration.millis(3000), kv4, kv5); var scale2 = new Timeline(); scale2.getKeyFrames().add(kf3); var seqtr = new SequentialTransition(scale, move, scale2); seqtr.play(); root.getChildren().add(c); var scene = new Scene(root, 300, 250); stage.setTitle("Sequential Timeline animation"); stage.setScene(scene); stage.show(); } public static void main(String[] args) { launch(args); } }
该示例创建了一个顺序时间轴动画。 动画由三个 Timelines
组成,这些 Timelines
使用 SequentialTransition
顺序执行。
var c = new Circle(50, 100, 10); c.setFill(Color.CADETBLUE);
此动画中的移动对象是一个 Circle
。
var kv1 = new KeyValue(c.scaleXProperty(), 4); var kv2 = new KeyValue(c.scaleYProperty(), 4); var kf1 = new KeyFrame(Duration.millis(3000), kv1, kv2); var scale = new Timeline(); scale.getKeyFrames().add(kf1);
这是第一个 Timeline
。 它在三秒钟内将圆放大。
var kv3 = new KeyValue(c.centerXProperty(), 250); var kf2 = new KeyFrame(Duration.millis(5000), kv3); var move = new Timeline(); move.getKeyFrames().add(kf2);
第二个 Timeline
向前移动圆。 动画的这部分持续五秒钟。
var kv4 = new KeyValue(c.scaleXProperty(), 1); var kv5 = new KeyValue(c.scaleYProperty(), 1); var kf3 = new KeyFrame(Duration.millis(3000), kv4, kv5); var scale2 = new Timeline(); scale2.getKeyFrames().add(kf3);
第三个 Timeline
将圆缩小。
var seqtr = new SequentialTransition(scale, move, scale2); seqtr.play();
这三个时间轴被放置在 SequentialTransition
中。 时间轴被顺序播放,一个接一个。
在本章中,我们介绍了 JavaFX 动画。