When ParallelTransitions are nested, the fraction the interpolate method is called with "jumps" when executed more than once.
Consider the following example:
==== AnimationTest.java ====
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AnimationTest extends Application{
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
final Rectangle rect1 = new Rectangle(10, 10, 100, 100);
rect1.setArcHeight(20);
rect1.setArcWidth(20);
rect1.setFill(Color.RED);
// create nested transitions
final Transition ft = createTransition("ft 1");
final Transition ft2 = createTransition("ft 2");
final Transition ft3 = createTransition("ft 3");
final ParallelTransition pt = new ParallelTransition(ft, ft2);
final ParallelTransition pt2 = new ParallelTransition(ft3, pt);
Button but = new Button("Play transition");
but.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
pt2.play();
}
});
VBox box = new VBox();
box.getChildren().addAll(but, rect1);
Scene scene = new Scene(box, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
private Transition createTransition(final String name) {
Transition ft = new Transition(){
{
setCycleDuration(Duration.millis(200));
}
@Override
protected void interpolate(double frac) {
System.out.println(name+" -> " +frac);
}
};
ft.statusProperty().addListener(new ChangeListener<Animation.Status>() {
@Override
public void changed(ObservableValue<? extends Animation.Status> observable, Animation.Status oldValue, Animation.Status newValue) {
if (newValue.equals(Animation.Status.RUNNING)){
System.out.println("starting "+name);
}
}
});
ft.setOnFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("finished "+name);
}
});
return ft;
}
}
=== console output ===
First try:
ft 3 -> 0.0
starting ft 3
ft 3 -> 0.0
ft 3 -> 0.007554253472222221
ft 2 -> 0.0
ft 1 -> 0.0
starting ft 1
ft 1 -> 0.0
ft 1 -> 0.007554253472222221
starting ft 2
ft 2 -> 0.0
ft 2 -> 0.007554253472222221
ft 3 -> 0.052137586805555566
ft 1 -> 0.052137586805555566
ft 2 -> 0.052137586805555566
ft 3 -> 0.13645833333333335
ft 1 -> 0.13645833333333335
ft 2 -> 0.13645833333333335
ft 3 -> 0.23854166666666665
ft 1 -> 0.23854166666666665
ft 2 -> 0.23854166666666665
ft 3 -> 0.3395833333333333
ft 1 -> 0.3395833333333333
ft 2 -> 0.3395833333333333
ft 3 -> 0.4375
ft 1 -> 0.4375
ft 2 -> 0.4375
ft 3 -> 0.5385416666666667
ft 1 -> 0.5385416666666667
ft 2 -> 0.5385416666666667
ft 3 -> 0.6395833333333334
ft 1 -> 0.6395833333333334
ft 2 -> 0.6395833333333334
ft 3 -> 0.7395833333333333
ft 1 -> 0.7395833333333333
ft 2 -> 0.7395833333333333
ft 3 -> 0.8395833333333332
ft 1 -> 0.8395833333333332
ft 2 -> 0.8395833333333332
ft 3 -> 0.9312413194444447
ft 1 -> 0.9312413194444447
ft 2 -> 0.9312413194444447
ft 3 -> 0.9854079861111109
ft 1 -> 0.9854079861111109
ft 2 -> 0.9854079861111109
ft 3 -> 1.0
finished ft 3
ft 1 -> 1.0
finished ft 1
ft 2 -> 1.0
finished ft 2
second try:
ft 1 -> 0.0 <<<<<<<< good
ft 2 -> 0.0 <<<<<<<< good
ft 3 -> 0.0
ft 3 -> 0.01156467013888889
starting ft 1
ft 1 -> 1.0 <<<<<<<< bad
ft 1 -> 1.0 <<<<<<<< bad
starting ft 2
ft 2 -> 1.0 <<<<<<<< bad
ft 2 -> 1.0 <<<<<<<< bad
starting ft 3
ft 3 -> 0.0
ft 3 -> 0.06495008680555556
ft 2 -> 0.0 <<<<<<<< good
ft 1 -> 0.0 <<<<<<<< good
ft 1 -> 0.01156467013888889
ft 2 -> 0.01156467013888889
ft 1 -> 0.0
ft 2 -> 0.0
starting ft 1
ft 1 -> 0.0
ft 1 -> 0.06495008680555556
starting ft 2
ft 2 -> 0.0
ft 2 -> 0.06495008680555556
ft 3 -> 0.15625
ft 1 -> 0.15625
ft 2 -> 0.15625
ft 3 -> 0.2520833333333334
ft 1 -> 0.2520833333333334
ft 2 -> 0.2520833333333334
ft 3 -> 0.3510416666666667
ft 1 -> 0.3510416666666667
ft 2 -> 0.3510416666666667
ft 3 -> 0.45625000000000004
ft 1 -> 0.45625000000000004
ft 2 -> 0.45625000000000004
ft 3 -> 0.5510416666666668
ft 1 -> 0.5510416666666668
ft 2 -> 0.5510416666666668
ft 3 -> 0.6520833333333333
ft 1 -> 0.6520833333333333
ft 2 -> 0.6520833333333333
ft 3 -> 0.7520833333333333
ft 1 -> 0.7520833333333333
ft 2 -> 0.7520833333333333
ft 3 -> 0.8562500000000001
ft 1 -> 0.8562500000000001
ft 2 -> 0.8562500000000001
ft 3 -> 0.9401996527777778
ft 1 -> 0.9401996527777778
ft 2 -> 0.9401996527777778
ft 3 -> 0.9890603298611111
ft 1 -> 0.9890603298611111
ft 2 -> 0.9890603298611111
ft 3 -> 1.0
finished ft 3
ft 1 -> 1.0
finished ft 1
ft 2 -> 1.0
finished ft 2
====== marks ====
as you can see, the second time the parallel transition is played the frac variable jumps from 0.0 to 1.0 and then back to 0.0 at the beginning. This leads to "flashing" animations which are ugly.
Consider the following example:
==== AnimationTest.java ====
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AnimationTest extends Application{
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws Exception {
final Rectangle rect1 = new Rectangle(10, 10, 100, 100);
rect1.setArcHeight(20);
rect1.setArcWidth(20);
rect1.setFill(Color.RED);
// create nested transitions
final Transition ft = createTransition("ft 1");
final Transition ft2 = createTransition("ft 2");
final Transition ft3 = createTransition("ft 3");
final ParallelTransition pt = new ParallelTransition(ft, ft2);
final ParallelTransition pt2 = new ParallelTransition(ft3, pt);
Button but = new Button("Play transition");
but.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
pt2.play();
}
});
VBox box = new VBox();
box.getChildren().addAll(but, rect1);
Scene scene = new Scene(box, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
private Transition createTransition(final String name) {
Transition ft = new Transition(){
{
setCycleDuration(Duration.millis(200));
}
@Override
protected void interpolate(double frac) {
System.out.println(name+" -> " +frac);
}
};
ft.statusProperty().addListener(new ChangeListener<Animation.Status>() {
@Override
public void changed(ObservableValue<? extends Animation.Status> observable, Animation.Status oldValue, Animation.Status newValue) {
if (newValue.equals(Animation.Status.RUNNING)){
System.out.println("starting "+name);
}
}
});
ft.setOnFinished(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("finished "+name);
}
});
return ft;
}
}
=== console output ===
First try:
ft 3 -> 0.0
starting ft 3
ft 3 -> 0.0
ft 3 -> 0.007554253472222221
ft 2 -> 0.0
ft 1 -> 0.0
starting ft 1
ft 1 -> 0.0
ft 1 -> 0.007554253472222221
starting ft 2
ft 2 -> 0.0
ft 2 -> 0.007554253472222221
ft 3 -> 0.052137586805555566
ft 1 -> 0.052137586805555566
ft 2 -> 0.052137586805555566
ft 3 -> 0.13645833333333335
ft 1 -> 0.13645833333333335
ft 2 -> 0.13645833333333335
ft 3 -> 0.23854166666666665
ft 1 -> 0.23854166666666665
ft 2 -> 0.23854166666666665
ft 3 -> 0.3395833333333333
ft 1 -> 0.3395833333333333
ft 2 -> 0.3395833333333333
ft 3 -> 0.4375
ft 1 -> 0.4375
ft 2 -> 0.4375
ft 3 -> 0.5385416666666667
ft 1 -> 0.5385416666666667
ft 2 -> 0.5385416666666667
ft 3 -> 0.6395833333333334
ft 1 -> 0.6395833333333334
ft 2 -> 0.6395833333333334
ft 3 -> 0.7395833333333333
ft 1 -> 0.7395833333333333
ft 2 -> 0.7395833333333333
ft 3 -> 0.8395833333333332
ft 1 -> 0.8395833333333332
ft 2 -> 0.8395833333333332
ft 3 -> 0.9312413194444447
ft 1 -> 0.9312413194444447
ft 2 -> 0.9312413194444447
ft 3 -> 0.9854079861111109
ft 1 -> 0.9854079861111109
ft 2 -> 0.9854079861111109
ft 3 -> 1.0
finished ft 3
ft 1 -> 1.0
finished ft 1
ft 2 -> 1.0
finished ft 2
second try:
ft 1 -> 0.0 <<<<<<<< good
ft 2 -> 0.0 <<<<<<<< good
ft 3 -> 0.0
ft 3 -> 0.01156467013888889
starting ft 1
ft 1 -> 1.0 <<<<<<<< bad
ft 1 -> 1.0 <<<<<<<< bad
starting ft 2
ft 2 -> 1.0 <<<<<<<< bad
ft 2 -> 1.0 <<<<<<<< bad
starting ft 3
ft 3 -> 0.0
ft 3 -> 0.06495008680555556
ft 2 -> 0.0 <<<<<<<< good
ft 1 -> 0.0 <<<<<<<< good
ft 1 -> 0.01156467013888889
ft 2 -> 0.01156467013888889
ft 1 -> 0.0
ft 2 -> 0.0
starting ft 1
ft 1 -> 0.0
ft 1 -> 0.06495008680555556
starting ft 2
ft 2 -> 0.0
ft 2 -> 0.06495008680555556
ft 3 -> 0.15625
ft 1 -> 0.15625
ft 2 -> 0.15625
ft 3 -> 0.2520833333333334
ft 1 -> 0.2520833333333334
ft 2 -> 0.2520833333333334
ft 3 -> 0.3510416666666667
ft 1 -> 0.3510416666666667
ft 2 -> 0.3510416666666667
ft 3 -> 0.45625000000000004
ft 1 -> 0.45625000000000004
ft 2 -> 0.45625000000000004
ft 3 -> 0.5510416666666668
ft 1 -> 0.5510416666666668
ft 2 -> 0.5510416666666668
ft 3 -> 0.6520833333333333
ft 1 -> 0.6520833333333333
ft 2 -> 0.6520833333333333
ft 3 -> 0.7520833333333333
ft 1 -> 0.7520833333333333
ft 2 -> 0.7520833333333333
ft 3 -> 0.8562500000000001
ft 1 -> 0.8562500000000001
ft 2 -> 0.8562500000000001
ft 3 -> 0.9401996527777778
ft 1 -> 0.9401996527777778
ft 2 -> 0.9401996527777778
ft 3 -> 0.9890603298611111
ft 1 -> 0.9890603298611111
ft 2 -> 0.9890603298611111
ft 3 -> 1.0
finished ft 3
ft 1 -> 1.0
finished ft 1
ft 2 -> 1.0
finished ft 2
====== marks ====
as you can see, the second time the parallel transition is played the frac variable jumps from 0.0 to 1.0 and then back to 0.0 at the beginning. This leads to "flashing" animations which are ugly.