我正在尝试使用可缩放/可绘制的画布创建应用程序.
特征:
>使用鼠标滚轮放大/缩小转动点
>使用鼠标左键在画布上拖动节点
>用鼠标右键拖动整个画布
只要您在比例尺1开始缩放,就可以在枢轴点进行缩放.将鼠标放在网格点上并滚动鼠标滚轮.枢轴点将保持开始缩放的位置.
问题:
当您放大时,将鼠标移动到另一点并再次放大,然后在初始鼠标位置,枢轴点移动,缩放不再发生.
例:
以下是代码:
- import javafx.application.Application;
- import javafx.event.EventHandler;
- import javafx.scene.Group;
- import javafx.scene.Node;
- import javafx.scene.Scene;
- import javafx.scene.canvas.Canvas;
- import javafx.scene.canvas.GraphicsContext;
- import javafx.scene.control.Label;
- import javafx.scene.input.MouseEvent;
- import javafx.scene.input.ScrollEvent;
- import javafx.scene.layout.Pane;
- import javafx.scene.paint.Color;
- import javafx.scene.shape.Circle;
- import javafx.scene.shape.Rectangle;
- import javafx.scene.transform.Scale;
- import javafx.stage.Stage;
- /**
- * The canvas which holds all of the nodes of the application.
- */
- class PannableCanvas extends Pane {
- Scale scaleTransform;
- public PannableCanvas() {
- setPrefSize(600,600);
- setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;");
- // add scale transform
- scaleTransform = new Scale( 1.0,1.0);
- getTransforms().add( scaleTransform);
- // logging
- addEventFilter(MouseEvent.MOUSE_PRESSED,event -> {
- System.out.println(
- "canvas event: " + ( ((event.getSceneX() - getBoundsInParent().getMinX()) / getScale()) + ",scale: " + getScale())
- );
- System.out.println( "canvas bounds: " + getBoundsInParent());
- });
- }
- /**
- * Add a grid to the canvas,send it to back
- */
- public void addGrid() {
- double w = getBoundsInLocal().getWidth();
- double h = getBoundsInLocal().getHeight();
- // add grid
- Canvas grid = new Canvas(w,h);
- // don't catch mouse events
- grid.setMouseTransparent(true);
- GraphicsContext gc = grid.getGraphicsContext2D();
- gc.setStroke(Color.GRAY);
- gc.setLineWidth(1);
- // draw grid lines
- double offset = 50;
- for( double i=offset; i < w; i+=offset) {
- // vertical
- gc.strokeLine( i,i,h);
- // horizontal
- gc.strokeLine( 0,w,i);
- }
- getChildren().add( grid);
- grid.toBack();
- }
- public Scale getScaleTransform() {
- return scaleTransform;
- }
- public double getScale() {
- return scaleTransform.getY();
- }
- /**
- * Set x/y scale
- * @param scale
- */
- public void setScale( double scale) {
- scaleTransform.setX(scale);
- scaleTransform.setY(scale);
- }
- /**
- * Set x/y pivot points
- * @param x
- * @param y
- */
- public void setPivot( double x,double y) {
- scaleTransform.setPivotX(x);
- scaleTransform.setPivotY(y);
- }
- }
- /**
- * Mouse drag context used for scene and nodes.
- */
- class DragContext {
- double mouseAnchorX;
- double mouseAnchorY;
- double translateAnchorX;
- double translateAnchorY;
- }
- /**
- * Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed.
- */
- class NodeGestures {
- private DragContext nodeDragContext = new DragContext();
- PannableCanvas canvas;
- public NodeGestures( PannableCanvas canvas) {
- this.canvas = canvas;
- }
- public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
- return onMousePressedEventHandler;
- }
- public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
- return onMouseDraggedEventHandler;
- }
- private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
- public void handle(MouseEvent event) {
- // left mouse button => dragging
- if( !event.isPrimaryButtonDown())
- return;
- nodeDragContext.mouseAnchorX = event.getSceneX();
- nodeDragContext.mouseAnchorY = event.getSceneY();
- Node node = (Node) event.getSource();
- nodeDragContext.translateAnchorX = node.getTranslateX();
- nodeDragContext.translateAnchorY = node.getTranslateY();
- }
- };
- private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
- public void handle(MouseEvent event) {
- // left mouse button => dragging
- if( !event.isPrimaryButtonDown())
- return;
- double scale = canvas.getScale();
- Node node = (Node) event.getSource();
- node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale));
- node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale));
- event.consume();
- }
- };
- }
- /**
- * Listeners for making the scene's canvas draggable and zoomable
- */
- class SceneGestures {
- private static final double MAX_SCALE = 10.0d;
- private static final double MIN_SCALE = .1d;
- private DragContext sceneDragContext = new DragContext();
- PannableCanvas canvas;
- public SceneGestures( PannableCanvas canvas) {
- this.canvas = canvas;
- }
- 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) {
- // right mouse button => panning
- if( !event.isSecondaryButtonDown())
- return;
- sceneDragContext.mouseAnchorX = event.getSceneX();
- sceneDragContext.mouseAnchorY = event.getSceneY();
- sceneDragContext.translateAnchorX = canvas.getTranslateX();
- sceneDragContext.translateAnchorY = canvas.getTranslateY();
- }
- };
- private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
- public void handle(MouseEvent event) {
- // right mouse button => panning
- if( !event.isSecondaryButtonDown())
- return;
- canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);
- canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - 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 = 1;
- double scale = canvas.getScale(); // currently we only use Y,same value is used for X
- double oldScale = scale;
- if (event.getDeltaY() < 0)
- scale -= delta;
- else
- scale += delta;
- if (scale <= MIN_SCALE) {
- scale = MIN_SCALE;
- } else if (scale >= MAX_SCALE) {
- scale = MAX_SCALE;
- }
- // pivot value must be untransformed,i. e. without scaling
- canvas.setPivot(
- ((event.getSceneX() - canvas.getBoundsInParent().getMinX()) / oldScale),((event.getSceneY() - canvas.getBoundsInParent().getMinY()) / oldScale)
- );
- canvas.setScale( scale);
- System.out.println( "new pivot x: " + canvas.scaleTransform.getPivotX() + "/" + canvas.scaleTransform.getPivotY() + ",new scale: " + scale);
- System.out.println( "bounds: " + canvas.getBoundsInParent());
- event.consume();
- }
- };
- }
- /**
- * An application with a zoomable and pannable canvas.
- */
- public class ScrollApplication extends Application {
- public static void main(String[] args) {
- launch(args);
- }
- @Override
- public void start(Stage stage) {
- Group group = new Group();
- // create canvas
- PannableCanvas canvas = new PannableCanvas();
- // we don't want the canvas on the top/left in this example => just
- // translate it a bit
- canvas.setTranslateX(100);
- canvas.setTranslateY(100);
- // create sample nodes which can be dragged
- NodeGestures nodeGestures = new NodeGestures( canvas);
- Label label1 = new Label("Draggable node 1");
- label1.setTranslateX(10);
- label1.setTranslateY(10);
- label1.addEventFilter( MouseEvent.MOUSE_PRESSED,nodeGestures.getOnMousePressedEventHandler());
- label1.addEventFilter( MouseEvent.MOUSE_DRAGGED,nodeGestures.getOnMouseDraggedEventHandler());
- Label label2 = new Label("Draggable node 2");
- label2.setTranslateX(100);
- label2.setTranslateY(100);
- label2.addEventFilter( MouseEvent.MOUSE_PRESSED,nodeGestures.getOnMousePressedEventHandler());
- label2.addEventFilter( MouseEvent.MOUSE_DRAGGED,nodeGestures.getOnMouseDraggedEventHandler());
- Label label3 = new Label("Draggable node 3");
- label3.setTranslateX(200);
- label3.setTranslateY(200);
- label3.addEventFilter( MouseEvent.MOUSE_PRESSED,nodeGestures.getOnMousePressedEventHandler());
- label3.addEventFilter( MouseEvent.MOUSE_DRAGGED,nodeGestures.getOnMouseDraggedEventHandler());
- Circle circle1 = new Circle( 300,300,50);
- circle1.setStroke(Color.ORANGE);
- circle1.setFill(Color.ORANGE.deriveColor(1,1,0.5));
- circle1.addEventFilter( MouseEvent.MOUSE_PRESSED,nodeGestures.getOnMousePressedEventHandler());
- circle1.addEventFilter( MouseEvent.MOUSE_DRAGGED,nodeGestures.getOnMouseDraggedEventHandler());
- Rectangle rect1 = new Rectangle(100,100);
- rect1.setTranslateX(450);
- rect1.setTranslateY(450);
- rect1.setStroke(Color.BLUE);
- rect1.setFill(Color.BLUE.deriveColor(1,0.5));
- rect1.addEventFilter( MouseEvent.MOUSE_PRESSED,nodeGestures.getOnMousePressedEventHandler());
- rect1.addEventFilter( MouseEvent.MOUSE_DRAGGED,nodeGestures.getOnMouseDraggedEventHandler());
- canvas.getChildren().addAll(label1,label2,label3,circle1,rect1);
- group.getChildren().add(canvas);
- // create scene which can be dragged and zoomed
- Scene scene = new Scene(group,1024,768);
- SceneGestures sceneGestures = new SceneGestures(canvas);
- scene.addEventFilter( MouseEvent.MOUSE_PRESSED,sceneGestures.getOnMousePressedEventHandler());
- scene.addEventFilter( MouseEvent.MOUSE_DRAGGED,sceneGestures.getOnMouseDraggedEventHandler());
- scene.addEventFilter( ScrollEvent.ANY,sceneGestures.getOnScrollEventHandler());
- stage.setScene(scene);
- stage.show();
- canvas.addGrid();
- }
- }
显然,枢轴点计算有问题,但我无法弄清楚它是什么,以及如何解决它.
非常感谢你!
解决方法
首先,我建议不要按直线步长进行分级,还可以通过因素来平滑缩放:
- double delta = 1.2;
- if (event.getDeltaY() < 0)
- scale /= delta;
- else
- scale *= delta;
…而且以某种方式老板,我推荐大括号作为一个很好的风格;-):
- double delta = 1.2;
- if (event.getDeltaY() < 0) {
- scale /= delta;
- } else {
- scale *= delta;
- }
…并使用鼠标滚动值更好的质量:
- double delta = 1.2;
- if (event.getDeltaY() < 0) {
- scale /= Math.pow(delta,-event.getDeltaY()/20);
- } else {
- scale *= Math.pow(delta,event.getDeltaY()/20);
- }
…那最终是一样的:
- scale *= Math.pow(1.01,event.getDeltaY());
第二,我建议使用画布翻译和缩放属性而不是转换:
- public class ZoomApplication extends Application {
- static public class PannableCanvas extends Pane {
- DoubleProperty myScale = new SimpleDoubleProperty(1.0);
- public PannableCanvas() {
- setPrefSize(600,600);
- setStyle("-fx-background-color: lightgrey; -fx-border-color: blue;");
- // add scale transform
- scaleXProperty().bind(myScale);
- scaleYProperty().bind(myScale);
- // logging
- addEventFilter(MouseEvent.MOUSE_PRESSED,event -> {
- System.out.println(
- "canvas event: " + ( ((event.getSceneX() - getBoundsInParent().getMinX()) / getScale()) + ",scale: " + getScale())
- );
- System.out.println( "canvas bounds: " + getBoundsInParent());
- });
- }
- /**
- * Add a grid to the canvas,send it to back
- */
- public void addGrid() {
- double w = getBoundsInLocal().getWidth();
- double h = getBoundsInLocal().getHeight();
- // add grid
- Canvas grid = new Canvas(w,h);
- // don't catch mouse events
- grid.setMouseTransparent(true);
- GraphicsContext gc = grid.getGraphicsContext2D();
- gc.setStroke(Color.GRAY);
- gc.setLineWidth(1);
- // draw grid lines
- double offset = 50;
- for( double i=offset; i < w; i+=offset) {
- // vertical
- gc.strokeLine( i,h);
- // horizontal
- gc.strokeLine( 0,i);
- }
- getChildren().add( grid);
- grid.toBack();
- }
- public double getScale() {
- return myScale.get();
- }
- /**
- * Set x/y scale
- * @param myScale
- */
- public void setScale( double scale) {
- myScale.set(scale);
- }
- /**
- * Set x/y pivot points
- * @param x
- * @param y
- */
- public void setPivot( double x,double y) {
- setTranslateX(getTranslateX()-x);
- setTranslateY(getTranslateY()-y);
- }
- }
- /**
- * Mouse drag context used for scene and nodes.
- */
- class DragContext {
- double mouseAnchorX;
- double mouseAnchorY;
- double translateAnchorX;
- double translateAnchorY;
- }
- /**
- * Listeners for making the nodes draggable via left mouse button. Considers if parent is zoomed.
- */
- class NodeGestures {
- private DragContext nodeDragContext = new DragContext();
- PannableCanvas canvas;
- public NodeGestures( PannableCanvas canvas) {
- this.canvas = canvas;
- }
- public EventHandler<MouseEvent> getOnMousePressedEventHandler() {
- return onMousePressedEventHandler;
- }
- public EventHandler<MouseEvent> getOnMouseDraggedEventHandler() {
- return onMouseDraggedEventHandler;
- }
- private EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
- public void handle(MouseEvent event) {
- // left mouse button => dragging
- if( !event.isPrimaryButtonDown())
- return;
- nodeDragContext.mouseAnchorX = event.getSceneX();
- nodeDragContext.mouseAnchorY = event.getSceneY();
- Node node = (Node) event.getSource();
- nodeDragContext.translateAnchorX = node.getTranslateX();
- nodeDragContext.translateAnchorY = node.getTranslateY();
- }
- };
- private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
- public void handle(MouseEvent event) {
- // left mouse button => dragging
- if( !event.isPrimaryButtonDown())
- return;
- double scale = canvas.getScale();
- Node node = (Node) event.getSource();
- node.setTranslateX(nodeDragContext.translateAnchorX + (( event.getSceneX() - nodeDragContext.mouseAnchorX) / scale));
- node.setTranslateY(nodeDragContext.translateAnchorY + (( event.getSceneY() - nodeDragContext.mouseAnchorY) / scale));
- event.consume();
- }
- };
- }
- /**
- * Listeners for making the scene's canvas draggable and zoomable
- */
- class SceneGestures {
- private static final double MAX_SCALE = 10.0d;
- private static final double MIN_SCALE = .1d;
- private DragContext sceneDragContext = new DragContext();
- PannableCanvas canvas;
- public SceneGestures( PannableCanvas canvas) {
- this.canvas = canvas;
- }
- 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) {
- // right mouse button => panning
- if( !event.isSecondaryButtonDown())
- return;
- sceneDragContext.mouseAnchorX = event.getSceneX();
- sceneDragContext.mouseAnchorY = event.getSceneY();
- sceneDragContext.translateAnchorX = canvas.getTranslateX();
- sceneDragContext.translateAnchorY = canvas.getTranslateY();
- }
- };
- private EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
- public void handle(MouseEvent event) {
- // right mouse button => panning
- if( !event.isSecondaryButtonDown())
- return;
- canvas.setTranslateX(sceneDragContext.translateAnchorX + event.getSceneX() - sceneDragContext.mouseAnchorX);
- canvas.setTranslateY(sceneDragContext.translateAnchorY + event.getSceneY() - 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 scale = canvas.getScale(); // currently we only use Y,same value is used for X
- double oldScale = scale;
- scale *= Math.pow(1.01,event.getDeltaY());
- if (scale <= MIN_SCALE) {
- scale = MIN_SCALE;
- } else if (scale >= MAX_SCALE) {
- scale = MAX_SCALE;
- }
- double f = (scale / oldScale)-1;
- double dx = (event.getSceneX() - (canvas.getBoundsInParent().getWidth()/2 + canvas.getBoundsInParent().getMinX()));
- double dy = (event.getSceneY() - (canvas.getBoundsInParent().getHeight()/2 + canvas.getBoundsInParent().getMinY()));
- canvas.setScale( scale);
- canvas.setPivot(f*dx,f*dy);
- event.consume();
- }
- };
- }
- public static void main(String[] args) {
- launch(args);
- }
- @Override
- public void start(Stage stage) {
- Group group = new Group();
- // create canvas
- PannableCanvas canvas = new PannableCanvas();
- // we don't want the canvas on the top/left in this example => just
- // translate it a bit
- canvas.setTranslateX(100);
- canvas.setTranslateY(100);
- // create sample nodes which can be dragged
- NodeGestures nodeGestures = new NodeGestures( canvas);
- Label label1 = new Label("Draggable node 1");
- label1.setTranslateX(10);
- label1.setTranslateY(10);
- label1.addEventFilter( MouseEvent.MOUSE_PRESSED,sceneGestures.getOnScrollEventHandler());
- stage.setScene(scene);
- stage.show();
- canvas.addGrid();
- }
- }
经过一些关于缩放的想法,我得出结论,这将是一个好主意
>编写一个独立的缩放辅助方法来简化缩放功能
>也可以使用相同的方法支持捏合到缩放手势
所以我写了以下帮助方法:
- /** Allow to zoom/scale any node with pivot at scene (x,y) coordinates.
- *
- * @param node
- * @param delta
- * @param x
- * @param y
- */
- public static void zoom(Node node,double factor,double x,double y) {
- double oldScale = node.getScaleX();
- double scale = oldScale * factor;
- if (scale < 0.05) scale = 0.05;
- if (scale > 50) scale = 50;
- node.setScaleX(scale);
- node.setScaleY(scale);
- double f = (scale / oldScale)-1;
- Bounds bounds = node.localToScene(node.getBoundsInLocal());
- double dx = (x - (bounds.getWidth()/2 + bounds.getMinX()));
- double dy = (y - (bounds.getHeight()/2 + bounds.getMinY()));
- node.setTranslateX(node.getTranslateX()-f*dx);
- node.setTranslateY(node.getTranslateY()-f*dy);
- }
- public static void zoom(Node node,ScrollEvent event) {
- zoom(node,Math.pow(1.01,event.getDeltaY()),event.getSceneX(),event.getSceneY());
- }
- public static void zoom(Node node,ZoomEvent event) {
- zoom(node,event.getZoomFactor(),event.getSceneY());
- }
- myView.setOnScroll(event -> GUITools.zoom(myView,event)); // mouse scroll wheel zoom
- myView.setOnZoom(event -> GUITools.zoom(myView,event)); // pinch to zoom
并做了…