JavaFX 8动态节点缩放

时间:2015-04-22 05:06:36

标签: javafx-8 scaling zooming

我正在尝试使用ScrollPane实现一个场景,用户可以在其中拖动一个节点并动态缩放它。我使用鼠标滚轮进行拖动和缩放以及重置缩放,但是我在计算上遇到问题以使节点适合父节点的宽度。

这是我的代码sscce

  1. (正常)鼠标滚轮将在鼠标指针周围放大和缩小
  2. (正常)按住鼠标左键或右键拖动矩形
  3. (正常)左键双击以重置缩放
  4. (不起作用)右键双击以适合宽度
  5. 如果我放大或缩小或更改窗口大小,则适合宽度不起作用。

    如果有人可以帮助我进行计算以使节点适合父母的宽度,我将非常感激。

    编辑:

    • 我标记了无法正常工作的方法。它是fitWidth(),可以通过鼠标右键双击来调用。
    • 为了清晰和重点,我编辑了问题的文本

    希望现在更清楚了。

    import javafx.animation.KeyFrame;
    import javafx.animation.KeyValue;
    import javafx.animation.Timeline;
    import javafx.application.Application;
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.event.EventHandler;
    import javafx.scene.Group;
    import javafx.scene.Scene;
    import javafx.scene.control.ScrollPane;
    import javafx.scene.control.ScrollPane.ScrollBarPolicy;
    import javafx.scene.input.MouseButton;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.input.ScrollEvent;
    import javafx.scene.layout.AnchorPane;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.shape.StrokeType;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    public class ZoomAndPanExample extends Application {
    
        private ScrollPane scrollPane = new ScrollPane();
    
        private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1.0d);
        private final DoubleProperty deltaY = new SimpleDoubleProperty(0.0d);
    
        private final Group group = new Group();
    
        public static void main(String[] args) {
            Application.launch(args);
        }
    
        @Override
        public void start(Stage primaryStage) {
    
            scrollPane.setPannable(true);
            scrollPane.setHbarPolicy(ScrollBarPolicy.NEVER);
            scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER);
            AnchorPane.setTopAnchor(scrollPane, 10.0d);
            AnchorPane.setRightAnchor(scrollPane, 10.0d);
            AnchorPane.setBottomAnchor(scrollPane, 10.0d);
            AnchorPane.setLeftAnchor(scrollPane, 10.0d);
    
            AnchorPane root = new AnchorPane();
    
            Rectangle rect = new Rectangle(80, 60);
    
            rect.setStroke(Color.NAVY);
            rect.setFill(Color.NAVY);
            rect.setStrokeType(StrokeType.INSIDE);
    
            group.getChildren().add(rect);
            // create canvas
            PanAndZoomPane panAndZoomPane = new PanAndZoomPane();
            zoomProperty.bind(panAndZoomPane.myScale);
            deltaY.bind(panAndZoomPane.deltaY);
            panAndZoomPane.getChildren().add(group);
    
            SceneGestures sceneGestures = new SceneGestures(panAndZoomPane);
    
            scrollPane.setContent(panAndZoomPane);
            panAndZoomPane.toBack();
            scrollPane.addEventFilter( MouseEvent.MOUSE_CLICKED, sceneGestures.getOnMouseClickedEventHandler());
            scrollPane.addEventFilter( MouseEvent.MOUSE_PRESSED, sceneGestures.getOnMousePressedEventHandler());
            scrollPane.addEventFilter( MouseEvent.MOUSE_DRAGGED, sceneGestures.getOnMouseDraggedEventHandler());
            scrollPane.addEventFilter( ScrollEvent.ANY, sceneGestures.getOnScrollEventHandler());
    
            root.getChildren().add(scrollPane);
            Scene scene = new Scene(root, 600, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        class PanAndZoomPane extends Pane {
    
            public static final double DEFAULT_DELTA = 1.3d;
            DoubleProperty myScale = new SimpleDoubleProperty(1.0);
            public DoubleProperty deltaY = new SimpleDoubleProperty(0.0);
            private Timeline timeline;
    
    
            public PanAndZoomPane() {
    
                this.timeline = new Timeline(60);
    
                // add scale transform
                scaleXProperty().bind(myScale);
                scaleYProperty().bind(myScale);
            }
    
    
            public double getScale() {
                return myScale.get();
            }
    
            public void setScale( double scale) {
                myScale.set(scale);
            }
    
            public void setPivot( double x, double y, double scale) {
                // note: pivot value must be untransformed, i. e. without scaling
                // timeline that scales and moves the node
                timeline.getKeyFrames().clear();
                timeline.getKeyFrames().addAll(
                    new KeyFrame(Duration.millis(200), new KeyValue(translateXProperty(), getTranslateX() - x)),
                    new KeyFrame(Duration.millis(200), new KeyValue(translateYProperty(), getTranslateY() - y)),
                    new KeyFrame(Duration.millis(200), new KeyValue(myScale, scale))
                );
                timeline.play();
    
            }
    
        /** 
         * !!!! The problem is in this method  !!!!
         * 
         * The calculations are incorrect, and result in unpredictable behavior
         *   
         */
            public void fitWidth () {
                double scale = getParent().getLayoutBounds().getMaxX()/getLayoutBounds().getMaxX();
                double oldScale = getScale();
    
                double f = (scale / oldScale)-1;
    
                double dx = getTranslateX() - getBoundsInParent().getMinX() - getBoundsInParent().getWidth()/2;
                double dy = getTranslateY() - getBoundsInParent().getMinY() - getBoundsInParent().getHeight()/2;
    
                double newX = f*dx + getBoundsInParent().getMinX();
                double newY = f*dy + getBoundsInParent().getMinY();
    
                setPivot(newX, newY, scale);
    
            }
    
            public void resetZoom () {
                double scale = 1.0d;
    
                double x = getTranslateX();
                double y = getTranslateY();
    
                setPivot(x, y, scale);
            }
    
            public double getDeltaY() {
                return deltaY.get();
            }
            public void setDeltaY( double dY) {
                deltaY.set(dY);
            }
        }
    
    
        /**
         * Mouse drag context used for scene and nodes.
         */
        class DragContext {
    
            double mouseAnchorX;
            double mouseAnchorY;
    
            double translateAnchorX;
            double translateAnchorY;
    
        }
    
        /**
         * Listeners for making the scene's canvas draggable and zoomable
         */
        public class SceneGestures {
    
            private DragContext sceneDragContext = new DragContext();
    
            PanAndZoomPane panAndZoomPane;
    
            public SceneGestures( PanAndZoomPane canvas) {
                this.panAndZoomPane = canvas;
            }
    
            public EventHandler<MouseEvent> getOnMouseClickedEventHandler() {
                return onMouseClickedEventHandler;
            }
    
            public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
                return onMousePressedEventHandler;
            }
    
            public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
                return onMouseDraggedEventHandler;
            }
    
            public EventHandler<ScrollEvent> getOnScrollEventHandler() {
                return onScrollEventHandler;
            }
    
            private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
    
                public void handle(MouseEvent event) {
    
                    sceneDragContext.mouseAnchorX = event.getX();
                    sceneDragContext.mouseAnchorY = event.getY();
    
                    sceneDragContext.translateAnchorX = panAndZoomPane.getTranslateX();
                    sceneDragContext.translateAnchorY = panAndZoomPane.getTranslateY();
    
                }
    
            };
    
            private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
                public void handle(MouseEvent event) {
    
                    panAndZoomPane.setTranslateX(sceneDragContext.translateAnchorX + event.getX() - sceneDragContext.mouseAnchorX);
                    panAndZoomPane.setTranslateY(sceneDragContext.translateAnchorY + event.getY() - sceneDragContext.mouseAnchorY);
    
                    event.consume();
                }
            };
    
            /**
             * Mouse wheel handler: zoom to pivot point
             */
            private EventHandler<ScrollEvent> onScrollEventHandler = new EventHandler<ScrollEvent>() {
    
                @Override
                public void handle(ScrollEvent event) {
    
                    double delta = PanAndZoomPane.DEFAULT_DELTA;
    
                    double scale = panAndZoomPane.getScale(); // currently we only use Y, same value is used for X
                    double oldScale = scale;
    
                    panAndZoomPane.setDeltaY(event.getDeltaY()); 
                    if (panAndZoomPane.deltaY.get() < 0) {
                        scale /= delta;
                    } else {
                        scale *= delta;
                    }
    
                    double f = (scale / oldScale)-1;
    
                    double dx = (event.getX() - (panAndZoomPane.getBoundsInParent().getWidth()/2 + panAndZoomPane.getBoundsInParent().getMinX()));
                    double dy = (event.getY() - (panAndZoomPane.getBoundsInParent().getHeight()/2 + panAndZoomPane.getBoundsInParent().getMinY()));
    
                    panAndZoomPane.setPivot(f*dx, f*dy, scale);
    
                    event.consume();
    
                }
            };
    
            /**
             * Mouse click handler
             */
            private EventHandler<MouseEvent> onMouseClickedEventHandler = new EventHandler<MouseEvent>() {
    
                @Override
                public void handle(MouseEvent event) {
                    if (event.getButton().equals(MouseButton.PRIMARY)) {
                        if (event.getClickCount() == 2) {
                            panAndZoomPane.resetZoom();
                        }
                    }
                    if (event.getButton().equals(MouseButton.SECONDARY)) {
                        if (event.getClickCount() == 2) {
                            panAndZoomPane.fitWidth();
                        }
                    }
                }
            };
        }
    }
    

1 个答案:

答案 0 :(得分:2)

我找到了答案。我正在查看错误的计算,假设它与翻译有关。真正的罪魁祸首是计算尺度差异。我只是改变了这个:

double f = (scale / oldScale)-1;

到此:

double f = scale - oldScale;