読者です 読者をやめる 読者になる 読者になる

@irof Drawing Song by JavaFX #2

JavaFX

This entry is the translation of 22 Dec. entry of JavaFX Advent Calendar.

The previous entry was JavaFX using JDK1.8 Lambda Expression by @tomo_taka01.

The next entry was irof Advent Calendar 23rd by @fukai_yas.

The reason I wrote @irof Drawing Song by JavaFX #2 is that I regretted the previous @irof Drawing Song. I used JavaFX, but NO ANIMATION!!

Animation is the one of most important JavaFX features, so I used animation on this entry.

However, how do I make shape animation? For this purpose, Transitions aren't suitable. Therefore, I used Timeline class.

For example, I explain line stretching animation: one edge point of line moves, the other doesn't move.

Line class has 4 properties about Line location: startX, startY, endX, and endY. For line stretching, endX and endY move from the same position of startX and startY to target point.

For instance, an animation that end point moves from (0, 0) to (300, 100) is indicated by the following code:

        Line line = new Line(0.0, 0.0, 0.0, 0.0);
        container.getChildren().add(line);

        // end point moves from (0, 0) to (300, 100)
        new Timeline(
            new KeyFrame(Duration.ZERO, new KeyValue(line.endXProperty(), 0.0),
                                        new KeyValue(line.endYProperty(), 0.0)),
            new KeyFrame(new Duration(500), new KeyValue(line.endXProperty(), 300.0),
                                            new KeyValue(line.endYProperty(), 100.0))
        ).play();

In case of drawing circle, I don't use Circle class, but Arc class. That is the animation that length property increases from 0 to 360.

        Arc arc = new Arc(100.0, 100.0, 50.0, 50.0, 0.0, 0.0);
        arc.setFill(null);
        arc.setStroke(Color.BLACK);
        container.getChildren().add(arc);

        // drawing circle: length increases from 0 to 360
        new Timeline(
            new KeyFrame(Duration.ZERO, new KeyValue(arc.lengthProperty(), 0.0)),
            new KeyFrame(new Duration(500), new KeyValue(arc.lengthProperty(), 360.0))
        ).play();

However, if adding all of curves or lines at first, the dots are plotted. Therefore, when the previous animation finish, the next animated shape is added to the container.

In case that rightBorder is animated after drawing topBorder, the source code is indicated below:

        Line topBorder = new Line(0.0, 0.0, 0.0, 0.0);
        topBorder.setStrokeWidth(5.0);
        container.getChildren().add(topBorder);

        new Timeline(
            new KeyFrame(Duration.ZERO, new KeyValue(topBorder.endXProperty(), 0.0)),
            new KeyFrame(new Duration(500), 
                         new EventHandler<ActionEvent>() {
                             @Override
                             public void handle(ActionEvent t) {
                                 // add next animated shape is added
                                 container.getChildren().add(rightBorder);
                             }
                         },
                         new KeyValue(topBorder.endXProperty(), 300.0))
        ).play();

KeyFrame object calls EventHandler#handle method when duration reached the target time, so I discribed the procedure of adding the next shape to the container into the handle method.


The hardest point was the balloon when I made this drawing song animation. Especially, Bézier curve animation.

I used two Bézier curves in the sample. First one wasn't hard, but second one was too hard for me. The reason was that end point moves along the first Bézier curves while moving end point.

The following draw is indicated the reason. The red line is between the start point and the end point of the second Bézier curves. The red line is located on the Bézier curves, so the end poinnt moves along the first Bézier curves like revers P.

However, the animation I want to do is that the end point moves along the red line of the following figure.

PathTransition is used for animation that a node moves along a path, and PathTransition makes whole node move along path. But, I'd like to move only end point of Bézier curve along path.

An idea came to me!

I used PathTransition to move hidden circle, and bound end point to center of hidden circle plus quantity of transition.

        final CubicCurve curve2 = new CubicCurve(129, 141, 90, 135, 66, 120, 129, 141);
        curve2.setFill(null);
        curve2.setStroke(Color.BLACK);
        curve2.setStrokeWidth(10.0);
        curve2.setStrokeLineCap(StrokeLineCap.ROUND);
        
        final CubicCurve drawPath = new CubicCurve(129, 141, 90, 135, 66, 120, 81, 90);
        // Hidden circle (not include in Scene Graph)
        final Circle circle = new Circle(129, 141, 1);

        // end point bind to center of circle plus quantiti of transition
        curve2.endXProperty().bind(Bindings.add(circle.centerXProperty(),
                                               circle.translateXProperty()));
        curve2.endYProperty().bind(Bindings.add(circle.centerYProperty(),
                                               circle.translateYProperty()));

        // Move hidden circle
        PathTransition transition = new PathTransition(new Duration(500), drawPath);
        transition.setNode(circle);

When the hidden circle moves, the end point moves simultaneously!

However, if I don't made control points of the Bézier curves move, the Bézier curves curvature is too big at first. So, I made control points also move. But the control points animation was not good, so the Bézier curves bend suddenly at the end of animation.

I upload Completed animation to YouTube.

I also update source code to gist:github.

https://gist.github.com/49b71f2371570be55cd5