Search code examples
animationjavafxinterpolation

Javafx Interpolation anticipate/overshoot


Trying to interpolate a javafx Timeline with anticipation and overshooting, I've found the Interpolator.SPLINE method which is supposed to

Create an Interpolator, which curve() is shaped using the spline control points defined by (x1, y1 ) and (x2, y2). The anchor points of the spline are implicitly defined as (0.0, 0.0) and (1.0, 1.0).

According to the documentation, but calling with values Y values lower than 0.0 or greater than 1.0 throws and IllegalArgumentException which means I can't make it anticipate/overshoot.

I'm simply calling Interpolator.SPLINE with the following parameters :

Interpolator anticipateOvershoot = Interpolator.SPLINE(0.68, -0.6, 0.32, 1.6);

What I'm expecting is something similar to CSS cubic-bezier interpolation

chrome interpolation curve editor

But what i'm getting is a RuntimeException

Caused by: java.lang.IllegalArgumentException: Control point coordinates must all be in range [0,1]
    at com.sun.scenario.animation.SplineInterpolator.<init>(SplineInterpolator.java:98)
    at javafx.animation.Interpolator.SPLINE(Interpolator.java:199)

Now my question is Why is it illegal to use Y values lower than 0 or greater than 1 in a SplineInterpolator, and what's the most convenient way to make an anticipate/overshoot interpolator in javafx ?

Adding a MRE

I'm trying to make transition between two nodes in a StackPane, using a timeline, but I need to interpolate it with anticipation/overshooting

public class App extends Application {

    @Override
    public void start(Stage ps) throws Exception {
        //Setting up the scene
        VBox root = new VBox();
        root.setBackground(new Background(new BackgroundFill(Color.GRAY, null, null)));
        
        StackPane cards = new StackPane();
        cards.setMinSize(600, 600);
        
        StackPane card1 = new StackPane(new Label("Card 1"));
        card1.setMaxSize(400, 200);
        card1.setBackground(new Background(new BackgroundFill(Color.WHITE, new CornerRadii(10), null)));
        
        StackPane card2 = new StackPane(new Label("Card 2"));
        card2.setMaxSize(200, 400);
        card2.setBackground(card1.getBackground());
        
        cards.getChildren().addAll(card1, card2);
        
        HBox buttons = new HBox(10);
        buttons.setPadding(new Insets(10));
        buttons.setAlignment(Pos.CENTER);
        
        Button show1 = new Button("Card 1");
        Button show2 = new Button("Card 2");
        
        buttons.getChildren().addAll(show1, show2);
        
        root.getChildren().addAll(cards, buttons);
        
        //adding behaviour
        double duration = .3;
        Interpolator interpolator = Interpolator.EASE_BOTH;
        
        Timeline showCard1 = new Timeline(new KeyFrame(Duration.seconds(duration), 
                new KeyValue(card1.opacityProperty(), 1, interpolator), 
                new KeyValue(card1.scaleXProperty(), 1, interpolator), 
                new KeyValue(card1.scaleYProperty(), 1, interpolator), 
                new KeyValue(card1.translateYProperty(), 0, interpolator),
                
                
                new KeyValue(card2.opacityProperty(), 0, interpolator), 
                new KeyValue(card2.scaleXProperty(), .5, interpolator), 
                new KeyValue(card2.scaleYProperty(), .5, interpolator), 
                new KeyValue(card2.translateYProperty(), -100, interpolator)
            ));
        
        Timeline showCard2 = new Timeline(new KeyFrame(Duration.seconds(duration), 
                new KeyValue(card2.opacityProperty(), 1, interpolator), 
                new KeyValue(card2.scaleXProperty(), 1, interpolator), 
                new KeyValue(card2.scaleYProperty(), 1, interpolator), 
                new KeyValue(card2.translateYProperty(), 0, interpolator),
                
                
                new KeyValue(card1.opacityProperty(), 0, interpolator), 
                new KeyValue(card1.scaleXProperty(), .5, interpolator), 
                new KeyValue(card1.scaleYProperty(), .5, interpolator), 
                new KeyValue(card1.translateYProperty(), -100, interpolator)
            ));
        
        show1.setOnAction(e-> {
            showCard2.stop();
            showCard1.playFromStart();
        });
        
        show2.setOnAction(e-> {
            showCard1.stop();
            showCard2.playFromStart();
        });
        
        show1.fire();
        
        Scene scene = new Scene(root);
        ps.setScene(scene);
        ps.show();
    }

}

(The example uses ease_both interpolation because attempting to use spline curve with values out of range gives the error described above)


Solution

  • Why is it illegal to use Y values lower than 0 or greater than 1 in a SplineInterpolator,

    I don’t know.

    and what's the most convenient way to make an anticipate/overshoot interpolator in javafx ?

    Create your own implementation.

    Extend Interpolator, don’t use a range check in your implementation, unit test it to ensure it performs the functionality you want.

    Example implementation you might wish to start from.