自动调整大小 Canvas 以填充封闭的父级

Automatically resize Canvas to fill the enclosing Parent

我最近想在 JavaFX 中创建一个动画背景,类似于 here. I used a Canvas on which to draw, as shown in Working with the Canvas API, and an AnimationTimer for the drawing loop, as shown in Animation Basics 中看到的 Swing 示例。不幸的是,我不确定如何在调整封闭的 Stage 大小时自动调整 Canvas 的大小。什么是好的方法?

How to make canvas Resizable in javaFX?, but the accepted answer there lacks the binding illustrated in the accepted answer 中检查了类似的问题。

在下面的示例中,静态嵌套 class CanvasPane 包装了 Canvas in a Pane and overrides layoutChildren() to make the canvas dimensions match the enclosing Pane. Note that Canvas returns false from isResizable(), so "the parent cannot resize it during layout," and Pane "does not perform layout beyond resizing resizable children to their preferred sizes." The width and height used to construct the canvas become its initial size. A similar approach is used in the Ensemble particle simulation, Fireworks.java 的实例,以缩放背景图像,同时保持其纵横比。

顺便说一句,请注意使用完全饱和的颜色与原始颜色的区别。这些相关的 examples 说明了在动画背景上放置控件。

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * @see 
 * @see 
 */

public class Baubles extends Application {

    private static final int MAX = 64;
    private static final double WIDTH = 640;
    private static final double HEIGHT = 480;
    private static final Random RND = new Random();
    private final Queue<Bauble> queue = new LinkedList<>();
    private Canvas canvas;

    @Override
    public void start(Stage stage) {
        CanvasPane canvasPane = new CanvasPane(WIDTH, HEIGHT);
        canvas = canvasPane.getCanvas();
        BorderPane root = new BorderPane(canvasPane);
        CheckBox cb = new CheckBox("Animate");
        cb.setSelected(true);
        root.setBottom(cb);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();

        for (int i = 0; i < MAX; i++) {
            queue.add(randomBauble());
        }
        AnimationTimer loop = new AnimationTimer() {
            @Override
            public void handle(long now) {
                GraphicsContext g = canvas.getGraphicsContext2D();
                g.setFill(Color.BLACK);
                g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
                for (Bauble b : queue) {
                    g.setFill(b.c);
                    g.fillOval(b.x, b.y, b.d, b.d);
                }
                queue.add(randomBauble());
                queue.remove();
            }
        };
        loop.start();
        cb.selectedProperty().addListener((Observable o) -> {
            if (cb.isSelected()) {
                loop.start();
            } else {
                loop.stop();
            }
        });
    }

    private static class Bauble {

        private final double x, y, d;
        private final Color c;

        public Bauble(double x, double y, double r, Color c) {
            this.x = x - r;
            this.y = y - r;
            this.d = 2 * r;
            this.c = c;
        }
    }

    private Bauble randomBauble() {
        double x = RND.nextDouble() * canvas.getWidth();
        double y = RND.nextDouble() * canvas.getHeight();
        double r = RND.nextDouble() * MAX + MAX / 2;
        Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75);
        return new Bauble(x, y, r, c);
    }

    private static class CanvasPane extends Pane {

        private final Canvas canvas;

        public CanvasPane(double width, double height) {
            canvas = new Canvas(width, height);
            getChildren().add(canvas);
        }

        public Canvas getCanvas() {
            return canvas;
        }

        @Override
        protected void layoutChildren() {
            super.layoutChildren();
            final double x = snappedLeftInset();
            final double y = snappedTopInset();
            // Java 9 - snapSize is deprecated, use snapSizeX() and snapSizeY() accordingly
            final double w = snapSize(getWidth()) - x - snappedRightInset();
            final double h = snapSize(getHeight()) - y - snappedBottomInset();
            canvas.setLayoutX(x);
            canvas.setLayoutY(y);
            canvas.setWidth(w);
            canvas.setHeight(h);
        }
    }

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

你不能用 Binding 做这个吗?以下似乎产生了相同的结果,而无需添加派生的 class.

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.layout.BorderPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

/**
 * @see 
 * @see 
 */

public class Baubles extends Application {

private static final int MAX = 64;
private static final double WIDTH = 640;
private static final double HEIGHT = 480;
private static final Random RND = new Random();
private final Queue<Bauble> queue = new LinkedList<>();
private Canvas canvas;

@Override
public void start(Stage stage) {
    canvas = new Canvas(WIDTH, HEIGHT);
    BorderPane root = new BorderPane(canvas);
    CheckBox cb = new CheckBox("Animate");
    cb.setSelected(true);
    root.setBottom(cb);
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();

    // Create bindings for resizing.
    DoubleBinding heightBinding = root.heightProperty()
            .subtract(root.bottomProperty().getValue().getBoundsInParent().getHeight());
    canvas.widthProperty().bind(root.widthProperty());
    canvas.heightProperty().bind(heightBinding);

    for (int i = 0; i < MAX; i++) {
        queue.add(randomBauble());
    }
    AnimationTimer loop = new AnimationTimer() {
        @Override
        public void handle(long now) {
            GraphicsContext g = canvas.getGraphicsContext2D();
            g.setFill(Color.BLACK);
            g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
            for (Bauble b : queue) {
                g.setFill(b.c);
                g.fillOval(b.x, b.y, b.d, b.d);
            }
            queue.add(randomBauble());
            queue.remove();
        }
    };
    loop.start();
    cb.selectedProperty().addListener((Observable o) -> {
        if (cb.isSelected()) {
            loop.start();
        } else {
            loop.stop();
        }
    });
}

private static class Bauble {

    private final double x, y, d;
    private final Color c;

    public Bauble(double x, double y, double r, Color c) {
        this.x = x - r;
        this.y = y - r;
        this.d = 2 * r;
        this.c = c;
    }
}

private Bauble randomBauble() {
    double x = RND.nextDouble() * canvas.getWidth();
    double y = RND.nextDouble() * canvas.getHeight();
    double r = RND.nextDouble() * MAX + MAX / 2;
    Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75);
    return new Bauble(x, y, r, c);
}

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

我通过将 canvas 放在窗格中并将其绑定到它来组合两个先前的解决方案(@trashgod 和 @clataq's):

private static class CanvasPane extends Pane {

    final Canvas canvas;

    CanvasPane(double width, double height) {
        setWidth(width);
        setHeight(height);
        canvas = new Canvas(width, height);
        getChildren().add(canvas);

        canvas.widthProperty().bind(this.widthProperty());
        canvas.heightProperty().bind(this.heightProperty());
    }
}