简体   繁体   English

JavaFX animation 不移动节点

[英]JavaFX animation not moving the Node

I am currently developing a small JavaFX library to show svg content.我目前正在开发一个小型 JavaFX 库来显示 svg 内容。 I am in the process of implementing animations.我正在实现动画。 They work correctly, except for animate or animateTransform for x and y positions, and I don't understand why.它们工作正常,除了 x 和 y 位置的animateanimateTransform ,我不明白为什么。

When I am doing this, it works flawlessly:当我这样做时,它完美无缺:

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();
  }

} }

But when I am doing what I think is exactly the same thing in my library, the rectangle seems to twitch a little but not really move.但是当我在我的库中做我认为完全一样的事情时,矩形似乎有点抽搐但并没有真正移动。

My code is:我的代码是:

  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);

Strangely when I'm doing the same thing with the width or height property, it works without any problem.奇怪的是,当我对 width 或 height 属性做同样的事情时,它没有任何问题。

I suspect that my scene is not created correctly (I checked that I am doing all of this in the platform Thread), but everything else is working without any problem.我怀疑我的场景没有正确创建(我检查了我是否在平台线程中执行所有这些操作),但其他一切都正常工作。

If I am trying to use a TranslateTransition instead, I have exactly the same problem.如果我尝试改用TranslateTransition ,我会遇到完全相同的问题。

After some comments on this question here, I now undestand why I have this problem (but not how to fix it, at least for now).在这里对这个问题发表了一些评论后,我现在不明白为什么我会遇到这个问题(但不知道如何解决它,至少现在是这样)。 I put the JavaFX content in a ScrollPane.我将 JavaFX 内容放在 ScrollPane 中。 I did not think that it would be relevant in this case, but it is.我不认为它在这种情况下是相关的,但它是相关的。 The code is:代码是:

     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);

I used the following StackOverflow answer for the ScrollPane: JAVAFX zoom, scroll in ScrollPane我对 ScrollPane 使用了以下 StackOverflow 答案: JAVAFX zoom, scroll in ScrollPane

Here is a reproductible example showing everything:这是一个显示所有内容的可复制示例:

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();
  }
}

As you can see, it is possible to zoom in the ScrollPane, but the effect of the animation is not visible.可以看到,ScrollPane可以放大,但是animation的效果是看不到的。

If I use a ScaleTransition in the same context, it works, such as:如果我在相同的上下文中使用 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();
  }
}

Do somebody have a clue of where is my mistake?有人知道我的错误在哪里吗? Perhaps there is a way to still be able to pan and zoom in the scrollPane but still seeing correctly the animations?也许有一种方法可以在 scrollPane 中平移和缩放,但仍然可以正确看到动画?

I understand why I have this problem.我明白为什么我有这个问题。 I put the JavaFX content in a ScrollPane.我将 JavaFX 内容放在 ScrollPane 中。 I did not think that it would be relevant in this case, but it is.我不认为它在这种情况下是相关的,但它是相关的。 The code is:代码是:

     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);

I used the following StackOverflow answer for the ScrollPane: JAVAFX zoom, scroll in ScrollPane我对 ScrollPane 使用了以下 StackOverflow 答案: JAVAFX zoom, scroll in ScrollPane

Perhaps there is a way to still be able to pan and zoom in the scrollPane but still seeing correctly the animations?也许有一种方法可以在 scrollPane 中平移和缩放,但仍然可以正确看到动画?

Here is a reproductible example showing everything:这是一个显示所有内容的可复制示例:

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();
  }
}

As you can see, it is possible to zoom in the ScrollPane, but the effect of the animation is not visible.可以看到,ScrollPane可以放大,但是animation的效果是看不到的。

If I use a ScaleTransition in the same context, it works, such as:如果我在相同的上下文中使用 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();
  }
}

I understood the problem: in fact it is because both the StackPane and Group will automatically update their layout when the children position or translation change.我理解这个问题:实际上是因为当子 position 或翻译发生变化时, StackPaneGroup都会自动更新它们的布局。 The solution is to not allow that.解决方案是不允许这样做。

It is simple for the Group because it has a setAutoSizeChildren(boolean) method. Group很简单,因为它有一个setAutoSizeChildren(boolean)方法。

But I had to create a subclass of StackPane to do the same thing:但是我必须创建一个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();
     }
  }
}

Then the full (working) code is:然后完整的(工作)代码是:

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();
         }
      }

   }

  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM