问题
I can't get working zoom relative to mouse position in JavaFX. I've read and tried rewrite this and this, but it's not working. Can't scroll viewport using scrollPane.setViewportBounds()
. I must use scrollPane.setHvalue()
and scrollPane.setVvalue()
, but can't get right recalculation to keep the same pixels under the mouse cursor. Current code keeps scrollbars positions:
ZoomHandler.java
import javafx.event.EventHandler;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Pane;
public class ZoomHandler implements EventHandler<ScrollEvent> {
private final ScrollPane scrollPane;
private final Pane canvas;
private final double minScale;
private final double maxScale;
public ZoomHandler(ScrollPane scrollPane, Pane canvas, double minScale, double maxScale) {
this.scrollPane = scrollPane;
this.canvas = canvas;
this.minScale = minScale;
this.maxScale = maxScale;
}
public ZoomHandler(ScrollPane scrollPane, Pane canvas) {
this(scrollPane, canvas, 0.1, 10);
}
@Override
public void handle(ScrollEvent e) {
if (e.isControlDown()) {
double actualScale = canvas.getScaleX();
if (actualScale > maxScale || actualScale < minScale) {
e.consume();
return;
}
double hVal = scrollPane.getHvalue();
double vVal = scrollPane.getVvalue();
double scale, factor;
if (e.getDeltaY() > 0) {
factor = 1.1;
} else {
factor = 0.9;
}
scale = actualScale * factor;
scale = Math.min(scale, maxScale);
scale = Math.max(scale, minScale);
canvas.setScaleX(scale);
canvas.setScaleY(scale);
scrollPane.setHvalue(hVal);
scrollPane.setVvalue(vVal);
e.consume();
}
}
}
Main.java
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class Main extends Application {
public static final int CANVAS_WIDTH = 1000;
public static final int CANVAS_HEIGHT = 1000;
private final Pane canvas = new Pane();
private final ScrollPane scrollPane = new ScrollPane();
private final ZoomHandler zoomHandler = new ZoomHandler(scrollPane, canvas);
public static void main(String[] args) { launch(args); }
@Override public void start(Stage primaryStage) {
canvas.setPrefWidth(CANVAS_WIDTH);
canvas.setPrefHeight(CANVAS_HEIGHT);
canvas.setOnScroll(zoomHandler);
scrollPane.setStyle("-fx-background-color: transparent; -fx-control-inner-background: transparent;");
scrollPane.setContent(new Group(canvas));
scrollPane.setPannable(true);
scrollPane.setOnScroll(zoomHandler);
addContent();
Scene scene = new Scene(scrollPane, 800, 600);
primaryStage.setTitle("Zoom");
primaryStage.setScene(scene);
primaryStage.show();
scrollPane.setHvalue(0.5);
scrollPane.setVvalue(0.5);
}
private void addContent() {
Text hello = new Text(400, 500, "Hello");
hello.setStyle("-fx-font-size: 20px; -fx-fill: red;");
Text world = new Text(500, 600, "world!");
world.setStyle("-fx-fill: blue;");
Text text = new Text(200, 350, "How to zoom to this text when mouse is over?");
text.setStyle("-fx-font-weight: bold;");
Rectangle rect = new Rectangle(600, 450, 300, 200);
rect.setFill(Color.GREEN);
Circle circle = new Circle(250, 700, 100);
circle.setFill(Color.YELLOW);
canvas.getChildren().addAll(hello, world, text, rect, circle);
}
}
Any ideas? Thanks in advance.
回答1:
I had the same problem and I did the following - I wouldn't call is a proper solution (rather a workaround), but it works mostly fine.
Basically, I do some calculations to retrieve the correct values of Hvalue and Vvalue. Moreover, I have to apply a compensation factor due to the fact that the ScrollPane will do not only the zooming but also a vertical translation of the image (due to standard behaviour of the ScrollPane).
Here you find the code.
/**
* Pane containing an image that can be resized.
*/
public class SizeableImageView extends ScrollPane {
/**
* The zoom factor to be applied for each zoom event.
*
* (480th root of 2 means that 12 wheel turns of 40 will result in size factor 2.)
*/
private static final double ZOOM_FACTOR = 1.0014450997779993488675056142818;
/**
* The zoom factor.
*/
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(1000);
/**
* The mouse X position.
*/
private final DoubleProperty mouseXProperty = new SimpleDoubleProperty();
/**
* The mouse Y position.
*/
private final DoubleProperty mouseYProperty = new SimpleDoubleProperty();
/**
* Constructor without initialization of image.
*/
public SizeableImageView() {
this(new ImageView());
}
/**
* Constructor, initializing with an image view.
*
* @param imageView
* The ImageView to be displayed.
*/
public SizeableImageView(final ImageView imageView) {
setContent(imageView);
setPannable(true);
setHbarPolicy(ScrollBarPolicy.NEVER);
setVbarPolicy(ScrollBarPolicy.NEVER);
setOnMouseMoved(new EventHandler<MouseEvent>() {
@Override
public void handle(final MouseEvent event) {
mouseXProperty.set(event.getX());
mouseYProperty.set(event.getY());
}
});
addEventFilter(ScrollEvent.ANY, new EventHandler<ScrollEvent>() {
@Override
public void handle(final ScrollEvent event) {
ImageView image = (ImageView) getContent();
// Original size of the image.
double sourceWidth = zoomProperty.get() * image.getImage().getWidth();
double sourceHeight = zoomProperty.get() * image.getImage().getHeight();
zoomProperty.set(zoomProperty.get() * Math.pow(ZOOM_FACTOR, event.getDeltaY()));
// Old values of the scrollbars.
double oldHvalue = getHvalue();
double oldVvalue = getVvalue();
// Image pixels outside the visible area which need to be scrolled.
double preScrollXFactor = Math.max(0, sourceWidth - getWidth());
double preScrollYFactor = Math.max(0, sourceHeight - getHeight());
// Relative position of the mouse in the image.
double mouseXPosition = (mouseXProperty.get() + preScrollXFactor * oldHvalue) / sourceWidth;
double mouseYPosition = (mouseYProperty.get() + preScrollYFactor * oldVvalue) / sourceHeight;
// Target size of the image.
double targetWidth = zoomProperty.get() * image.getImage().getWidth();
double targetHeight = zoomProperty.get() * image.getImage().getHeight();
// Image pixels outside the visible area which need to be scrolled.
double postScrollXFactor = Math.max(0, targetWidth - getWidth());
double postScrollYFactor = Math.max(0, targetHeight - getHeight());
// Correction applied to compensate the vertical scrolling done by ScrollPane
double verticalCorrection = (postScrollYFactor / sourceHeight) * event.getDeltaY();
// New scrollbar positions keeping the mouse position.
double newHvalue = ((mouseXPosition * targetWidth) - mouseXProperty.get()) / postScrollXFactor;
double newVvalue =
((mouseYPosition * targetHeight) - mouseYProperty.get() + verticalCorrection)
/ postScrollYFactor;
image.setFitWidth(targetWidth);
image.setFitHeight(targetHeight);
setHvalue(newHvalue);
setVvalue(newVvalue);
}
});
addEventFilter(ZoomEvent.ANY, new EventHandler<ZoomEvent>() {
@Override
public void handle(final ZoomEvent event) {
zoomProperty.set(zoomProperty.get() * event.getZoomFactor());
ImageView image = (ImageView) getContent();
image.setFitWidth(zoomProperty.get() * image.getImage().getWidth());
image.setFitHeight(zoomProperty.get() * image.getImage().getHeight());
}
});
}
/**
* Set the image view displayed by this class.
*
* @param imageView
* The ImageView.
*/
public final void setImageView(final ImageView imageView) {
setContent(imageView);
zoomProperty.set(Math.min(imageView.getFitWidth() / imageView.getImage().getWidth(), imageView.getFitHeight()
/ imageView.getImage().getHeight()));
}
}
来源:https://stackoverflow.com/questions/22453814/javafx-zoom-relative-to-mouse-position