Case Study: Bouncing Ball

Paul Ngugi - Jun 24 - - Dev Community

This section presents an animation that displays a ball bouncing in a pane.
The program uses Timeline to animation ball bouncing, as shown in Figure below.

Image description

Here are the major steps to write this program:

  1. Define a subclass of Pane named BallPane to display a ball bouncing, as shown in code below.
  2. Define a subclass of Application named BounceBallControl to control the bouncing ball with mouse actions, as shown in the program below. The animation pauses when the mouse is pressed and resumes when the mouse is released. Pressing the UP and DOWN arrow keys increases/decreases animation speed.

The relationship among these classes is shown in Figure below.

Image description

package application;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.util.Duration;

public class BallPane extends Pane {
    public final double radius = 20;
    private double x = radius, y = radius;
    private double dx = 1, dy = 1;
    private Circle circle = new Circle(x, y, radius);
    private Timeline animation;

    public BallPane() {
        circle.setFill(Color.GREEN); // Set ball color
        getChildren().add(circle); // Place a ball into this game

        // Create an animation for moving the ball
        animation = new Timeline(new KeyFrame(Duration.millis(50), e -> moveBall()));
        animation.setCycleCount(Timeline.INDEFINITE);
        animation.play(); // Start animation
    }

    public void play() {
        animation.play();
    }

    public void pause() {
        animation.pause();
    }

    public void increaseSpeed() {
        animation.setRate(animation.getRate() + 0.1);
    }

    public void decreaseSpeed() {
        animation.setRate(animation.getRate() > 0 ? animation.getRate() - 0.1 : 0);
    }

    public DoubleProperty rateProperty() {
        return animation.rateProperty();
    }

    protected void moveBall() {
        // Check boundaries
        if(x < radius || x > getWidth() - radius) {
            dx *= -1; // Change ball move direction
        }
        if(y < radius || y > getHeight() - radius) {
            dy *= -1; // Change ball move direction
        }

        // Adjust ball position
        x += dx;
        y += dy;
        circle.setCenterX(x);
        circle.setCenterX(y);
    }
}

Enter fullscreen mode Exit fullscreen mode

BallPane extends Pane to display a moving ball (line 10). An instance of Timeline is created to control animation (lines 22). This instance contains a KeyFrame object that invokes the moveBall() method at a fixed rate. The moveBall() method moves the ball to simulate animation. The center of the ball is at (x, y), which changes to (x + dx, y + dy) on the next move (lines 57–58). When the ball is out of the horizontal boundary, the sign of dx is changed (from positive to negative or vice versa) (lines 49–51). This causes the ball to change its horizontal movement direction. When the ball is out of the vertical boundary, the sign of dy is changed (from positive to negative or vice versa) (lines 52–54). This causes the ball to change its vertical movement direction. The pause and play methods (lines 27–33) can be used to pause and resume the animation. The increaseSpeed() and decreaseSpeed() methods (lines 35–41) can be used to increase and decrease animation speed. The rateProperty() method (lines 43–45) returns a binding property value for rate.

package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;

public class BounceBallControl extends Application {
    @Override // Override the start method in the Application class
    public void start(Stage primaryStage) {
        BallPane ballPane = new BallPane(); // Create a ball pane

        // Pause and resume animation
        ballPane.setOnMousePressed(e -> ballPane.pause());
        ballPane.setOnMouseReleased(e -> ballPane.play());

        // Increase and decrease animation
        ballPane.setOnKeyPressed(e -> {
            if(e.getCode() == KeyCode.UP) {
                ballPane.increaseSpeed();
            }
            else if(e.getCode() == KeyCode.DOWN) {
                ballPane.decreaseSpeed();
            }
        });

        // Create a scene and place it in the stage
        Scene scene = new Scene(ballPane, 250, 150);
        primaryStage.setTitle("FlagRisingAnimation"); // Set the stage title
        primaryStage.setScene(scene); // Place the scene in the stage
        primaryStage.show(); // Display the stage

        // Must request focus after the primary stage is displayed
        ballPane.requestFocus();
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

Enter fullscreen mode Exit fullscreen mode

The BounceBallControl class is the main JavaFX class that extends Application to display the ball pane with control functions. The mouse-pressed and mouse-released handlers are implemented for the ball pane to pause the animation and resume the animation (lines 13 and 14). When the UP arrow key is pressed, the ball pane’s increaseSpeed() method is invoked to increase the ball’s movement (line 19). When the DOWN arrow key is pressed, the ball pane’s decreaseSpeed() method is invoked to reduce the ball’s movement (line 22).

Invoking ballPane.requestFocus() in line 33 sets the input focus to ballPane.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .