如何在 JavaFx 中重置缩放级别?

How can I reset zoom level in JavaFx?

我正在用 Java 制作地图,我已经设法创建了放大和缩小的方法,但我想实现一个功能,可以将缩放级别重置为默认值。这样地图就全部缩小了。我曾尝试在使用 canvas.zoom() 时更改该系数,但它不起作用。有人知道吗?

下面是我的方法。这是我正在尝试修复的 TabResetBtnAction。

FXML:

<Scene fx:id="scene" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller.Controller">
    <AnchorPane fx:id="MainPane" prefHeight="443.0" prefWidth="670.0">
        <children>
            <MapCanvas fx:id="canvas" height="${scene.height}" width="${scene.width}" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" onScroll="#onScroll" onMousePressed="#onMousePressed" onMouseDragged="#onMouseDragged"/>
            <ToggleButton fx:id="ectBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ectBtnAction" prefHeight="25.0" prefWidth="25.0" AnchorPane.rightAnchor="5.0" AnchorPane.topAnchor="5.0">
                <graphic>
                    <ImageView fitHeight="29.0" fitWidth="22.0" pickOnBounds="true" preserveRatio="true">
                        <image>
                            <Image url="@../icons/gear.png" />
                        </image>
                    </ImageView>
                </graphic>
            </ToggleButton>
            <TabPane fx:id="ectMenu" layoutX="456.0" layoutY="27.0" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="189.0" prefWidth="197.0" side="RIGHT" tabClosingPolicy="UNAVAILABLE" AnchorPane.rightAnchor="-197.0" AnchorPane.topAnchor="32.0">
                <tabs>
                    <Tab fx:id="ThemeTab" text="Themes">
                        <content>
                            <FlowPane fx:id="TabThemePane" alignment="CENTER" columnHalignment="CENTER" hgap="5.0" prefHeight="210.0" prefWidth="135.0" vgap="5.0">
                                <children>
                                    <Button fx:id="colorFadedBtn" mnemonicParsing="false" onAction="#colorFadedBtnAction" text="Faded" />
                                    <Button fx:id="colorGoogleBtn" mnemonicParsing="false" onAction="#colorGoogleBtnAction" text="Google maps">
                                        <FlowPane.margin>
                                            <Insets />
                                        </FlowPane.margin>
                                    </Button>
                                    <Button fx:id="colorOSMBtn" mnemonicParsing="false" onAction="#colorOSMBtnAction" text="Open Street Map" />
                                    <Button fx:id="colorMwtDewBtn" mnemonicParsing="false" onAction="#colorMwtDewBtnAction" text="Mountain Dew" />
                                </children>
                                <opaqueInsets>
                                    <Insets />
                                </opaqueInsets>
                                <padding>
                                    <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
                                </padding>
                            </FlowPane>
                        </content>
                    </Tab>
                    <Tab text="View">
                        <content>
                            <VBox fx:id="TabViewPane" alignment="CENTER" prefHeight="200.0" prefWidth="100.0" spacing="5.0">
                                <children>
                                    <Button fx:id="TabCenterBtn" mnemonicParsing="false" onAction="#TabCenterBtnAction" text="Center" />
                                    <Button fx:id="TabResetBtn" mnemonicParsing="false" onAction="#TabResetBtnAction" text="Reset view" />
                                    <Separator prefWidth="168.0" />
                                    <ToggleButton fx:id="TabToggleAllBtn" mnemonicParsing="false" onAction="#TabToggleAllBtnAction" text="Toggle all" />
                                    <ToggleButton fx:id="TabToggleSearchBtn" mnemonicParsing="false" onAction="#TabToggleSearchBtnAction" text="Toggle search" />
                                    <ToggleButton fx:id="TabToggleToolsBtn" mnemonicParsing="false" onAction="#TabToggleToolsBtnAction" text="Toggle tools" />
                                    <ToggleButton fx:id="TabToggleZoomBtn" mnemonicParsing="false" onAction="#TabToggleZoomBtnAction" text="Toggle zoom" />
                                </children>
                            </VBox>
                        </content>
                    </Tab>
                    <Tab text="Exit">
                        <content>
                            <VBox fx:id="TabExitPane" alignment="CENTER_LEFT" spacing="5.0">
                                <children>
                                    <Button fx:id="UploadFileBtn" mnemonicParsing="false" onAction="#UploadFileBtnAction" text="Upload file">
                                        <graphic>
                                            <ImageView fitHeight="13.0" fitWidth="14.0" pickOnBounds="true" preserveRatio="true">
                                                <image>
                                                    <Image url="@../icons/folder.png" />
                                                </image>
                                            </ImageView>
                                        </graphic>
                                    </Button>
                                    <Button fx:id="ExitBtn" mnemonicParsing="false" onAction="#ExitBtnAction" text="Exit">
                                        <graphic>
                                            <ImageView fitHeight="13.0" fitWidth="14.0" pickOnBounds="true" preserveRatio="true">
                                                <image>
                                                    <Image url="@../icons/logout.png" />
                                                </image>
                                            </ImageView>
                                        </graphic>
                                    </Button>
                                </children>
                                <padding>
                                    <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
                                </padding>
                            </VBox>
                        </content>
                    </Tab>
                </tabs>
            </TabPane>
            <ScrollPane fx:id="FromScrollPane" layoutX="5.0" layoutY="5.0" prefWidth="${searchbar.width}" visible="false">
                <content>
                    <VBox fx:id="ToVBox" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" spacing="5.0">
                        <padding>
                            <Insets left="5.0" right="5.0" top="35.0" />
                        </padding>
                    </VBox>
                </content>
                <opaqueInsets>
                    <Insets />
                </opaqueInsets>
            </ScrollPane>
            <HBox fx:id="SearchPane" layoutX="20.0" layoutY="20.0" spacing="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.topAnchor="5.0">
                <children>
                    <TextField fx:id="searchbar" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" onAction="#searchbarAction" promptText="Search" />
                    <Button maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0">
                        <graphic>
                            <ImageView fitHeight="21.0" fitWidth="25.0" nodeOrientation="INHERIT" pickOnBounds="true" preserveRatio="true">
                                <image>
                                    <Image url="@../icons/68213.png" />
                                </image>
                            </ImageView>
                        </graphic>
                    </Button>
                    <ToggleButton fx:id="routeBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#routeBtnAction" prefHeight="30.0" prefWidth="30.0" HBox.hgrow="NEVER">
                        <graphic>
                            <ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
                                <image>
                                    <Image url="@../icons/route(16).png" />
                                </image>
                            </ImageView>
                        </graphic>
                        <HBox.margin>
                            <Insets />
                        </HBox.margin>
                    </ToggleButton>
                </children>
            </HBox>
            <VBox fx:id="routeMenu" layoutX="-150.0" layoutY="35.0" spacing="5.0" AnchorPane.leftAnchor="-150.0" AnchorPane.topAnchor="35.0">
                <children>
                    <TextField fx:id="searchbar1" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" promptText="To" />
                    <HBox alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" spacing="5.0">
                        <children>
                            <ToggleButton minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0">
                                <graphic>
                                    <ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
                                        <image>
                                            <Image url="@../icons/car.png" />
                                        </image>
                                    </ImageView>
                                </graphic>
                                <toggleGroup>
                                    <ToggleGroup fx:id="Vehicle" />
                                </toggleGroup>
                            </ToggleButton>
                            <ToggleButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0" toggleGroup="$Vehicle">
                                <graphic>
                                    <ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
                                        <image>
                                            <Image url="@../icons/bike.png" />
                                        </image>
                                    </ImageView>
                                </graphic>
                            </ToggleButton>
                            <ToggleButton maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" prefHeight="30.0" prefWidth="30.0" toggleGroup="$Vehicle">
                                <graphic>
                                    <ImageView fitHeight="21.0" fitWidth="25.0" pickOnBounds="true" preserveRatio="true">
                                        <image>
                                            <Image url="@../icons/walk.png" />
                                        </image>
                                    </ImageView>
                                </graphic>
                            </ToggleButton>
                        </children>
                    </HBox>
                </children>
            </VBox>
            <HBox fx:id="ZoomPane" alignment="CENTER_RIGHT" layoutX="341.0" layoutY="411.0" spacing="5.0" AnchorPane.bottomAnchor="5.0" AnchorPane.rightAnchor="5.0">
                <children>
                    <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Nearest road" />
                    <Text fx:id="NearestRoadText" strokeType="OUTSIDE" strokeWidth="0.0" text="Text" />
                    <Separator orientation="VERTICAL" />
                    <Text fx:id="ZoomText" strokeType="OUTSIDE" strokeWidth="0.0" text="Zoom text" />
                    <ImageView fitHeight="16.0" fitWidth="141.0" pickOnBounds="true" preserveRatio="true" />
                </children>
                <padding>
                    <Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
                </padding>
            </HBox>
            <VBox fx:id="ToolPane" alignment="BOTTOM_RIGHT" layoutX="640.0" layoutY="261.0" spacing="5.0" AnchorPane.bottomAnchor="37.0" AnchorPane.rightAnchor="5.0">
                <children>
                    <Button fx:id="ZoomInBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ZoomInBtnAction" prefHeight="25.0" prefWidth="25.0">
                        <graphic>
                            <ImageView fitHeight="22.0" fitWidth="17.0" pickOnBounds="true" preserveRatio="true">
                                <image>
                                    <Image url="@../icons/plus.png" />
                                </image>
                            </ImageView>
                        </graphic>
                    </Button>
                    <Button fx:id="ZoomOutBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#ZoomOutBtnAction" prefHeight="25.0" prefWidth="25.0">
                        <graphic>
                            <ImageView fitHeight="18.0" fitWidth="21.0" pickOnBounds="true" preserveRatio="true">
                                <image>
                                    <Image url="@../icons/minus.png" />
                                </image>
                            </ImageView>
                        </graphic>
                    </Button>
                    <ToggleButton fx:id="PanBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#PanBtnAction" prefHeight="25.0" prefWidth="25.0" selected="true">
                        <toggleGroup>
                            <ToggleGroup fx:id="mapFunction1" />
                        </toggleGroup>
                        <graphic>
                            <ImageView fitHeight="15.0" fitWidth="18.0" pickOnBounds="true" preserveRatio="true">
                                <image>
                                    <Image url="@../icons/drag.png" />
                                </image>
                            </ImageView>
                        </graphic>
                    </ToggleButton>
                    <ToggleButton fx:id="POIBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#POIBtnAction" prefHeight="25.0" prefWidth="25.0" toggleGroup="$mapFunction1">
                        <graphic>
                            <ImageView fitHeight="18.0" fitWidth="70.0" pickOnBounds="true" preserveRatio="true">
                                <image>
                                    <Image url="@../icons/starPin.png" />
                                </image>
                            </ImageView>
                        </graphic>
                    </ToggleButton>
                    <ToggleButton fx:id="BBBtn" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" onAction="#BBBtnAction" prefHeight="25.0" prefWidth="25.0" toggleGroup="$mapFunction1">
                        <graphic>
                            <ImageView fitHeight="16.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
                                <image>
                                    <Image url="@../icons/rectangle.png" />
                                </image>
                            </ImageView>
                        </graphic>
                    </ToggleButton>
                </children>
            </VBox>
        </children></AnchorPane>
</Scene>

控制器:



public class Controller{
    private Model model;
    private Point2D lastMouse;

   ...

    public void init(Model model) throws NonInvertibleTransformException {
        this.model = model;
        canvas.init(model);
    }

  
    @FXML
    private void onMousePressed(MouseEvent e) throws NonInvertibleTransformException {
        lastMouse = new Point2D(e.getX(), e.getY());
        if (e.getButton() == MouseButton.SECONDARY) {
            canvas.drawNearest(lastMouse);
        }
        if (canvas.getNearestName() != null) {
            NearestRoadText.setText(canvas.getNearestName());
        } else {
            NearestRoadText.setText("Unnamed road/path");
        }


    }

    @FXML
    private void repaint() throws NonInvertibleTransformException{
        canvas.repaint();;
    }

    @FXML
    private void TabResetBtnAction() throws NonInvertibleTransformException {
      
        Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
        canvas.zoom(1.0 ,center);

    }

    @FXML
    private void ZoomInBtnAction () throws  NonInvertibleTransformException{
        Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
        canvas.zoom(1.4888637335882213,center);
    }
    @FXML
    private void ZoomOutBtnAction () throws NonInvertibleTransformException {
        Point2D center = new Point2D(ZoomInBtn.getScene().getWindow().getWidth()/2,ZoomInBtn.getScene().getWindow().getHeight()/2);
        canvas.zoom(0.6716531388604381,center);
    }
   

canvas:


public class MapCanvas extends Canvas {
    private Model model;
    private Affine trans = new Affine();
    private Point2D max, min, point;
    private double zoomFactor = 1;
    private double initialZoomFactor;
    private double maxZoomFactor = 500000;
    private int counter = 0;
    private static ThemeDB themeDB = new ThemeDB();
    private ArrayList<Way> roadsSmall, roadsLarge, motorways;
    private Way nearest;


    //private long lastZoom;
    //private final int zoomDelay = 100; // this is in milliseconds

    public void init(Model model) throws NonInvertibleTransformException {
        this.model = model;
        pan(-model.min_x,-model.min_y);
        zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
    }
 

    public void pan(double dx, double dy) throws NonInvertibleTransformException {
        trans.prependTranslation(dx, dy);
        repaint();
    }

    public void zoom(double factor, Point2D center) throws NonInvertibleTransformException {
        if (counter < 1) {
            setStartZoom(factor, center);
        } else {
            if (zoomFactor * factor >= initialZoomFactor && zoomFactor * factor <= maxZoomFactor) {
                trans.prependScale(factor, factor, center);
                zoomFactor *= factor;
            }
        }
        repaint();
    }

    private void setStartZoom (double factor, Point2D center) {
        initialZoomFactor = factor;
        trans.prependScale(factor, factor, center);
        zoomFactor *= factor;
        counter++;
    }

类似于

public void resetZoom() throws NonInvertibleTransformException {
    Point2D p1 = trans.inverseTransform(0, 0);
    Point2D p2 = trans.inverseTransform(getWidth(), getHeight());
    Rectangle2D rect = new Rectangle2D(
            Math.min(p1.getX(), p2.getX()),
            Math.min(p1.getY(), p2.getY()),
            Math.max(p1.getX(), p2.getX()) - Math.min(p1.getX(), p2.getX()),
            Math.max(p1.getY(), p2.getY()) - Math.min(p1.getY(), p2.getY()));
    trans.prependScale(initialZoomFactor / zoomFactor, initialZoomFactor / zoomFactor, rect.getMaxX() - rect.getMinX() / 2, rect.getMaxY() - rect.getMinY() / 2);
    zoomFactor = initialZoomFactor;
    repaint();
}

确定的部分是通过返回 initialZoomFactor 来反转缩放。

现在你需要确定我不清楚的轴心点。在这里,我尝试使用视图的当前中心,因此 canvas 的绘图大致位于视口内。

如果我不设置轴心点或不使用 (0, 0),它们就会完全失焦

可用class

package test.mapcanvas;

import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.transform.Affine;
import javafx.scene.transform.NonInvertibleTransformException;

public class MapCanvas extends Canvas {
    private Affine trans = new Affine();
    private double zoomFactor = 1;
    private double initialZoomFactor;
    private double maxZoomFactor = 500000;
    private int counter = 0;


    public void init() throws NonInvertibleTransformException {
        zoom(2, new Point2D(0,0));
    }
 

    public void pan(double dx, double dy) throws NonInvertibleTransformException {
        trans.prependTranslation(dx, dy);
        repaint();
    }

    public void zoom(double factor, Point2D center) throws NonInvertibleTransformException {
        if (counter < 1) {
            setStartZoom(factor, center);
        } else {
            if (zoomFactor * factor >= initialZoomFactor && zoomFactor * factor <= maxZoomFactor) {
                trans.prependScale(factor, factor, center);
                zoomFactor *= factor;
            }
        }
        repaint();
    }

    private void setStartZoom (double factor, Point2D center) {
        initialZoomFactor = factor;
        trans.prependScale(factor, factor, center);
        zoomFactor *= factor;
        counter++;
    }

    public void resetZoom() throws NonInvertibleTransformException {
        Rectangle2D rect = getCurrentViewBounds();
        trans.prependScale(initialZoomFactor / zoomFactor, initialZoomFactor / zoomFactor, rect.getMaxX() - rect.getMinX() / 2, rect.getMaxY() - rect.getMinY() / 2);
        zoomFactor = initialZoomFactor;
        repaint();
    }
    
    public void repaint() {
        Platform.runLater(() -> {
            try {
                /* requires file example.jpg in class' package */
                Image image = new Image(getClass().getResource("example.jpg").toExternalForm());
                GraphicsContext g = getGraphicsContext2D();
                g.setTransform(trans);
                Rectangle2D rect = getCurrentViewBounds();
                g.clearRect(rect.getMinX(), rect.getMinY(), rect.getWidth(), rect.getHeight());
                g.drawImage(image, 0, 0);
            } catch (NonInvertibleTransformException e) {
                e.printStackTrace();
            }
        });
    }
    
    private Rectangle2D getCurrentViewBounds() throws NonInvertibleTransformException {
        Point2D p1 = trans.inverseTransform(0, 0);
        Point2D p2 = trans.inverseTransform(getWidth(), getHeight());
        return new Rectangle2D(
                Math.min(p1.getX(), p2.getX()),
                Math.min(p1.getY(), p2.getY()),
                Math.max(p1.getX(), p2.getX()) - Math.min(p1.getX(), p2.getX()),
                Math.max(p1.getY(), p2.getY()) - Math.min(p1.getY(), p2.getY()));
    }
}

考虑这个重构:

public class MapCanvas extends Canvas {

    // ...

//    public void init(Model model) throws NonInvertibleTransformException {
//        this.model = model;
//        pan(-model.min_x,-model.min_y);
//        zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
//    }

    public void init(Model model) throws NonInvertibleTransformException {
        this.model = model;
        init();
    }

    private void init() throws NonInvertibleTransformException {
        pan(-model.min_x,-model.min_y);
        zoom(getWidth()/(model.max_x-model.min_x), new Point2D(0,0));
    }

    public void resetZoom() throws NonInvertibleTransformException {
        trans.setToTransform(1, 0, 0, 1, 0, 0);
        init();
    }

    // ...

}

然后从控制器调用 canvas.resetZoom() 应该重置为初始缩放设置。 trans.setToTransform(1, 0, 0, 1, 0, 0); 调用将仿射变换重置为标识(其初始状态),然后执行与原始 init(...) 方法相同的初始化。