如何使方法调用等待动画完成

Ern*_*P W 1 java animation javafx

我想制作一个带有动画文本的通知窗口。单击按钮即可发送通知,并且动画将开始播放。我的问题是,当我在上一个动画完成之前再次单击按钮时,会同时执行两个动画。如何使“sendMessage()”的每个方法调用等待另一个方法完成?如果它有任何意义的话,我的程序中有多个节点调用 sendMessage() 方法,这与我的 MRE 不同,所以我想要某种带有消息的队列。这是我的 MRE:

public class AnimationTest extends Application {

  private final Label messageLabel = new Label();

  @Override
  public void start(Stage stage) throws IOException {

    VBox vBox = new VBox();
    vBox.setAlignment(Pos.CENTER);
    Scene scene = new Scene(vBox, 320, 240);
    vBox.getChildren().add(messageLabel);
    Button button = new Button();
    button.setOnAction(event -> sendMessage("Some animated text."));
    vBox.getChildren().add(button);
    stage.setScene(scene);
    stage.show();
  }

  private void sendMessage(String message) {
    final IntegerProperty i = new SimpleIntegerProperty(0);
    Timeline timeline = new Timeline();
    KeyFrame keyFrame = new KeyFrame(
        Duration.millis(40),
        event -> {
          if (i.get() > message.length()) {
            timeline.stop();
          } else {
            messageLabel.setText(message.substring(0, i.get()));
            i.set(i.get() + 1);
          }
        });
    timeline.getKeyFrames().add(keyFrame);
    timeline.setCycleCount(Animation.INDEFINITE);
    timeline.play();
  }

  public static void main(String[] args) {
    launch();
  }
} 
Run Code Online (Sandbox Code Playgroud)

Jam*_*s_D 6

对于您发布的具体示例,最简单的方法是在开始动画之前立即禁用按钮,并在动画停止时再次启用它。这是执行此操作的一种方法:

public class AnimationTest extends Application {

    private final Label messageLabel = new Label();

    @Override
    public void start(Stage stage) {

        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER);
        Scene scene = new Scene(vBox, 320, 240);
        vBox.getChildren().add(messageLabel);
        Button button = new Button();
        button.setOnAction(event -> {
            Animation animation = sendMessage("Some animated text.");
            button.disableProperty().bind(Bindings.equal(animation.statusProperty(), Animation.Status.RUNNING));
        });
        vBox.getChildren().add(button);
        stage.setScene(scene);
        stage.show();
    }

    private Animation sendMessage(String message) {
        final IntegerProperty i = new SimpleIntegerProperty(0);
        Timeline timeline = new Timeline();
        KeyFrame keyFrame = new KeyFrame(
                Duration.millis(40),
                event -> {
                    if (i.get() > message.length()) {
                        timeline.stop();
                    } else {
                        messageLabel.setText(message.substring(0, i.get()));
                        i.set(i.get() + 1);
                    }
                });
        timeline.getKeyFrames().add(keyFrame);
        timeline.setCycleCount(Animation.INDEFINITE);
        timeline.play();
        return timeline ;
    }

    public static void main(String[] args) {
        launch();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您希望允许这些消息累积在队列中,并在旧动画完成时启动新动画,则需要保留消息队列以及对当前正在运行的动画的引用(如果有)。如果当前没有正在运行的动画,您可以从队列中轮询AnimationTimer并在出现新消息时启动新动画。

我建议您考虑一下这是否是您想要采取的方法;这里不能保证您的消息的显示速度不会快于动画的速度,在这种情况下,队列将无限期地增长。但是,如果您可以以其他方式确保情况并非如此,那么这就是一个实现:

public class AnimationTest extends Application {

    private final Label messageLabel = new Label();
    
    private final Queue<String> messages = new LinkedList<>();
    private Animation currentAnimation = null ;

    @Override
    public void start(Stage stage) {

        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER);
        Scene scene = new Scene(vBox, 320, 240);
        vBox.getChildren().add(messageLabel);
        Button button = new Button();
        button.setOnAction(event -> messages.add("Some animated text."));

        AnimationTimer timer = new AnimationTimer() {
            @Override
            public void handle(long l) {
                if (currentAnimation == null || currentAnimation.getStatus() == Animation.Status.STOPPED) {
                    String message = messages.poll();
                    if (message != null) {
                        currentAnimation = sendMessage(message);
                        currentAnimation.play();
                    }
                }
            }
        };
        timer.start();
        vBox.getChildren().add(button);
        stage.setScene(scene);
        stage.show();
    }

    private Animation sendMessage(String message) {
        final IntegerProperty i = new SimpleIntegerProperty(0);
        Timeline timeline = new Timeline();
        KeyFrame keyFrame = new KeyFrame(
                Duration.millis(40),
                event -> {
                    if (i.get() > message.length()) {
                        timeline.stop();
                    } else {
                        messageLabel.setText(message.substring(0, i.get()));
                        i.set(i.get() + 1);
                    }
                });
        timeline.getKeyFrames().add(keyFrame);
        timeline.setCycleCount(Animation.INDEFINITE);
        return timeline ;
    }

    public static void main(String[] args) {
        launch();
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,这里没有考虑线程。该handle()方法在 FX 应用程序线程上调用,因此唯一的要求是将消息放置在同一线程的队列中。在此示例中会发生这种情况,因为按钮的事件处理程序是在该线程上调用的。如果您的消息来自后台线程,您应该确保将它们添加到 FX 应用程序线程上的队列中,方法是使用或 (最好)使用 JavaFX 并发 API(即通过检索orPlatform.runLater(...)中的消息并将它们添加到到处理程序中的队列)。TaskServiceonSucceeded