3 动画基础
JavaFX中的动画可以分为时间轴动画和过渡动画。本章提供了每种动画类型的示例。
Timeline
和Transition
是javafx.animation.Animation
类的子类。有关特定类、方法或其他功能的更多信息,请参阅API文档。
过渡效果
JavaFX中的过渡效果提供了在内部时间轴中添加动画的方法。过渡效果可以组合在一起,创建并行或顺序执行的多个动画。有关详细信息,请参阅并行过渡和顺序过渡部分。以下部分提供了一些过渡动画示例。
淡入淡出过渡
淡入淡出过渡会在给定的时间内改变节点的不透明度。
示例3-1显示了一个应用于矩形的淡入淡出过渡的代码片段。首先创建一个带有圆角的矩形,然后将淡入淡出过渡应用于它。
示例3-1 淡入淡出过渡
final Rectangle rect1 = new Rectangle(10, 10, 100, 100); rect1.setArcHeight(20); rect1.setArcWidth(20); rect1.setFill(Color.RED); ... FadeTransition ft = new FadeTransition(Duration.millis(3000), rect1); ft.setFromValue(1.0); ft.setToValue(0.1); ft.setCycleCount(Timeline.INDEFINITE); ft.setAutoReverse(true); ft.play();
路径过渡
路径过渡会在给定的时间内将节点沿着路径从一端移动到另一端。
示例3-2显示了一个应用于矩形的路径过渡的代码片段。当矩形到达路径的末端时,动画会反向播放。在代码中,首先创建一个带有圆角的矩形,然后创建一个新的路径动画并将其应用于矩形。
示例3-2 路径过渡
final Rectangle rectPath = new Rectangle (0, 0, 40, 40); rectPath.setArcHeight(10); rectPath.setArcWidth(10); rectPath.setFill(Color.ORANGE); ... Path path = new Path(); path.getElements().add(new MoveTo(20,20)); path.getElements().add(new CubicCurveTo(380, 0, 380, 120, 200, 120)); path.getElements().add(new CubicCurveTo(0, 120, 0, 240, 380, 240)); PathTransition pathTransition = new PathTransition(); pathTransition.setDuration(Duration.millis(4000)); pathTransition.setPath(path); pathTransition.setNode(rectPath); pathTransition.setOrientation(PathTransition.OrientationType.ORTHOGONAL_TO_TANGENT); pathTransition.setCycleCount(Timeline.INDEFINITE); pathTransition.setAutoReverse(true); pathTransition.play();
并行过渡
并行过渡同时执行多个过渡效果。
示例3-3显示了应用于矩形的淡入淡出、平移、旋转和缩放过渡的代码片段。
示例3-3 并行过渡
Rectangle rectParallel = new Rectangle(10,200,50, 50); rectParallel.setArcHeight(15); rectParallel.setArcWidth(15); rectParallel.setFill(Color.DARKBLUE); rectParallel.setTranslateX(50); rectParallel.setTranslateY(75); ... FadeTransition fadeTransition = new FadeTransition(Duration.millis(3000), rectParallel); fadeTransition.setFromValue(1.0f); fadeTransition.setToValue(0.3f); fadeTransition.setCycleCount(2); fadeTransition.setAutoReverse(true); TranslateTransition translateTransition = new TranslateTransition(Duration.millis(2000), rectParallel); translateTransition.setFromX(50); translateTransition.setToX(350); translateTransition.setCycleCount(2); translateTransition.setAutoReverse(true); RotateTransition rotateTransition = new RotateTransition(Duration.millis(3000), rectParallel); rotateTransition.setByAngle(180f); rotateTransition.setCycleCount(4); rotateTransition.setAutoReverse(true); ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(2000), rectParallel); scaleTransition.setToX(2f); scaleTransition.setToY(2f); scaleTransition.setCycleCount(2); scaleTransition.setAutoReverse(true); parallelTransition = new ParallelTransition(); parallelTransition.getChildren().addAll( fadeTransition, translateTransition, rotateTransition, scaleTransition ); parallelTransition.setCycleCount(Timeline.INDEFINITE); parallelTransition.play();
顺序转换
顺序转换执行多个转换,一个接一个地。
示例3-4显示了一个顺序转换的代码,它一个接一个地执行淡入、平移、旋转和缩放转换应用于一个矩形。
示例3-4 顺序转换
Rectangle rectSeq = new Rectangle(25,25,50,50); rectSeq.setArcHeight(15); rectSeq.setArcWidth(15); rectSeq.setFill(Color.CRIMSON); rectSeq.setTranslateX(50); rectSeq.setTranslateY(50); ... FadeTransition fadeTransition = new FadeTransition(Duration.millis(1000), rectSeq); fadeTransition.setFromValue(1.0f); fadeTransition.setToValue(0.3f); fadeTransition.setCycleCount(1); fadeTransition.setAutoReverse(true); TranslateTransition translateTransition = new TranslateTransition(Duration.millis(2000), rectSeq); translateTransition.setFromX(50); translateTransition.setToX(375); translateTransition.setCycleCount(1); translateTransition.setAutoReverse(true); RotateTransition rotateTransition = new RotateTransition(Duration.millis(2000), rectSeq); rotateTransition.setByAngle(180f); rotateTransition.setCycleCount(4); rotateTransition.setAutoReverse(true); ScaleTransition scaleTransition = new ScaleTransition(Duration.millis(2000), rectSeq); scaleTransition.setFromX(1); scaleTransition.setFromY(1); scaleTransition.setToX(2); scaleTransition.setToY(2); scaleTransition.setCycleCount(1); scaleTransition.setAutoReverse(true); sequentialTransition = new SequentialTransition(); sequentialTransition.getChildren().addAll( fadeTransition, translateTransition, rotateTransition, scaleTransition); sequentialTransition.setCycleCount(Timeline.INDEFINITE); sequentialTransition.setAutoReverse(true); sequentialTransition.play();
有关动画和转换的更多信息,请参阅SDK中的API文档和Ensemble项目中的动画部分。
时间轴动画
动画是由其关联的属性驱动的,例如大小、位置和颜色等。Timeline
提供了在时间进度中更新属性值的能力。JavaFX支持关键帧动画。在关键帧动画中,图形场景的动画状态转换是通过在特定时间点上的场景的起始和结束快照(关键帧)来声明的。系统可以自动执行动画。当请求时,它可以停止、暂停、恢复、反转或重复运动。
基本时间轴动画
示例3-5中的代码将一个矩形水平移动,并将其从原始位置X=100
移动到X=300
,持续500毫秒。要水平动画一个对象,改变x坐标并保持y坐标不变。
示例3-5显示了基本时间轴动画的代码片段。
示例3-5 时间轴动画
final Rectangle rectBasicTimeline = new Rectangle(100, 50, 100, 50); rectBasicTimeline.setFill(Color.RED); ... final Timeline timeline = new Timeline(); timeline.setCycleCount(Timeline.INDEFINITE); timeline.setAutoReverse(true); final KeyValue kv = new KeyValue(rectBasicTimeline.xProperty(), 300); final KeyFrame kf = new KeyFrame(Duration.millis(500), kv); timeline.getKeyFrames().add(kf); timeline.play();
时间轴事件
JavaFX提供了在时间轴播放期间触发的事件的方法。在示例3-6中的代码改变了指定范围内圆的半径,KeyFrame
触发了场景中圆的x坐标的随机变换。
示例3-6 时间轴事件
import javafx.application.Application; import javafx.stage.Stage; import javafx.animation.AnimationTimer; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.effect.Lighting; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.text.Text; import javafx.util.Duration; public class TimelineEvents extends Application { //主时间轴 private Timeline timeline; private AnimationTimer timer; //用于存储实际帧的变量 private Integer i=0; @Override public void start(Stage stage) { Group p = new Group(); Scene scene = new Scene(p); stage.setScene(scene); stage.setWidth(500); stage.setHeight(500); p.setTranslateX(80); p.setTranslateY(80); //创建一个带有效果的圆 final Circle circle = new Circle(20, Color.rgb(156,216,255)); circle.setEffect(new Lighting()); //在圆内创建一个文本 final Text text = new Text (i.toString()); text.setStroke(Color.BLACK); //创建一个带有文本的圆的布局 final StackPane stack = new StackPane(); stack.getChildren().addAll(circle, text); stack.setLayoutX(30); stack.setLayoutY(30); p.getChildren().add(stack); stage.show(); //创建一个用于移动圆的时间轴 timeline = new Timeline(); timeline.setCycleCount(Timeline.INDEFINITE); timeline.setAutoReverse(true); //可以在每个帧开始时添加特定的动作。 timer = new AnimationTimer() { @Override public void handle(long l) { text.setText(i.toString()); i++; } }; //创建一个带有工厂的关键值:将圆缩放2倍 KeyValue keyValueX = new KeyValue(stack.scaleXProperty(), 2); KeyValue keyValueY = new KeyValue(stack.scaleYProperty(), 2); //创建一个关键帧,关键值在2秒时达到 Duration duration = Duration.millis(2000); //可以在关键帧达到时添加特定的动作 EventHandler onFinished = new EventHandler<ActionEvent>() { public void handle(ActionEvent t) { stack.setTranslateX(java.lang.Math.random()*200-100); //重置计数器 i = 0; } }; KeyFrame keyFrame = new KeyFrame(duration, onFinished , keyValueX, keyValueY); //将关键帧添加到时间轴 timeline.getKeyFrames().add(keyFrame); timeline.play(); timer.start(); } public static void main(String[] args) { Application.launch(args); } }
插值器
插值器定义了物体在移动的起点和终点之间的位置。您可以使用各种内置的插值器类实现,也可以实现自己的插值器以实现自定义的插值行为。
内置插值器
JavaFX提供了几种内置的插值器,您可以使用它们来创建不同的动画效果。默认情况下,JavaFX使用线性插值来计算坐标。
示例3-7显示了一个代码片段,其中将EASE_BOTH插值器实例添加到基本时间轴动画中的KeyValue中。当物体达到起点和终点时,此插值器会创建一个弹簧效果。
示例3-7 内置插值器
final Rectangle rectBasicTimeline = new Rectangle(100, 50, 100, 50); rectBasicTimeline.setFill(Color.BROWN); ... final Timeline timeline = new Timeline(); timeline.setCycleCount(Timeline.INDEFINITE); timeline.setAutoReverse(true); final KeyValue kv = new KeyValue(rectBasicTimeline.xProperty(), 300, Interpolator.EASE_BOTH); final KeyFrame kf = new KeyFrame(Duration.millis(500), kv); timeline.getKeyFrames().add(kf); timeline.play();