首页 > 解决方案 > Fitting rotated ImageView into Application Window / Scene

问题描述

In JavaFX I am trying to show an rotated ImageView in an Application Window. Therefore I have put it into a stackPane to have it always centered and I have bound the widths/heights of the ImageView and the stackPane to the scene's width/height to view it just as large as possible.

This works fine as soon as the Image is not rotated.
As soon as I rotate the Image by 90° using stackPane.setRotate(90) (and exchange binding for width/height) then the stackPane is no longer bound to the upper left corner of the Application Window (or scene).

What can I do to place the rotated image correctly?

In the example code [any key] will toggle the rotation 90°/0° so the location problem of the rotated image becomes visible:

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

  }
}

Results:

Result with no Rotation

Without rotation the stackPane (black) fits the window perfectly and the image has the correct size even if the window is resized with the mouse.

Result with Rotation of stackPane by 90 degree

After pressing [any key] the stackPane is rotated.

The stackPane (black) seems to have the correct width/height and also the image seems to be correctly rotated. But the stackPane is no longer in the upper left corner??? It moves around when the window is resized with the mouse???

标签: javafxrotationimageviewtranslate

解决方案


Why not simply leave the Group and the preferred sizes out of the equation?

The root is automatically resized to fit the scene and you can use it's width/height properties to bind the fitWidth and fitHeight properties:

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

Note that this may not result in correct layout, if placed in some other layout, since the size constraints may be calculated wrong.

You could implement your own region though to fix this:

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

推荐阅读