自动调整 Canvas 的大小以填充封闭的父级
Posted
技术标签:
【中文标题】自动调整 Canvas 的大小以填充封闭的父级【英文标题】:Automatically resize Canvas to fill the enclosing Parent 【发布时间】: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 中说明的绑定。
【问题讨论】:
How to make canvas Resizable in javaFX?的可能重复 @smac89:很好的发现;问题已更新。 【参考方案1】:在下面的示例中,静态嵌套类CanvasPane
将Canvas
的实例包装在Pane
中并覆盖layoutChildren()
以使画布尺寸与封闭的Pane
匹配。请注意,Canvas
从isResizable()
返回false
,因此“父级无法在布局期间调整其大小”和Pane
“除了将可调整大小的子级调整为其首选大小之外,不执行布局”。用于构造画布的width
和height
成为其初始大小。在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://***.com/a/31761362/230513
* @see https://***.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://***.com/a/31761362/230513
* @see http://***.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
布局进行更改?我只在BorderPane
和StackPane
中测试了CanvasPane
。
细节需要改变,但类似的东西应该可以。此外,该示例对于“BorderPane”并不完全通用,因为如果您在顶部、左侧或右侧位置有节点,则需要更改一些细节。
啊,我明白了;在StackPane
中,我可以将bind()
canvas.heightProperty()
直接发送给root.heightProperty()
。到目前为止,我认为CanvasPane
更灵活,但加上一个有用的替代方案和Binding
算术的具体示例。
作为参考,这里有一个example 在StackPane
中的绑定。以上是关于自动调整 Canvas 的大小以填充封闭的父级的主要内容,如果未能解决你的问题,请参考以下文章
WPF TextBlock 字体调整大小以填充网格中的可用空间