JavaFX z Arią - powrót do strony głównej
Mamy animację obrazka uruchamianą i zatrzymywaną przy użyciu przycisku.
Przycisk znajdujący się się na poziomie animacji – nie da się włączyć. Jest obecny tylko po to, aby pokazać, że nie działa. Aktywny przycisk znajduje się na wyświetlaczu. Tym przyciskiem możemy uruchomić i zatrzymać animację. Wyświetlacz mógłby być całkowicie przezroczysty przy ustawieniu koloru wyświetlacza na Color.TRANSPARENT albo Color.RGB(0, 0, 0)

Klasa Listing11_17c
package rozdzial11c;

import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.control.ToggleButton;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Duration;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class Listing11_17c extends Application {
    private Timeline timeline;
    private BufferedInputStream bis;

    public static void main(String[] args) {

        Application.launch(args);
    }
    @Override
    public void init() {
        try {
            FileInputStream is = new FileInputStream(
                    "rozdzial11c/src/rozdzial11c/kwiatek.jpg");
            bis = new BufferedInputStream(is);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
    @Override
    public void start(Stage stage) {
        ToggleButton tgglass = new ToggleButton(
                "Włącz animację");
        BooleanProperty selglass = tgglass.selectedProperty();
        selglass.addListener((observable, oldValue, newValue) -> {
            tgglass.setText(newValue ? "Wyłącz animację" :
                    "Włącz animację");
            if (tgglass.selectedProperty().get()) {
                timeline.play();
            } else {
                timeline.stop();
            }
        });
        StackPane spane = new StackPane();
        spane.setPrefWidth(250);
        spane.setMaxWidth(250);
        spane.setMinWidth(250);
        spane.setPrefHeight(200);
        spane.setMinHeight(200);
        spane.setMinHeight(200);
        BackgroundFill bgf1 = new BackgroundFill(
                Color.rgb(0, 0, 0, 0.05),
                CornerRadii.EMPTY,
                new Insets(0, 0, 0, 0));
        Background bg1 = new Background(bgf1);
        spane.setBackground(bg1);
        spane.getChildren().add(tgglass);
        SubScene sub = new SubScene(spane, 250, 200);
        DropShadow ds = new DropShadow();
        sub.setEffect(ds);
        BorderStroke bs = new BorderStroke(
                Color.rgb(0, 0, 0, 0.4),
                BorderStrokeStyle.SOLID,
                CornerRadii.EMPTY,
                new BorderWidths(0.5),
                new Insets(0, 0, 0, 0));
        spane.setBorder(new Border(bs));
        //--------------------------------------
        Image image = new Image(bis);
        ImageView iv = new ImageView();
        iv.setImage(image);
        iv.setFitWidth(256);
        iv.setFitHeight(192);
        iv.setSmooth(true);
        iv.setCache(true);
        iv.relocate(100, 75);
        ToggleButton tg = new ToggleButton(
                "Włącz animację");
        BooleanProperty sel = tg.selectedProperty();
        sel.addListener((observable, oldValue, newValue) -> {
            tg.setText(newValue ? "Wyłącz animację" :
                    "Włącz animację");
            if (tg.selectedProperty().get()) {
                timeline.play();
            } else {
                timeline.stop();
            }
        });
        BackgroundFill bgf = new BackgroundFill(
                Color.WHITE,
                CornerRadii.EMPTY,
                new Insets(0, 0, 0, 0));
        Background bg = new Background(bgf);
        Pane root = new Pane();
        root.setBackground(bg);
        root.getChildren().addAll(iv, tg, sub);
        Scene scene = new Scene(
                root,
                500, 400,
                Color.WHITE);
        stage.setTitle("Bouncing Image");
        stage.setScene(scene);
        stage.show();

        EventHandler<ActionEvent> ev = new EventHandler<>() {
            double dx = 5;
            double dy = 3;

            @Override
            public void handle(ActionEvent t) {
                iv.setLayoutX(iv.getLayoutX() + dx);
                iv.setLayoutY(iv.getLayoutY() + dy);
                Bounds bounds = root.getBoundsInLocal();
                if (iv.getLayoutX() <= (bounds.getMinX())
                        || iv.getLayoutX() >=
                        (bounds.getMaxX()
                        - iv.getFitWidth())) {
                    dx = -dx;
                }
                if (iv.getLayoutY() >= (bounds.getMaxY() -
                        iv.getFitHeight())
                        || iv.getLayoutY() <=
                        (bounds.getMinY())) {
                    dy = -dy;
                }
            }
        };
        timeline = new Timeline(
                new KeyFrame(
                        Duration.millis(40), ev));
        timeline.setCycleCount(Timeline.INDEFINITE);
    }

    @Override
    public void stop() {
        Animation.Status s = timeline.getStatus();
        if (!s.equals(Animation.Status.STOPPED)) {
            timeline.stop();
        }
    }
}
Klasa module-info.java
module rozdzial11c {
    requires java.desktop;
    requires javafx.graphics;
    requires javafx.controls;
    requires javafx.swing;

    exports rozdzial11c to javafx.graphics;
}

Po uruchomieniu kodu i uruchomieniu animacji zobaczymy poruszający się obrazek, a nad nim nakładkę dla kontrolek:

Overlay for controls

Nakładka jest przezroczysta dla światła, a nieprzezroczysta dla zdarzeń.