自动调整画布大小以填充父级容器

18

可能是如何在JavaFX中使画布可调整大小?的重复问题。 - smac89
@smac89:发现得不错,问题已更新。 - trashgod
3个回答

14

在下面的示例中,静态嵌套类CanvasPaneCanvas的实例包装在Pane中,并覆盖layoutChildren()以使画布尺寸与封闭的Pane匹配。请注意,CanvasisResizable()返回false,因此“父级无法在布局期间调整其大小”,而Pane“不会执行超出调整可调整大小子项的首选大小的布局”。用于构建画布的widthheight成为其初始大小。在Ensemble粒子模拟FireworksApp中使用了类似的方法来缩放背景图像并保留其纵横比。
作为旁注,注意使用完全饱和的颜色与原始颜色的区别。这些相关的示例说明了在动画背景上放置控件。

image

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 https://dev59.com/ulwZ5IYBdhLWcg3wLdoF#31761362
 * @see https://dev59.com/g-o6XIcBkEYKwwoYQibQ#8616169
 */

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);
    }
}

5

你不也可以使用 Binding 实现这个功能吗?以下代码似乎可以在不添加派生类的情况下产生相同的结果。

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 https://dev59.com/ulwZ5IYBdhLWcg3wLdoF#31761362
 * @see https://dev59.com/g-o6XIcBkEYKwwoYQibQ#8616169
 */

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);
}
}

感谢您建议使用“Binding”。我是否可以推断出,对于不同的“root”布局,绑定将不得不更改?我只在“BorderPane”和“StackPane”中测试了“CanvasPane”。 - trashgod
细节可能需要更改,但类似的东西应该可以工作。此外,如果您在顶部、左侧或右侧位置有节点,则示例对于“BorderPane”并不完全通用,因此某些细节需要更改。 - clartaq
啊,我明白了;在 StackPane 中,我可以直接将 canvas.heightProperty()root.heightProperty() 进行 bind() 绑定。到目前为止,我认为 CanvasPane 更加灵活,但是提供有用的替代方案和关于 Binding 算术的具体示例也很不错。 - trashgod
作为参考,这里有一个关于在 StackPane 中绑定的示例 - trashgod

5

我将之前两种解决方案(@trashgod和@clataq提供的)结合起来,把canvas放在一个Pane中并绑定到它上面:

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());
    }
}

1
当不需要自定义布局时,这是最简单的方法。 - trashgod

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接