【问题标题】:Automatically resize Canvas to fill the enclosing Parent自动调整 Canvas 的大小以填充封闭的父级
【发布时间】:2015-10-24 00:42:29
【问题描述】:

我最近想在 JavaFX 中创建一个动画背景,类似于 here 中看到的 Swing 示例。我使用Canvas 进行绘制,如Working with the Canvas API 所示,并使用AnimationTimer 绘制循环,如Animation Basics 所示。不幸的是,我不确定如何在调整封闭Stage 的大小时自动调整Canvas 的大小。什么是好的方法?

How to make canvas Resizable in javaFX? 中检查了一个类似的问题,但那里接受的答案缺少接受的答案here 中说明的绑定。

【问题讨论】:

标签: java animation javafx resize


【解决方案1】:

在下面的示例中,静态嵌套类CanvasPaneCanvas 的实例包装在Pane 中并覆盖layoutChildren() 以使画布尺寸与封闭的Pane 匹配。请注意,CanvasisResizable() 返回false,因此“父级无法在布局期间调整其大小”和Pane“除了将可调整大小的子级调整为其首选大小之外,不执行布局”。用于构造画布的widthheight 成为其初始大小。在Ensemble 粒子模拟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 https://stackoverflow.com/a/31761362/230513
 * @see https://stackoverflow.com/a/8616169/230513
 */

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

【讨论】:

    【解决方案2】:

    我通过将画布放在窗格中并将其绑定到它来结合先前的解决方案(@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());
        }
    }
    

    【讨论】:

    • custom layout 不是很重要时,这是最简单的方法。
    【解决方案3】:

    你不能用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 http://stackoverflow.com/a/31761362/230513
     * @see http://stackoverflow.com/a/8616169/230513
     */
    
    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 布局进行更改?我只在BorderPaneStackPane 中测试了CanvasPane
    • 细节需要改变,但类似的东西应该可以。此外,该示例对于“BorderPane”并不完全通用,因为如果您在顶部、左侧或右侧位置有节点,则需要更改一些细节。
    • 啊,我明白了;在StackPane 中,我可以将bind() canvas.heightProperty() 直接发送给root.heightProperty()。到目前为止,我认为CanvasPane 更灵活,但加上一个有用的替代方案和Binding 算术的具体示例。
    • 作为参考,这里有一个exampleStackPane 中的绑定。
    猜你喜欢
    • 1970-01-01
    • 2015-07-08
    • 2011-12-11
    • 2010-10-15
    • 1970-01-01
    • 2022-01-09
    • 2014-06-07
    • 2015-02-20
    相关资源
    最近更新 更多