JavaFX 动画不移动节点

JavaFX animation not moving the Node

我目前正在开发一个小型 JavaFX 库来显示 svg 内容。我正在实施动画。它们工作正常,但 x 和 y 位置的 animateanimateTransform 除外,我不明白为什么。

当我这样做时,它完美地工作:

public class TimelineTest extends Application {

   public static void main(String[] args) {
     launch(args);
  }

  public void start(Stage primaryStage) {
     Group root = new Group();
     Scene scene = new Scene(root, 800, 600);
     primaryStage.setScene(scene);
     Rectangle rect = new Rectangle(100, 100, 50, 50);
     root.getChildren().add(rect);
     rect.setFill(Color.BLUE);

     Timeline timeline = new Timeline();
     KeyFrame kf0 = new KeyFrame(Duration.ZERO, new KeyValue(rect.xProperty(), 0));
     KeyFrame kf1 = new KeyFrame(Duration.seconds(10), new KeyValue(rect.xProperty(), 100));
     timeline.getKeyFrames().addAll(kf0, kf1);
     timeline.play();

     primaryStage.show();
  }

}

但是当我在做我认为与我的库中完全相同的事情时,矩形似乎有点抽动但并没有真正移动。

我的代码是:

  WritableValue value = null;
  switch (nodeName) {
     case "rect":
        Rectangle rect = (Rectangle) node;
        switch (attrName) {
           case X:
              value = rect.xProperty();
              break;
           case Y:
              value = rect.yProperty();
              break;
           case WIDTH:
              value = rect.widthProperty();
              break;
           case HEIGHT:
              value = rect.heightProperty();
              break;
        }
        break;
  }  
  Timeline timeline = new Timeline();
  KeyValue fromValue = new KeyValue(value, 10);
  KeyValue toValue = new KeyValue(value, 100);
  KeyFrame fromFrame = new KeyFrame(Duration.ZERO, fromValue);
  KeyFrame toFrame = new KeyFrame(Duration.seconds(10), toValue);
  timeline.getKeyFrames().addAll(fromFrame, toFrame);

奇怪的是,当我对宽度或高度 属性 做同样的事情时,它没有任何问题。

我怀疑我的场景创建不正确(我检查过我是在平台 Thread 中做的所有这些),但其他一切都没有任何问题。

如果我尝试使用 TranslateTransition,我会遇到完全相同的问题。

在这里对这个问题发表了一些评论之后,我现在不明白为什么我会遇到这个问题(但不知道如何解决它,至少现在是这样)。我将 JavaFX 内容放在 ScrollPane 中。我不认为它在这种情况下是相关的,但它是。密码是:

     VBox vBox = new VBox(node);
     vBox.setAlignment(Pos.CENTER);
     Group group = new Group(vBox );
     StackPane content = new StackPane(group);
     group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
        content.setMinWidth(newBounds.getWidth());
        content.setMinHeight(newBounds.getHeight());
     });
     ScrollPane scrollPane = new ScrollPane(content);
     scrollPane.setPannable(true);
     scrollPane.setFitToHeight(true);
     scrollPane.setFitToWidth(true);
     scrollPane.setPrefSize(500, 500);

我对 ScrollPane 使用了以下 Whosebug 答案:

这是一个可复制的示例,显示了所有内容:

public class TestAnimationInScroll extends Application {

  public static void main(String[] args) {
    launch(args);
  }
  public void start(Stage primaryStage) {
     Rectangle rect = new Rectangle(100, 100, 50, 50);
     rect.setFill(Color.BLUE);
     Group root = new Group();
     root.getChildren().add(rect);

     VBox vBox = new VBox(root);
     vBox.setAlignment(Pos.CENTER);
     Group group = new Group(root);
     StackPane content = new StackPane(group);
     group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
        content.setMinWidth(newBounds.getWidth());
        content.setMinHeight(newBounds.getHeight());
     });
     ScrollPane scrollPane = new ScrollPane(content);
     scrollPane.setPannable(true);
     scrollPane.setFitToHeight(true);
     scrollPane.setFitToWidth(true);
     scrollPane.setPrefSize(500, 500);

     Timeline timeline = new Timeline();
     KeyFrame kf0 = new KeyFrame(Duration.ZERO, new KeyValue(rect.xProperty(), 0));
     KeyFrame kf1 = new KeyFrame(Duration.seconds(10), new KeyValue(rect.xProperty(), 100));
     timeline.getKeyFrames().addAll(kf0, kf1);
     timeline.play();

     content.setOnScroll(new EventHandler<ScrollEvent>() {
        public void handle(ScrollEvent event) {
           double zoomFactor = 1.05;
           double deltaY = event.getDeltaY();
           if (deltaY < 0) {
              zoomFactor = 1 / zoomFactor;
           }
           Bounds groupBounds = group.getBoundsInLocal();
           final Bounds viewportBounds = scrollPane.getViewportBounds();

           double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
           double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());
           group.setScaleX(group.getScaleX() * zoomFactor);
           group.setScaleY(group.getScaleY() * zoomFactor);

           Point2D posInZoomTarget = group.parentToLocal(new Point2D(event.getX(), event.getY()));
           Point2D adjustment = group.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
           scrollPane.layout();
           scrollPane.setViewportBounds(groupBounds);

           groupBounds = group.getBoundsInLocal();
           scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
           scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
        }
     });

     Scene scene = new Scene(scrollPane, 800, 600);
     primaryStage.setScene(scene);
     primaryStage.show();
  }
}

可以看到,ScrollPane可以放大,但是看不到动画的效果

如果我在同一上下文中使用 ScaleTransition,它会起作用,例如:

public class TestAnimationInScroll extends Application {
  public static void main(String[] args) {
    launch(args);
  }
  public void start(Stage primaryStage) {
     Rectangle rect = new Rectangle(100, 100, 50, 50);
     rect.setFill(Color.BLUE);
     Group root = new Group();
     root.getChildren().add(rect);

     VBox vBox = new VBox(root);
     vBox.setAlignment(Pos.CENTER);
     Group group = new Group(root);
     StackPane content = new StackPane(group);
     group.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
        content.setMinWidth(newBounds.getWidth());
        content.setMinHeight(newBounds.getHeight());
     });
     ScrollPane scrollPane = new ScrollPane(content);
     scrollPane.setPannable(true);
     scrollPane.setFitToHeight(true);
     scrollPane.setFitToWidth(true);
     scrollPane.setPrefSize(500, 500);

     ScaleTransition transition = new ScaleTransition(Duration.seconds(5), rect);
     transition.setFromX(1d);
     transition.setFromY(1d);
     transition.setToX(3d);
     transition.setToY(3d);
     transition.play();

     content.setOnScroll(new EventHandler<ScrollEvent>() {
        public void handle(ScrollEvent event) {
           double zoomFactor = 1.05;
           double deltaY = event.getDeltaY();
           if (deltaY < 0) {
              zoomFactor = 1 / zoomFactor;
           }
           Bounds groupBounds = group.getBoundsInLocal();
           final Bounds viewportBounds = scrollPane.getViewportBounds();

           double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
           double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());
           group.setScaleX(group.getScaleX() * zoomFactor);
           group.setScaleY(group.getScaleY() * zoomFactor);

           Point2D posInZoomTarget = group.parentToLocal(new Point2D(event.getX(), event.getY()));
           Point2D adjustment = group.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
           scrollPane.layout();
           scrollPane.setViewportBounds(groupBounds);

           groupBounds = group.getBoundsInLocal();
           scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
           scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
        }
     });

     Scene scene = new Scene(scrollPane, 800, 600);
     primaryStage.setScene(scene);
     primaryStage.show();
  }
}

有人知道我的错误在哪里吗?也许有一种方法仍然能够在 scrollPane 中平移和缩放,但仍能正确看到动画?

我理解了这个问题:实际上是因为 StackPaneGroup 都会在子位置或翻译发生变化时自动更新它们的布局。解决方案是不允许这样做。

对于 Group 来说很简单,因为它有一个 setAutoSizeChildren(boolean) 方法。

但是我不得不创建一个 StackPane 的子类来做同样的事情:

private class MyStackPane extends StackPane {
  private boolean allowLayoutChildren = true;

  private MyStackPane(Node root) {
     super(root);
  }

  private void allowLayoutChildren(boolean allow) {
     this.allowLayoutChildren = allow;
  }

  @Override
  public void layoutChildren() {
     if (allowLayoutChildren) {
        super.layoutChildren();
     }
  }
}

那么完整的(工作)代码是:

public class TestAnimationInScroll extends Application {
   private Group root = null;

   public static void main(String[] args) {
     launch(args);
   }

   @Override
   public void start(Stage primaryStage) {
      Rectangle rect = new Rectangle(100, 100, 50, 50);
      rect.setFill(Color.BLUE);
      root = new Group();
      root.getChildren().add(rect);

      MyStackPane content = new MyStackPane(root);
      root.layoutBoundsProperty().addListener((observable, oldBounds, newBounds) -> {
         content.setMinWidth(newBounds.getWidth());
         content.setMinHeight(newBounds.getHeight());
      });
      ScrollPane scrollPane = new ScrollPane(content);
      scrollPane.setPannable(true);
      scrollPane.setFitToHeight(true);
      scrollPane.setFitToWidth(true);
      scrollPane.setPrefSize(500, 500);
      root.setAutoSizeChildren(false);
      content.allowLayoutChildren(false);

      Timeline timeline = new Timeline();
      KeyFrame kf0 = new KeyFrame(Duration.ZERO, new KeyValue(rect.xProperty(), 0));
      KeyFrame kf1 = new KeyFrame(Duration.seconds(10), new KeyValue(rect.xProperty(), 100));
      timeline.getKeyFrames().addAll(kf0, kf1);
      timeline.play();

      content.setOnScroll(new EventHandler<ScrollEvent>() {
         @Override
         public void handle(ScrollEvent event) {
            double zoomFactor = 1.05;
            double deltaY = event.getDeltaY();
            if (deltaY < 0) {
               zoomFactor = 1 / zoomFactor;
            }
            Bounds groupBounds = root.getBoundsInLocal();
            final Bounds viewportBounds = scrollPane.getViewportBounds();

            double valX = scrollPane.getHvalue() * (groupBounds.getWidth() - viewportBounds.getWidth());
            double valY = scrollPane.getVvalue() * (groupBounds.getHeight() - viewportBounds.getHeight());
            root.setScaleX(root.getScaleX() * zoomFactor);
            root.setScaleY(root.getScaleY() * zoomFactor);

            Point2D posInZoomTarget = root.parentToLocal(new Point2D(event.getX(), event.getY()));
            Point2D adjustment = root.getLocalToParentTransform().deltaTransform(posInZoomTarget.multiply(zoomFactor - 1));
            scrollPane.layout();
            scrollPane.setViewportBounds(groupBounds);

            groupBounds = root.getBoundsInLocal();
            scrollPane.setHvalue((valX + adjustment.getX()) / (groupBounds.getWidth() - viewportBounds.getWidth()));
            scrollPane.setVvalue((valY + adjustment.getY()) / (groupBounds.getHeight() - viewportBounds.getHeight()));
         }
      });

      Scene scene = new Scene(scrollPane, 800, 600);
      primaryStage.setScene(scene);

      primaryStage.show();
   }

   private class MyStackPane extends StackPane {
      private boolean allowLayoutChildren = true;

      private MyStackPane(Node root) {
         super(root);
      }

      private void allowLayoutChildren(boolean allow) {
         this.allowLayoutChildren = allow;
      }

      @Override
      public void layoutChildren() {
         if (allowLayoutChildren) {
            super.layoutChildren();
         }
      }

   }

  }