В предыдущем посте я спросил, почему масштабирование с помощью ползунка не работает должным образом, когда я привязываю ползунок в двух направлениях к масштабу, чтобы гарантировать, что ползунок обновляется во время масштабирования колеса.
В комментарии James_D предположил, что ползунок может обновлять свойство масштаба до того, как будет вызван прослушиватель свойства значения ползунка. Он был прав, поэтому newScale - oldScale всегда равен 0, заставляя прослушиватель каждый раз возвращаться и завершать работу.
Исправления и проблема
Чтобы устранить проблему, я использовал слайдер.valueProperty().addListener((_, oldV, newV) вместо слайдер.valueProperty().addListener((_, _, newV) для доступа к предыдущему значению свойства до его изменения из-за привязки.
Хотя проблема исчезла при масштабировании с помощью ползунка, теперь она появляется при масштабировании с помощью колеса, перемещаясь в те же положения, которые упоминались ранее.
Я также заметил, что минимум ползунка значение не обновляется при изменении размера окна. Чтобы исправить это, CENTER_FIT_SCALE пересчитывается каждый раз, когда изменяется LayoutBoundsProperty области просмотра. Это позволяет установить минимальное значение ползунка на Math.min(viewport.getWidth() / image.getWidth(), viewport.getHeight() / image.getHeight()), представляющее коэффициент масштабирования, обеспечивающий изображение помещается в контейнер, сохраняя при этом соотношение сторон.
Чтобы убедиться, что масштабирование с помощью ползунка и масштабирование с помощью колеса используют правильные значения oldScale и newScale, я выполняю последовательные действия масштабирования, используя один метод, затем переключаюсь на другой, каждый раз печатая новый и старый масштаб. Моя цель — убедиться, что ползунок устанавливает новый масштаб, а колесо расчеты точны, и при возобновлении работы старая шкала становится новой. Это показало, что значения правильно установлены и рассчитаны, и они корректно возобновляются при переключении.
После обширных проверок и отладки проблема, похоже, связана с Pivot. В методе колеса параметры tx и ty устанавливаются следующим образом:
Код: Выделить всё
tx.set(tx.get() + (oldScale - newScale) * pivotOnImage.getX());
ty.set(ty.get() + (oldScale - newScale) * pivotOnImage.getY());
Код: Выделить всё
Point2D pivotOnImage = imageView.sceneToLocal(viewport.localToScene(viewport.getWidth() / 2, viewport.getHeight() / 2));
tx.set(tx.get() + (oldScale - newScale) * pivotOnImage.getX());
ty.set(ty.get() + (oldScale - newScale) * pivotOnImage.getY());
Другой факт, подтверждающий, что причиной является опорный элемент, является удаление PivotOnImage.getX(), PivotOnImage.getY() из формулы, устанавливающей tx и ty в слайдере, проблема переходит от метода колеса обратно к слайдеру!!!!
Обновленный код
Код: Выделить всё
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.transform.Affine;
import javafx.stage.Stage;
import javafx.scene.control.Slider;
import javafx.util.StringConverter;
import java.util.Objects;
public class ZoomAndShiftImage extends Application {
private final double MAX_SCALE = 8.0;
private static double CENTER_FIT_SCALE;
@Override
public void start(Stage stage) {
Image image = new Image(Objects.requireNonNull(Objects.requireNonNull(getClass().getResourceAsStream("/playground/bird.jpg"))));
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);
imageView.setSmooth(true);
Affine affine = new Affine();
imageView.getTransforms().add(affine);
Pane viewport = new Pane(imageView);
Rectangle clip = new Rectangle();
clip.widthProperty().bind(viewport.widthProperty());
clip.heightProperty().bind(viewport.heightProperty());
viewport.setClip(clip);
viewport.setStyle("-fx-background-color: #1d2026;");
final Delta drag = new Delta();
DoubleProperty scale = new SimpleDoubleProperty(1.0);
DoubleProperty tx = new SimpleDoubleProperty(0);
DoubleProperty ty = new SimpleDoubleProperty(0);
Runnable applyTransform = () -> affine.setToTransform(scale.get(), 0, tx.get(), 0, scale.get(), ty.get());
scale.addListener((_, _, _) -> applyTransform.run());
tx.addListener((_, _, _) -> applyTransform.run());
ty.addListener((_, _, _) -> applyTransform.run());
applyTransform.run();
Slider slider = getSlider();
slider.setMax(MAX_SCALE);
slider.valueProperty().bindBidirectional(scale);
viewport.layoutBoundsProperty().addListener((_, _, _) -> {
CENTER_FIT_SCALE = Math.min(viewport.getWidth() / image.getWidth(), viewport.getHeight() / image.getHeight());
slider.setMin(CENTER_FIT_SCALE);
centerFit(imageView, viewport, scale, tx, ty);
});
viewport.setOnMousePressed(e -> {
if (e.getButton() == MouseButton.PRIMARY) {
// Save the current coordinates of the click
drag.x = e.getX();
drag.y = e.getY();
}
});
viewport.setOnMouseDragged(e -> {
double dx = e.getX() - drag.x;
double dy = e.getY() - drag.y;
drag.x = e.getX();
drag.y = e.getY();
tx.set(tx.get() + dx);
ty.set(ty.get() + dy);
});
viewport.setOnMouseClicked(e -> {
if (e.getClickCount() == 2 && e.getButton() == MouseButton.PRIMARY) {
scale.set(1.0);
tx.set(0.0);
ty.set(0.0);
centerFit(imageView, viewport, scale, tx, ty);
}
});
viewport.addEventFilter(ScrollEvent.SCROLL, e -> {
Point2D pivotOnImage = imageView.sceneToLocal(e.getSceneX(), e.getSceneY());
if (!imageView.getBoundsInLocal().contains(pivotOnImage))
return;
double direction = (e.getDeltaY() > 0) ? 1.1 : (1 / 1.1);
double oldScale = scale.get();
double newScale = clamp(oldScale, direction, CENTER_FIT_SCALE);
System.out.println("wheel: " + oldScale + " | " + newScale + " | " + CENTER_FIT_SCALE);
if (newScale == 0) {
centerFit(imageView, viewport, scale, tx, ty);
return;
}
tx.set(tx.get() + (oldScale - newScale) * pivotOnImage.getX());
ty.set(ty.get() + (oldScale - newScale) * pivotOnImage.getY());
scale.set(newScale);
e.consume();
});
slider.valueProperty().addListener((_, oldV, newV) -> {
double oldScale = oldV.doubleValue();
double newScale = newV.doubleValue();
if (Math.abs(newScale - oldScale) < 1e-9)
return;
if (newV.doubleValue()
Подробнее здесь: [url]https://stackoverflow.com/questions/79803086/how-binding-the-slider-value-property-to-a-scale-property-makes-the-pivot-calcul[/url]
Мобильная версия