当前位置: 首页 > 知识库问答 >
问题:

将旋转的ImageView拟合到应用程序窗口/场景中

慕志泽
2023-03-14

在JavaFX中,我试图在应用程序窗口中显示旋转的ImageView。因此,我将其放入堆栈窗格中,使其始终居中,并将ImageView和堆栈窗格的宽度/高度绑定到场景的宽度/高度,以尽可能大地查看它。


只要我使用stackPane.setRotate(90)(并将绑定换成宽度/高度)将图像旋转90°,那么stackPane就不再绑定到应用程序窗口(或场景)的左上角。

如何正确放置旋转的图像?

在示例代码中,[任意键]将切换旋转90°/0°,因此旋转图像的位置问题变得可见:

public class RotationTest extends Application {

  boolean rotated = false;

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

  @Override
  public void start(Stage primaryStage) {
    primaryStage.setTitle("Rotation test");

    Group root = new Group();
    Scene scene = new Scene(root, 1024,768);

    //a stackPane is used to center the image
    StackPane stackPane = new StackPane();
    stackPane.setStyle("-fx-background-color: black;");

    stackPane.prefHeightProperty().bind(scene.heightProperty());
    stackPane.prefWidthProperty().bind(scene.widthProperty());

    scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
    @Override
      public void handle(KeyEvent event) {
        //toggle rotate 90° / no rotation
        rotated = !rotated;

        stackPane.prefHeightProperty().unbind();
        stackPane.prefWidthProperty().unbind();

        if (rotated){
          stackPane.setRotate(90);
          //rotation: exchange width and height for binding to scene
          stackPane.prefWidthProperty().bind(scene.heightProperty());
          stackPane.prefHeightProperty().bind(scene.widthProperty());
        }else{
          stackPane.setRotate(0);
          //no rotation: height is height and width is width
          stackPane.prefHeightProperty().bind(scene.heightProperty());
          stackPane.prefWidthProperty().bind(scene.widthProperty());
        }
      }
    });


    final ImageView imageView = new ImageView("file:D:/test.jpg");
    imageView.setPreserveRatio(true);
    imageView.fitWidthProperty().bind(stackPane.prefWidthProperty());
    imageView.fitHeightProperty().bind(stackPane.prefHeightProperty());

    stackPane.getChildren().add(imageView);

    root.getChildren().add(stackPane);
    primaryStage.setScene(scene);
    primaryStage.show();

  }
}

结果:

如果不旋转,stackPane(黑色)完全适合窗口,即使用鼠标调整窗口大小,图像也具有正确的大小。

按下[任意键]后,堆叠窗格旋转。

堆叠窗格(黑色)似乎具有正确的宽度/高度,并且图像似乎已正确旋转。但是stackPane不再位于左上角???当用鼠标调整窗口大小时,它会四处移动???

共有2个答案

储仲渊
2023-03-14

我找到了一个解决方案:-)Fabian的方法启发了我(谢谢!!)我的老朋友Pit帮助我调试(也谢谢你!!)

当resize()应用于旋转窗格(甚至节点-我没有尝试过)时,JavaFX的布局定位算法似乎有问题:

按照Fabian的想法,我调试了类Pane的layout儿童()方法。我发现setRotate()之后的重定位是正确的,并且按照预期保持子窗格的中心。但是一旦调用resize()(这是因为将旋转的子窗格再次拟合到其父窗格中,并且总是在用户调整窗口大小时),原点计算就会出错:

上图以绿色表示setRotate(90)、resize()和relocate()的序列,以蓝色表示setRotate(270)的序列。在1024x786示例中,一个蓝色/绿色的小圆圈描绘了相应的原点及其坐标。

对于计算,窗格resize()的位置似乎不使用来自BoundsInParent属性的高度和宽度(请参见节点的JavaFX Docu),而是来自似乎反映BoundsInLocal的getWidth()和getHeight()。因此,对于90°或270°的旋转,高度和宽度似乎可以互换。因此,当resize()在调整大小后再次尝试将子窗格居中时,新原点的计算误差仅为宽度和高度差(delta=(宽度-高度)/2)的一半。

为旋转=90或270度的窗格调整大小后,需要应用重定位(delta,-delta)。

我的实现结构遵循Fabian的基本思想:我构建了一个layouter RotatablePaneLayouter:区域,它只覆盖layoutChildren()方法。在其构造函数中,它得到一个窗格(在我的示例中是StackPane),该窗格可以包含任意数量的子级(在我的示例中是ImageView),并且可以旋转。

LayoutChildren()然后只执行子窗格的resize()和relocate(),以使其完全适合RotateAblePanelLayouter,与子窗格的方向有关。

public class RotatablePaneLayouter extends Region {
  private Pane child;

  public RotatablePaneLayouter(Pane child) {
    getChildren().add(child);
    this.child = child;

    // make sure layout gets invalidated when the child orientation changes
    child.rotateProperty().addListener(new ChangeListener<Number>() {
      @Override
      public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
        requestLayout();
      }
    });
  }

  @Override
  protected void layoutChildren() {
    // set fit sizes:
    //resize child to fit into RotatablePane and correct movement caused by resizing if necessary
    if ((child.getRotate() == 90)||(child.getRotate() == 270)) {
      //vertical
      child.resize( getHeight(), getWidth() ); //exchange width and height
      // and relocate to correct movement caused by resizing
      double delta = (getWidth() - getHeight()) / 2;
      child.relocate(delta,-delta);
    } else {
      //horizontal
      child.resize( getWidth(), getHeight() ); //keep width and height
      //with 0° or 180° resize does no movement to be corrected
      child.relocate(0,0);
    }
  }
}

要使用它:首先将要旋转的窗格放置到图层中,而不是直接放置窗格。

这里是示例主程序的代码。可以使用空格键将子窗格旋转90度、180度、270度,然后再旋转0度。也可以用鼠标调整窗口大小。布局器始终能够正确放置旋转的窗格。

public class RotationTest extends Application {
  public static void main(String[] args) {
   Application.launch(args);
  }

  @Override
  public void start(Stage primaryStage) {

    //image in a StackPane to be rotated
    final ImageView imageView = new ImageView("file:D:/Test_org.jpg");
    imageView.setPreserveRatio(true);
    StackPane stackPane = new StackPane(imageView); //a stackPane is used to center the image
    stackPane.setStyle("-fx-background-color: black;");
    imageView.fitWidthProperty().bind(stackPane.widthProperty());
    imageView.fitHeightProperty().bind(stackPane.heightProperty());

    //container for layouting rotated Panes
    RotatablePaneLayouter root = new RotatablePaneLayouter(stackPane);
    root.setStyle("-fx-background-color: blue;");

    Scene scene = new Scene(root, 1024,768);

    scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
      @Override
      public void handle(KeyEvent event) {
        if (event.getCode() == KeyCode.SPACE) {
          //rotate additionally 90°
          stackPane.setRotate((stackPane.getRotate() + 90) % 360);
        }
      }
    });

    primaryStage.setTitle("Rotation test");
    primaryStage.setScene(scene);
    primaryStage.show();
  }
}

对我来说,这似乎是resize()中javaFX错误的解决方法。

沙星波
2023-03-14

为什么不简单地将组和首选尺寸排除在等式之外?

根会自动调整大小以适应场景,您可以使用其宽度属性来绑定fitWidth和fitHeight属性:

private static void setRotated(boolean rotated, ImageView targetNode, Pane parent) {
    double angle;
    if (rotated) {
        angle = 90;
        targetNode.fitWidthProperty().bind(parent.heightProperty());
        targetNode.fitHeightProperty().bind(parent.widthProperty());
    } else {
        angle = 0;
        targetNode.fitWidthProperty().bind(parent.widthProperty());
        targetNode.fitHeightProperty().bind(parent.heightProperty());
    }
    targetNode.setRotate(angle);
}

@Override
public void start(Stage primaryStage) {
    Image image = new Image("file:D:/test.jpg");
    ImageView imageView = new ImageView(image);
    imageView.setPreserveRatio(true);

    StackPane root = new StackPane(imageView);
    root.setStyle("-fx-background-color: black;");

    // initialize unrotated
    setRotated(false, imageView, root);

    Scene scene = new Scene(root, 1024, 768);

    scene.setOnKeyPressed(evt -> {
        // toggle between 0° and 90° rotation
        setRotated(imageView.getRotate() == 0, imageView, root);
    });

    primaryStage.setScene(scene);
    primaryStage.show();
}

请注意,如果放置在其他布局中,这可能不会导致正确的布局,因为尺寸约束可能计算错误。

您可以实现自己的区域来解决此问题:

public class CenteredImage extends Region {
    private final BooleanProperty rotated = new SimpleBooleanProperty();
    private final ImageView imageView = new ImageView();

    public CenteredImage() {
        // make sure layout gets invalidated when the image changes
        InvalidationListener listener = o -> requestLayout();
        imageProperty().addListener(listener);
        rotated.addListener((o, oldValue, newValue) -> {
            imageView.setRotate(newValue ? 90 : 0);
            requestLayout();
        });
        getChildren().add(imageView);
        imageView.setPreserveRatio(true);
    }

    public final BooleanProperty rotatedProperty() {
        return rotated;
    }

    public final void setRotated(boolean value) {
        this.rotated.set(value);
    }

    public boolean isRotated() {
        return rotated.get();
    }

    public final void setImage(Image value) {
        imageView.setImage(value);
    }

    public final Image getImage() {
        return imageView.getImage();
    }

    public final ObjectProperty<Image> imageProperty() {
        return imageView.imageProperty();
    }

    @Override
    protected double computeMinWidth(double height) {
        return 0;
    }

    @Override
    protected double computeMinHeight(double width) {
        return 0;
    }

    @Override
    protected double computePrefWidth(double height) {
        Image image = getImage();
        Insets insets = getInsets();

        double add = 0;
        if (image != null && height > 0) {
            height -= insets.getBottom() + insets.getTop();
            add = isRotated()
                    ? height / image.getWidth()  * image.getHeight()
                    : height / image.getHeight()  * image.getWidth();
        }

        return insets.getLeft() + insets.getRight() + add;
    }

    @Override
    protected double computePrefHeight(double width) {
        Image image = getImage();
        Insets insets = getInsets();

        double add = 0;
        if (image != null && width > 0) {
            width -= insets.getLeft() + insets.getRight();
            add = isRotated()
                    ? width / image.getHeight()  * image.getWidth()
                    : width / image.getWidth()  * image.getHeight();
        }

        return insets.getTop() + insets.getBottom() + add;
    }

    @Override
    protected double computeMaxWidth(double height) {
        return Double.MAX_VALUE;
    }

    @Override
    protected double computeMaxHeight(double width) {
        return Double.MAX_VALUE;
    }

    @Override
    protected void layoutChildren() {
        Insets insets = getInsets();
        double left = insets.getLeft();
        double top = insets.getTop();
        double availableWidth = getWidth() - left - insets.getRight();
        double availableHeight = getHeight() - top - insets.getBottom();

        // set fit sizes
        if (isRotated()) {
            imageView.setFitWidth(availableHeight);
            imageView.setFitHeight(availableWidth);
        } else {
            imageView.setFitWidth(availableWidth);
            imageView.setFitHeight(availableHeight);
        }

        // place image
        layoutInArea(imageView, left, top, availableWidth, availableHeight, 0, null, false,
                false, HPos.CENTER, VPos.CENTER);

    }

}
@Override
public void start(Stage primaryStage) {
    Image image = new Image("file:D:/test.jpg");
    ImageView imageView = new ImageView(image);
    imageView.setPreserveRatio(true);

    CenteredImage imageArea = new CenteredImage();
    imageArea.setImage(image);

    imageArea.setStyle("-fx-background-color: black;");

    imageArea.setPrefWidth(300);

    SplitPane splitPane = new SplitPane(new Region(), imageArea);
    SplitPane.setResizableWithParent(imageArea, true);

    Scene scene = new Scene(splitPane, 1024, 768);

    scene.setOnKeyPressed(evt -> {
        // toggle between 0° and 90° rotation
        imageArea.setRotated(!imageArea.isRotated());
    });

    primaryStage.setScene(scene);
    primaryStage.show();
}
 类似资料:
  • 使用我的代码,当我旋转ModelView时;它单独旋转每个对象。 我试图旋转整个视图平面。我怎么能那么做? 渲染器称为每个并图框

  • 我有个问题,不知道从哪里开始。我想将文件(仅.zip)从windows中的任何位置放入Swing应用程序(放入JList)。我该怎么做? 在列表中,我只显示绝对路径,文件可能是数组或类似的东西。Java 1.6

  • 我在一个java项目上工作,我处理了每一个功能,但当谈到GUI时,我是一个初学者。我想知道的是,我可以像在JavaFX中一样使用java在一个阶段中显示不同的场景吗?例如,我的起点是一个登录面板,登录后清空Jframe,并显示下一个veiw或场景。观点很多,怎么办?

  • 问题内容: 我正在尝试创建一个Rotatable ImageView,我将向其指定特定角度和枢轴点,并查看其围绕该枢轴点旋转。我尝试过这样的事情: 但是postRotate方法的参数(第二个和第三个-枢轴点)完全没有变化。即使它们是0、0,也一样。 所以我想创建一个ImageView,在初始化时将旋转一定角度。在此示例中为45度。我试图设置范围和人员..没有帮助。 我怎么做?:/ 问题答案: 您可

  • 本文档介绍 TiDB Data Migration (DM) 支持的主要应用场景及相关的使用建议。 非合库合表场景 将 TiDB 作为 MySQL/MariaDB 的从库 如需将 TiDB 作为上游 MySQL/MariaDB 的从库,即将上游实例中的所有数据先以全量形式导入到 TiDB,然后以增量形式实时复制后续变更到 TiDB,则简单按如下规则配置数据迁移任务即可: 指定 task-mode