2013-05-21 148 views
14

我想缩放滚动事件中窗格中的所有节点。JavaFX正确缩放

我迄今为止尝试:

  1. 当我做的scaleX或的scaleY,分别 秤面板的边框(设置窗格样式-fx-border-color: black;时看到的)。因此,如果我不是来自边框的边框 ,那么不是每个事件都会开始,所以我需要这一切。 enter image description here

  2. 下一步我试图缩放每个节点,结果变得非常糟糕, 这样的事情 - (线条通过点延伸)。或者如果 在另一侧滚动,则会更少enter image description here

  3. 我试过的另一种方法是缩放Node节点。这是更好,但 我不喜欢它。它看起来像 point.setScaleX(point.getScaleX()+scaleX)和适当的y和其他节点 。

+0

可伸缩,可平移的scrollpane:https://stackoverflow.com/a/44314455/1386911 –

回答

30

我创建了一个示例应用一种方法表明在上滚动事件视口执行节点的缩放(例如,通过滚动鼠标滚轮滚动输入和输出)。

关键逻辑到样品用于缩放置于StackPane内的基团:

final double SCALE_DELTA = 1.1; 
final StackPane zoomPane = new StackPane(); 

zoomPane.getChildren().add(group); 
zoomPane.setOnScroll(new EventHandler<ScrollEvent>() { 
    @Override public void handle(ScrollEvent event) { 
    event.consume(); 

    if (event.getDeltaY() == 0) { 
     return; 
    } 

    double scaleFactor = 
     (event.getDeltaY() > 0) 
     ? SCALE_DELTA 
     : 1/SCALE_DELTA; 

    group.setScaleX(group.getScaleX() * scaleFactor); 
    group.setScaleY(group.getScaleY() * scaleFactor); 
    } 
}); 

滚动事件处理程序上封闭StackPane其是可调整大小的窗格所以它膨胀以填充任何空的空间设置,将缩放的内容放在窗格的中心。如果您将鼠标滚轮移动到StackPane中的任何位置,它将放大或缩小所包含的一组节点。

zoomyzoomyin

import javafx.application.Application; 
import javafx.beans.value.*; 
import javafx.event.*; 
import javafx.geometry.Bounds; 
import javafx.scene.*; 
import javafx.scene.control.*; 
import javafx.scene.image.*; 
import javafx.scene.input.*; 
import javafx.scene.layout.*; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.*; 
import javafx.stage.Stage; 

public class GraphicsScalingApp extends Application { 
    public static void main(String[] args) { launch(args); } 

    @Override public void start(final Stage stage) { 
    final Group group = new Group(
     createStar(), 
     createCurve() 
    ); 

    Parent zoomPane = createZoomPane(group); 

    VBox layout = new VBox(); 
    layout.getChildren().setAll(
     createMenuBar(stage, group), 
     zoomPane 
    ); 

    VBox.setVgrow(zoomPane, Priority.ALWAYS); 
    Scene scene = new Scene(
     layout 
    ); 

    stage.setTitle("Zoomy"); 
    stage.getIcons().setAll(new Image(APP_ICON)); 
    stage.setScene(scene); 
    stage.show(); 
    } 

    private Parent createZoomPane(final Group group) { 
    final double SCALE_DELTA = 1.1; 
    final StackPane zoomPane = new StackPane(); 

    zoomPane.getChildren().add(group); 
    zoomPane.setOnScroll(new EventHandler<ScrollEvent>() { 
     @Override public void handle(ScrollEvent event) { 
     event.consume(); 

     if (event.getDeltaY() == 0) { 
      return; 
     } 

     double scaleFactor = 
      (event.getDeltaY() > 0) 
      ? SCALE_DELTA 
      : 1/SCALE_DELTA; 

     group.setScaleX(group.getScaleX() * scaleFactor); 
     group.setScaleY(group.getScaleY() * scaleFactor); 
     } 
    }); 

    zoomPane.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() { 
     @Override public void changed(ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds bounds) { 
     zoomPane.setClip(new Rectangle(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())); 
     } 
    }); 

    return zoomPane; 
    } 

    private SVGPath createCurve() { 
    SVGPath ellipticalArc = new SVGPath(); 
    ellipticalArc.setContent(
     "M10,150 A15 15 180 0 1 70 140 A15 25 180 0 0 130 130 A15 55 180 0 1 190 120" 
    ); 
    ellipticalArc.setStroke(Color.LIGHTGREEN); 
    ellipticalArc.setStrokeWidth(4); 
    ellipticalArc.setFill(null); 
    return ellipticalArc; 
    } 

    private SVGPath createStar() { 
    SVGPath star = new SVGPath(); 
    star.setContent(
     "M100,10 L100,10 40,180 190,60 10,60 160,180 z" 
    ); 
    star.setStrokeLineJoin(StrokeLineJoin.ROUND); 
    star.setStroke(Color.BLUE); 
    star.setFill(Color.DARKBLUE); 
    star.setStrokeWidth(4); 
    return star; 
    } 

    private MenuBar createMenuBar(final Stage stage, final Group group) { 
    Menu fileMenu = new Menu("_File"); 
    MenuItem exitMenuItem = new MenuItem("E_xit"); 
    exitMenuItem.setGraphic(new ImageView(new Image(CLOSE_ICON))); 
    exitMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override public void handle(ActionEvent event) { 
     stage.close(); 
     } 
    }); 
    fileMenu.getItems().setAll(
     exitMenuItem 
    ); 
    Menu zoomMenu = new Menu("_Zoom"); 
    MenuItem zoomResetMenuItem = new MenuItem("Zoom _Reset"); 
    zoomResetMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE)); 
    zoomResetMenuItem.setGraphic(new ImageView(new Image(ZOOM_RESET_ICON))); 
    zoomResetMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override public void handle(ActionEvent event) { 
     group.setScaleX(1); 
     group.setScaleY(1); 
     } 
    }); 
    MenuItem zoomInMenuItem = new MenuItem("Zoom _In"); 
    zoomInMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.I)); 
    zoomInMenuItem.setGraphic(new ImageView(new Image(ZOOM_IN_ICON))); 
    zoomInMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override public void handle(ActionEvent event) { 
     group.setScaleX(group.getScaleX() * 1.5); 
     group.setScaleY(group.getScaleY() * 1.5); 
     } 
    }); 
    MenuItem zoomOutMenuItem = new MenuItem("Zoom _Out"); 
    zoomOutMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.O)); 
    zoomOutMenuItem.setGraphic(new ImageView(new Image(ZOOM_OUT_ICON))); 
    zoomOutMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override public void handle(ActionEvent event) { 
     group.setScaleX(group.getScaleX() * 1/1.5); 
     group.setScaleY(group.getScaleY() * 1/1.5); 
     } 
    }); 
    zoomMenu.getItems().setAll(
     zoomResetMenuItem, 
     zoomInMenuItem, 
     zoomOutMenuItem 
    ); 
    MenuBar menuBar = new MenuBar(); 
    menuBar.getMenus().setAll(
     fileMenu, 
     zoomMenu 
    ); 
    return menuBar; 
    } 

    // icons source from: http://www.iconarchive.com/show/soft-scraps-icons-by-deleket.html 
    // icon license: CC Attribution-Noncommercial-No Derivate 3.0 =? http://creativecommons.org/licenses/by-nc-nd/3.0/ 
    // icon Commercial usage: Allowed (Author Approval required -> Visit artist website for details). 

    public static final String APP_ICON  = "http://icons.iconarchive.com/icons/deleket/soft-scraps/128/Zoom-icon.png"; 
    public static final String ZOOM_RESET_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-icon.png"; 
    public static final String ZOOM_OUT_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-Out-icon.png"; 
    public static final String ZOOM_IN_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-In-icon.png"; 
    public static final String CLOSE_ICON  = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Button-Close-icon.png"; 
} 

更新为一个ScrollPane

上述实施效果很好,只要它进入缩放节点,但它是能够把缩放节点有用在滚动窗格中,以便在放大缩放节点时使其大于可用视口时,仍然可以在滚动窗格内平移缩放节点以查看节点的各个部分。

我发现实现放大滚动窗格的行为很困难,所以我在Oracle JavaFX Forum thread上寻求帮助。

Oracle JavaFX论坛用户James_D提出了以下解决方案,它很好地解决了ScrollPane问题中的缩放问题。

他的评论和代码分别如下:

一个微小的改动第一:我包裹StackPane在一个组,以便将ScrollPane会知道的更改变换,按滚动窗格Javadoc中。然后我将StackPane的最小尺寸绑定到视口尺寸(当视口小于内容时,保持内容居中)。

最初我以为我应该使用缩放变换来缩放显示的中心(即,位于视口中心的内容点)。但是我发现我仍然需要修改后滚动位置以保持相同的显示中心,所以我放弃了这一点,并恢复使用setScaleX()和setScaleY()。

诀窍是在缩放后修复滚动位置。我计算了滚动内容的局部坐标中的滚动偏移量,然后计算了缩放后需要的新滚动值。这有点棘手。基本观察是, (hValue-hMin)/(hMax-hMin)= x /(contentWidth -viewportWidth),其中x是视口左边缘与内容左边缘的水平偏移。 然后你有centerX = x + viewportWidth/2。

缩放后,旧centerX的x坐标现在为centerX * scaleFactor。所以我们只需设置新的h值就可以创建新的中心。有一些代数可以解释这一点。

之后,通过拖动平移非常容易:)。

添加高级API以支持ScrollPane中的缩放和缩放功能的相应功能请求为Add scaleContent functionality to ScrollPane。如果您希望看到它实施,请投票或评论功能要求。

import javafx.application.Application; 
import javafx.beans.property.ObjectProperty; 
import javafx.beans.property.SimpleObjectProperty; 
import javafx.beans.value.*; 
import javafx.event.*; 
import javafx.geometry.Bounds; 
import javafx.geometry.Point2D; 
import javafx.scene.*; 
import javafx.scene.control.*; 
import javafx.scene.image.*; 
import javafx.scene.input.*; 
import javafx.scene.layout.*; 
import javafx.scene.paint.Color; 
import javafx.scene.shape.*; 
import javafx.stage.Stage; 

public class GraphicsScalingApp extends Application { 
    public static void main(String[] args) { 
    launch(args); 
    } 

    @Override 
    public void start(final Stage stage) { 
    final Group group = new Group(createStar(), createCurve()); 

    Parent zoomPane = createZoomPane(group); 

    VBox layout = new VBox(); 
    layout.getChildren().setAll(createMenuBar(stage, group), zoomPane); 

    VBox.setVgrow(zoomPane, Priority.ALWAYS); 

    Scene scene = new Scene(layout); 

    stage.setTitle("Zoomy"); 
    stage.getIcons().setAll(new Image(APP_ICON)); 
    stage.setScene(scene); 
    stage.show(); 
    } 

    private Parent createZoomPane(final Group group) { 
    final double SCALE_DELTA = 1.1; 
    final StackPane zoomPane = new StackPane(); 

    zoomPane.getChildren().add(group); 

    final ScrollPane scroller = new ScrollPane(); 
    final Group scrollContent = new Group(zoomPane); 
    scroller.setContent(scrollContent); 

    scroller.viewportBoundsProperty().addListener(new ChangeListener<Bounds>() { 
     @Override 
     public void changed(ObservableValue<? extends Bounds> observable, 
      Bounds oldValue, Bounds newValue) { 
     zoomPane.setMinSize(newValue.getWidth(), newValue.getHeight()); 
     } 
    }); 

    scroller.setPrefViewportWidth(256); 
    scroller.setPrefViewportHeight(256); 

    zoomPane.setOnScroll(new EventHandler<ScrollEvent>() { 
     @Override 
     public void handle(ScrollEvent event) { 
     event.consume(); 

     if (event.getDeltaY() == 0) { 
      return; 
     } 

     double scaleFactor = (event.getDeltaY() > 0) ? SCALE_DELTA 
      : 1/SCALE_DELTA; 

     // amount of scrolling in each direction in scrollContent coordinate 
     // units 
     Point2D scrollOffset = figureScrollOffset(scrollContent, scroller); 

     group.setScaleX(group.getScaleX() * scaleFactor); 
     group.setScaleY(group.getScaleY() * scaleFactor); 

     // move viewport so that old center remains in the center after the 
     // scaling 
     repositionScroller(scrollContent, scroller, scaleFactor, scrollOffset); 

     } 
    }); 

    // Panning via drag.... 
    final ObjectProperty<Point2D> lastMouseCoordinates = new SimpleObjectProperty<Point2D>(); 
    scrollContent.setOnMousePressed(new EventHandler<MouseEvent>() { 
     @Override 
     public void handle(MouseEvent event) { 
     lastMouseCoordinates.set(new Point2D(event.getX(), event.getY())); 
     } 
    }); 

    scrollContent.setOnMouseDragged(new EventHandler<MouseEvent>() { 
     @Override 
     public void handle(MouseEvent event) { 
     double deltaX = event.getX() - lastMouseCoordinates.get().getX(); 
     double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth(); 
     double deltaH = deltaX * (scroller.getHmax() - scroller.getHmin())/extraWidth; 
     double desiredH = scroller.getHvalue() - deltaH; 
     scroller.setHvalue(Math.max(0, Math.min(scroller.getHmax(), desiredH))); 

     double deltaY = event.getY() - lastMouseCoordinates.get().getY(); 
     double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight(); 
     double deltaV = deltaY * (scroller.getHmax() - scroller.getHmin())/extraHeight; 
     double desiredV = scroller.getVvalue() - deltaV; 
     scroller.setVvalue(Math.max(0, Math.min(scroller.getVmax(), desiredV))); 
     } 
    }); 

    return scroller; 
    } 

    private Point2D figureScrollOffset(Node scrollContent, ScrollPane scroller) { 
    double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth(); 
    double hScrollProportion = (scroller.getHvalue() - scroller.getHmin())/(scroller.getHmax() - scroller.getHmin()); 
    double scrollXOffset = hScrollProportion * Math.max(0, extraWidth); 
    double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight(); 
    double vScrollProportion = (scroller.getVvalue() - scroller.getVmin())/(scroller.getVmax() - scroller.getVmin()); 
    double scrollYOffset = vScrollProportion * Math.max(0, extraHeight); 
    return new Point2D(scrollXOffset, scrollYOffset); 
    } 

    private void repositionScroller(Node scrollContent, ScrollPane scroller, double scaleFactor, Point2D scrollOffset) { 
    double scrollXOffset = scrollOffset.getX(); 
    double scrollYOffset = scrollOffset.getY(); 
    double extraWidth = scrollContent.getLayoutBounds().getWidth() - scroller.getViewportBounds().getWidth(); 
    if (extraWidth > 0) { 
     double halfWidth = scroller.getViewportBounds().getWidth()/2 ; 
     double newScrollXOffset = (scaleFactor - 1) * halfWidth + scaleFactor * scrollXOffset; 
     scroller.setHvalue(scroller.getHmin() + newScrollXOffset * (scroller.getHmax() - scroller.getHmin())/extraWidth); 
    } else { 
     scroller.setHvalue(scroller.getHmin()); 
    } 
    double extraHeight = scrollContent.getLayoutBounds().getHeight() - scroller.getViewportBounds().getHeight(); 
    if (extraHeight > 0) { 
     double halfHeight = scroller.getViewportBounds().getHeight()/2 ; 
     double newScrollYOffset = (scaleFactor - 1) * halfHeight + scaleFactor * scrollYOffset; 
     scroller.setVvalue(scroller.getVmin() + newScrollYOffset * (scroller.getVmax() - scroller.getVmin())/extraHeight); 
    } else { 
     scroller.setHvalue(scroller.getHmin()); 
    } 
    } 

    private SVGPath createCurve() { 
    SVGPath ellipticalArc = new SVGPath(); 
    ellipticalArc.setContent("M10,150 A15 15 180 0 1 70 140 A15 25 180 0 0 130 130 A15 55 180 0 1 190 120"); 
    ellipticalArc.setStroke(Color.LIGHTGREEN); 
    ellipticalArc.setStrokeWidth(4); 
    ellipticalArc.setFill(null); 
    return ellipticalArc; 
    } 

    private SVGPath createStar() { 
    SVGPath star = new SVGPath(); 
    star.setContent("M100,10 L100,10 40,180 190,60 10,60 160,180 z"); 
    star.setStrokeLineJoin(StrokeLineJoin.ROUND); 
    star.setStroke(Color.BLUE); 
    star.setFill(Color.DARKBLUE); 
    star.setStrokeWidth(4); 
    return star; 
    } 

    private MenuBar createMenuBar(final Stage stage, final Group group) { 
    Menu fileMenu = new Menu("_File"); 
    MenuItem exitMenuItem = new MenuItem("E_xit"); 
    exitMenuItem.setGraphic(new ImageView(new Image(CLOSE_ICON))); 
    exitMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override 
     public void handle(ActionEvent event) { 
     stage.close(); 
     } 
    }); 
    fileMenu.getItems().setAll(exitMenuItem); 
    Menu zoomMenu = new Menu("_Zoom"); 
    MenuItem zoomResetMenuItem = new MenuItem("Zoom _Reset"); 
    zoomResetMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.ESCAPE)); 
    zoomResetMenuItem.setGraphic(new ImageView(new Image(ZOOM_RESET_ICON))); 
    zoomResetMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override 
     public void handle(ActionEvent event) { 
     group.setScaleX(1); 
     group.setScaleY(1); 
     } 
    }); 
    MenuItem zoomInMenuItem = new MenuItem("Zoom _In"); 
    zoomInMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.I)); 
    zoomInMenuItem.setGraphic(new ImageView(new Image(ZOOM_IN_ICON))); 
    zoomInMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override 
     public void handle(ActionEvent event) { 
     group.setScaleX(group.getScaleX() * 1.5); 
     group.setScaleY(group.getScaleY() * 1.5); 
     } 
    }); 
    MenuItem zoomOutMenuItem = new MenuItem("Zoom _Out"); 
    zoomOutMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.O)); 
    zoomOutMenuItem.setGraphic(new ImageView(new Image(ZOOM_OUT_ICON))); 
    zoomOutMenuItem.setOnAction(new EventHandler<ActionEvent>() { 
     @Override 
     public void handle(ActionEvent event) { 
     group.setScaleX(group.getScaleX() * 1/1.5); 
     group.setScaleY(group.getScaleY() * 1/1.5); 
     } 
    }); 
    zoomMenu.getItems().setAll(zoomResetMenuItem, zoomInMenuItem, 
     zoomOutMenuItem); 
    MenuBar menuBar = new MenuBar(); 
    menuBar.getMenus().setAll(fileMenu, zoomMenu); 
    return menuBar; 
    } 

    // icons source from: 
    // http://www.iconarchive.com/show/soft-scraps-icons-by-deleket.html 
    // icon license: CC Attribution-Noncommercial-No Derivate 3.0 =? 
    // http://creativecommons.org/licenses/by-nc-nd/3.0/ 
    // icon Commercial usage: Allowed (Author Approval required -> Visit artist 
    // website for details). 

    public static final String APP_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/128/Zoom-icon.png"; 
    public static final String ZOOM_RESET_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-icon.png"; 
    public static final String ZOOM_OUT_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-Out-icon.png"; 
    public static final String ZOOM_IN_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Zoom-In-icon.png"; 
    public static final String CLOSE_ICON = "http://icons.iconarchive.com/icons/deleket/soft-scraps/24/Button-Close-icon.png"; 
} 
+0

非常感谢,它真的帮助我! – lummycoder

+0

你能用鼠标拖动吗? –

+0

没有周杰伦,这个答案已经够长了。如果您愿意,可以提出一个新问题。 – jewelsea

3

jewelsea的答案有一个问题,如果zoomPane中原始内容的大小已经大于查看端口。然后下面的代码将不起作用。 zoomPane.setMinSize(newValue.getWidth(),newValue.getHeight());

结果是当我们缩小,内容不再居中。

要解决此问题,您需要在zoomPane和ScrollPane之间创建另一个StackPane。

 // Create a zoom pane for zoom in/out 
    final StackPane zoomPane = new StackPane(); 
    zoomPane.getChildren().add(group); 
    final Group zoomContent = new Group(zoomPane); 
    // Create a pane for holding the content, when the content is smaller than the view port, 
    // it will stay the view port size, make sure the content is centered 
    final StackPane canvasPane = new StackPane(); 
    canvasPane.getChildren().add(zoomContent); 
    final Group scrollContent = new Group(canvasPane); 
    // Scroll pane for scrolling 
    scroller = new ScrollPane(); 
    scroller.setContent(scrollContent); 

而在viewportBoundsProperty监听器,更改zoomPane到canvasPane

// Set the minimum canvas size 
canvasPane.setMinSize(newValue.getWidth(), newValue.getHeight()); 

JavaFX是在输入/输出变焦太复杂了。为了达到同样的效果,WPF要容易得多。