JavaFX Canvas.setScaleX/Y(2) 未缩放到两倍大小

JavaFX Canvas.setScaleX/Y(2) not scaling to twice the size

也许我误解了一些基本的东西,但我正在试验 JavaFX,我很困惑为什么缩放 Canvas(使用 .setScaleX/Y)值为 2 不会导致 canvas 大两倍 width/height.

相关代码是这样的:(此时我没有使用任何 .fxml)

public class Main extends Application {

    @Override
    public void start(Stage primaryStage){
        AnchorPane pane = new AnchorPane();
        Canvas canvas = new Canvas();
        canvas.setWidth(100);
        canvas.setHeight(100);
        canvas.getGraphicsContext2D().setFill(Color.BLUE);
        canvas.getGraphicsContext2D().fillRect(0,0,100,100);
        canvas.setScaleX(2);
        canvas.setScaleY(2);
        pane.getChildren().add(canvas);
        primaryStage.setScene(new Scene(pane, 200, 200));
        primaryStage.show();
    }


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

这导致 canvas 大约占 window 的 2/3(根据 width/heighth,而不是面积)。使用 3 的比例会使它填满整个 window(边缘有一个像素间隙),我不明白为什么。我查看了文档,但似乎缩放因子 2 应该导致尺寸的两倍。

编辑: 因此,根据下面的建议,将 canvas 翻译成 width/height 的一半可以解决问题。但是它在 StackPane 中居中也是如此,所以我不明白为什么 AnchorPane 在缩放后确实是 200x200 时不会通过它的 top/left 边缘来锚定它,就好像它是通过它的 100x100 边缘来锚定它一样.但是,如果 Canvas 是 100x100,那么如果只是在 StackPane 中居中,它是如何绘制到这些边界之外的呢?

编辑2: 通过一些试验,我认为我确认 AnchorPane 将 Canvas 定位为“好像”它仍然是 100x100。因此,通过将顶部和左侧锚点设置为 50,我可以获得与平移 50 或在 StackPane 中居中相同的结果。 但这意味着 Canvas 实际上是在其“边缘”之外“绘制”。这肯定不对吧?

您已经将 canvas 添加到窗格中,尝试在 pane.getChildren().add(canvas).

之前应用 .setScaleX/Y

您在原始编辑中添加的信息 post 给出了问题的大部分答案。

首先请注意,scaleXscaleY 围绕其中心缩放节点。来自 documentation:“定义坐标沿此节点的 X 轴围绕对象中心缩放的系数。”

其次,请注意,在该节点的布局计算中不会考虑应用于该节点的转换。来自 layout documentation(参见“视觉边界与布局边界”部分):

So for example ... if a ScaleTransition is used to pulse the size of a button, that pulse animation will not disturb layout around that button. If an application wishes to have the effect, clip, or transform factored into the layout of a node, it should wrap that node in a Group.

文档中引用的最后一句话是指 Group 的布局边界包括应用于组的子项(但不应用于组本身)的任何转换。

要可视化正在发生的事情,请从 100x100 canvas 开始。布局忽略了缩放,因此您可以将其可视化为在 应用缩放之前将 canvas 锚定到锚窗格的左上角 。然后 canvas 围绕其中心缩放,将所有四个角从两个维度的中心推开 50 像素。因此,锚窗格坐标系中 canvas 的边界是从 (-50, -50)(150, 150)。您可以通过添加

来验证这一点
System.out.println(canvas.getBoundsInParent());

舞台演出后

有两种解决方法。一种是将 canvas 包裹在 Group 中。 Group 的布局边界将考虑 canvas:

上的缩放比例
public class Main extends Application {

    @Override
    public void start(Stage primaryStage){
        AnchorPane pane = new AnchorPane();
        Canvas canvas = new Canvas();
        canvas.setWidth(100);
        canvas.setHeight(100);
        canvas.getGraphicsContext2D().setFill(Color.BLUE);
        canvas.getGraphicsContext2D().fillRect(0,0,100,100);
        canvas.setScaleX(2);
        canvas.setScaleY(2);
        
        Group group = new Group(canvas);
        AnchorPane.setTopAnchor(group, 0.0);
        AnchorPane.setLeftAnchor(group, 0.0);
        
        pane.getChildren().add(group);
        primaryStage.setScene(new Scene(pane, 200, 200));
        primaryStage.show();
    }


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

另一种解决方案是应用缩放 canvas 左上角的变换:

public class Main extends Application {

    @Override
    public void start(Stage primaryStage){
        AnchorPane pane = new AnchorPane();
        Canvas canvas = new Canvas();
        canvas.setWidth(100);
        canvas.setHeight(100);
        canvas.getGraphicsContext2D().setFill(Color.BLUE);
        canvas.getGraphicsContext2D().fillRect(0,0,100,100);

        Scale scale = new Scale(2,2);
        scale.setPivotX(0);
        scale.setPivotY(0);
        canvas.getTransforms().add(scale);

        pane.getChildren().add(canvas);
        primaryStage.setScene(new Scene(pane, 200, 200));
        primaryStage.show();
    }


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