JavaFX - 使用 FillTransition 和 TranslateTransition 放错了对象

JavaFX - using FillTransition and TranslateTransition misplaces the object

所以我有几个 Rectangle 个不同大小的对象,分别命名为 r1r2,它们最初出现在屏幕的正中央。它们有不同的颜色(比如一个是红色的,另一个是蓝色的),较小的放在较大的上面,所以即使它们堆叠在中间,两者仍然可以区分。我希望他们四处移动 return 到他们原来的位置,因此使用 TranslateTransition 如下:

tt1 = new TranslateTransition(Duration.millis(20000), button1);
tt1.setByX(100);   // moves 100 pixels to the right
tt1.setByY(-100);   // moves 100 pixels down
tt1.setCycleCount(20);   // oscillates 20 times
tt1.setAutoReverse(true);

tt2 = new TranslateTransition(Duration.millis(20000), button2);
tt2.setByX(-100);   // moves 100 pixels to the left
tt2.setByY(100);   // moves 100 pixels up
tt2.setCycleCount(20);   // oscillates 20 times
tt2.setAutoReverse(true);

然而,在他们移动的过程中,如果 r1r2 中的任何一个被 MouseEvent 按下,我希望他们消失几秒钟(比如 5 秒)并再次活着回来。使用背景颜色是完全黑色的事实,我使用 FillTransition 来实现该效果:

FillTransition ft1 = new FillTransition(Duration.millis(5000), r1, Color.BLACK, Color.BLACK);
FillTransition ft2 = new FillTransition(Duration.millis(5000), r2, Color.BLACK, Color.BLACK);

通过将Rectangles从Color.BLACK转换为Color.BLACK 5秒,它给出了按钮消失5秒的效果。此外,我在 r1r2 上有以下 setOnMouseClicked,因此它们可以在用户输入时消失:

r1.setOnMouseClicked((MouseEvent t) -> {
    ft1.play();
});
r2.setOnMouseClicked((MouseEvent t) -> {
    ft2.play();
});

两个物体消失 5 秒后,它们必须重新出现在中心,就像开始时一样,并使用 tt1tt2 重复相同的摆动动作,我在 ft1ft2:

上实现了 setOnFinished
ft1.setOnFinished((ActionEvent event) -> {
    r1.setFill(color1);   // restore the original color
    tt1.play();
});
ft2.setOnFinished((ActionEvent event) -> {
    r2.setFill(color2);   // restore the original color
    tt2.play();
});

然而问题是,当r1r2再次出现时,它们的位置不是在中心,而是在它们最后消失的位置——换句话说,位置他们重生的起点是上次 TranslateTransition 检测到用户的 MouseEvent 时他们所在的位置。我试过用r1.setX(centerX)r1.setY(centerY)来修改,其中centerXcenterY是一开始使用的原始中心坐标,但无法解决问题。事实上,当我使用 r1.getX() 时,returned 值等于原始 centerX 值,即使很明显 r1 没有放在中间。这让我怀疑 TranslateTransition 在不改变实际 getX() 值的情况下履行其职责。我还想过在TranslateTransitionFillTransition上以某种方式使用ParallelTransition,所以tt1可以在ft1生效时完成,但从那以后ft1tt1 已经 运行 一段时间后,将开始 运行,它不会提供可行的解决方案。

所以我的问题是,如果一个对象的 TranslateTransition 在中间被打断了,我该如何恢复对象的 "original" 坐标,而不是 [=17= 时对象最后停止的位置]被打断了?

p.s。我想避免在每次检测到 MouseEvent 时创建新的 Rectangle 对象,因为这意味着所有 TranslateTransitionFillTransition 链接到 r1r2 也必须重新创建。

示例解决方案

运行程序,矩形开始移动。单击矩形,它会立即消失。在它消失后不久,矩形会重新出现在它原来的起始位置,并开始沿着它原来的轨迹移动。

import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Pauser extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        final Rectangle r1 = new Rectangle(
                50, 150, 30, 30
        );

        final TranslateTransition tt1 = new TranslateTransition(
                Duration.seconds(5),
                r1
        );
        tt1.setFromX(0);    // start at the layout origin for the node
        tt1.setFromY(0);    //
        tt1.setByX(100);    // moves 100 pixels to the right
        tt1.setByY(-100);   // moves 100 pixels down
        tt1.setCycleCount(TranslateTransition.INDEFINITE);
        tt1.setAutoReverse(true);
        tt1.play();

        final PauseTransition pt1 = new PauseTransition(
                Duration.seconds(1)
        );
        pt1.setOnFinished(event -> {
            tt1.playFromStart();
            r1.setVisible(true);
        });

        r1.setOnMouseClicked(event -> {
            r1.setVisible(false);
            tt1.stop();
            r1.setTranslateX(0);
            r1.setTranslateY(0);
            pt1.play();
        });

        stage.setScene(
                new Scene(
                        new Group(r1),
                        200, 200
                )
        );
        stage.show();
    }

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

这只解决了一个矩形的问题。您可以将解决方案放在一个循环中以处理多个矩形。

观察结果

  1. A PauseTransition is used rather than a FillTransition 并且节点的可见性设置为 false 而暂停是 运行ning。使用 FillTransition,用户仍然可以单击节点,即使无法从背景中明显地区分填充有背景色的节点。所以 FillTransition 可能是不可取的。
  2. fromX/fromY 属性是为转换转换设置的,否则如果你停止并从头开始 运行 它只会使用节点的当前 translateX/translateY 值而不是原点0/0 的值就是你想要的。
  3. 虽然节点不可见,但无需保持 运行 TranslateTransition,因此在此期间停止转换。
  4. 当平移转换停止时,它会将 translateX/translateY 坐标留在当前为最后一个动画帧设置的位置。因此添加了将 translateX/translateY 设置为 0/0 的手动调用。
  5. 暂停过渡完成后,请求从头开始播放翻译过渡,而不是之前暂停或停止的地方。

了解转换

节点在屏幕上的位置基于应用于其布局位置的变换。布局位置在节点的 layoutX/layoutY 属性中维护。但是,如果您对节点应用平移转换(正如您在 TranslateTransition 中隐式执行的那样),则节点的屏幕位置将为 layoutX+translateX / layoutY+translateY

背景研究

  1. 阅读 Node documentation,尤其是关于变换和边界矩形的部分。
  2. 试试这个 layout bounds demonstration 来帮助理解这些概念。

So the key was using setFromX and setFromY to start from the original coordinate.

手动设置 translateX/translateY 值为 0/0 也是关键。如果不这样做,节点将在开始从原点位置移动之前在其最后转换的位置闪烁。我认为这是因为从您请求动画开始到它实际开始并使用 0/0 的 fromX/fromY 坐标之间存在帧延迟。这是一种奇怪的行为。

这对我来说很好用。希望它是个好方法。可能有新的 class 来创建 extends rectangle,其中包含事件和转换。这只是两个 rectangles 的样本。

import javafx.animation.FadeTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {

        Rectangle rect1 = new Rectangle (100, 40, 120, 120);
        rect1.setArcHeight(42);
        rect1.setArcWidth(42);
        rect1.setFill(Color.AQUA);

        TranslateTransition tt1; 
        tt1 = new TranslateTransition(Duration.millis(3000), rect1);
        tt1.setByX(200);
        tt1.setByY(-200);
        tt1.setCycleCount(2);
        tt1.setAutoReverse(true);
        tt1.play();

        rect1.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent event) {
                FadeTransition ft = new FadeTransition(Duration.millis(1000), rect1);
                ft.setFromValue(1.0);
                ft.setToValue(0.0);
                ft.play();
                event.consume();
            }
        });

        tt1.setOnFinished(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                tt1.play();
                FadeTransition ft = new FadeTransition(Duration.millis(1000), rect1);
                ft.setToValue(1.0);
                ft.play();
            }
        });

        Rectangle rect2 = new Rectangle (100, 40, 80, 80);
        rect2.setArcHeight(42);
        rect2.setArcWidth(42);
        rect2.setFill(Color.YELLOWGREEN);

        TranslateTransition tt2; 
        tt2= new TranslateTransition(Duration.millis(6000), rect2);
        tt2.setByX(-200);
        tt2.setByY(200);
        tt2.setCycleCount(2);
        tt2.setAutoReverse(true);
        tt2.play();

        rect2.setOnMousePressed(new EventHandler<MouseEvent>() {
            @Override public void handle(MouseEvent event) {
                FadeTransition ft = new FadeTransition(Duration.millis(1000), rect2);
                ft.setFromValue(1.0);
                ft.setToValue(0.0);
                ft.play();
                event.consume();
            }
        });

        tt2.setOnFinished(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                tt2.play();
                FadeTransition ft = new FadeTransition(Duration.millis(1000), rect2);
                ft.setToValue(1.0);
                ft.play();
            }
        });

        StackPane pane = new StackPane();
        pane.setAlignment(Pos.CENTER);
        pane.getChildren().addAll(rect1,rect2);
        Scene scene = new Scene(pane, 600, 600);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

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

}