JavaFX - 顺序转换 - 来回播放(一步一步)

时间:2014-07-24 15:43:00

标签: java animation javafx binary-search-tree transitions

我正在努力制作动画,在JavaFX中显示二进制搜索树中的搜索。 目标是将树节点的值与可能性的比较进行可视化:

暂停并随时播放 能够向后播放动画(至少退一步), 让用户能够一步一步地或整体地播放动画。

The preview of visualization

我的愿景是在一个SequentialTransition( ST )中添加一系列TranslateTransitions( TT )。如果动画被标记为“逐步”,则每个TT在其OnFinished处理程序中暂停整个ST。然而,这种方法仅适用于单向行走。

我的问题是。 在反方向上保持流畅和逐步动画的最佳方法是什么?

我在考虑:

  • 可能会产生另一个逆转换序列(但是如何判断 它从哪一步继续?)
  • 以某种方式使用费率属性? ST运行时可以改变它吗?

非常感谢您的回答。

1 个答案:

答案 0 :(得分:1)

通常,您可以在Animation正在进行时更改其速率属性。使用SequentialTransition的想法很吸引人,但它并不像您想象的那样容易。当在两个单独转换之间的边界处暂停顺序转换时出现问题:您没有任何方法可以判断哪个转换被认为是当前转换(即下一转换或前一转换)。因此,当你试图改变速度并进行游戏时,顺序转换可能会混淆,并立即认为它是在它试图播放的结束时。

在开始播放任何一个步骤之前,您可以通过使用Animation.getCurrentTime()Animation.jumpTo(...)向正确的方向“微调”顺序转换来稍微破解这一点,但我认为这是可能更容易管理自己的个别过渡,而不是使用SequentialTransition

以下是使用此技术移动矩形的简单示例:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import javafx.animation.Animation;
import javafx.animation.Animation.Status;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class ReverseSequentialTransitionTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();
        Rectangle rect = new Rectangle(50, 50, 250, 150);
        rect.setFill(Color.color(.5, .5, .1));
        pane.getChildren().add(rect);

        TranslateTransition ttForward = new TranslateTransition(Duration.seconds(1), rect);
        ttForward.setFromX(0);
        ttForward.setToX(400);
        TranslateTransition ttDown = new TranslateTransition(Duration.seconds(1), rect);
        ttDown.setFromY(0);
        ttDown.setToY(100);
        TranslateTransition ttBackward = new TranslateTransition(Duration.seconds(1), rect);
        ttBackward.setFromX(400);
        ttBackward.setToX(0);
        TranslateTransition ttUp = new TranslateTransition(Duration.seconds(1), rect);
        ttUp.setFromY(100);
        ttUp.setToY(0);

        List<Animation> transitions = Arrays.asList(ttForward, ttDown, ttBackward, ttUp);
        IntegerProperty nextTransitionIndex = new SimpleIntegerProperty();

        Button playButton = new Button("Play Forward");
        playButton.setOnAction(event -> {
            int index = nextTransitionIndex.get();
            Animation anim = transitions.get(index);
            anim.setOnFinished(evt -> nextTransitionIndex.set(index+1));
            anim.setRate(1);
            anim.play();
        });

        Button reverseButton = new Button("Play backward");
        reverseButton.setOnAction(event -> {
            int index = nextTransitionIndex.get()-1;
            Animation anim = transitions.get(index);
            anim.setOnFinished(evt -> nextTransitionIndex.set(index));
            anim.setRate(-1);
            anim.play();
        });

        // This is not really part of the answer to the current question, but the
        // next three statements just disable the buttons when appropriate.

        // This is a binding which is true if and only if any of the transitions are
        // currently running:

        BooleanBinding anyPlaying = createAnyPlayingBinding(transitions);

        // Disable playButton if we are at the end of the last transition, or if
        // any transitions are playing:

        playButton.disableProperty().bind(
                nextTransitionIndex.greaterThanOrEqualTo(transitions.size())
                .or(anyPlaying)
        );

        // Disable reverseButton if we are at the beginning of the first transition,
        // or if any transitions are currently playing:

        reverseButton.disableProperty().bind(
                nextTransitionIndex.lessThanOrEqualTo(0)
                .or(anyPlaying));

        HBox controls = new HBox(5);
        controls.setAlignment(Pos.CENTER);
        controls.getChildren().addAll(playButton, reverseButton);


        BorderPane root = new BorderPane();
        root.setCenter(pane);
        root.setBottom(controls);

        primaryStage.setScene(new Scene(root, 800, 400));
        primaryStage.show();
    }

    private BooleanBinding createAnyPlayingBinding(List<Animation> transitions) {
        return new BooleanBinding() {
            { // Anonymous constructor
                // bind to the status properties of all the transitions
                // (i.e. mark this binding as invalid if any of the status properties change)
                transitions.stream()
                    .map(Animation::statusProperty)
                    .forEach(this::bind);
            }
            @Override
            protected boolean computeValue() {
                // return true if any of the transitions statuses are equal to RUNNING:
                return transitions.stream()
                    .anyMatch(anim -> anim.getStatus()==Status.RUNNING);
            }
        };

    }

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

在JDK 7中,playButton的事件处理程序如下所示:

playButton.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        final int index = nextTransitionIndex.get();
        Animation anim = transitions.get(index);
        anim.setOnFinished(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent evt) {
                nextTransitionIndex.set(index + 1) ;
            }
        });
        anim.setRate(1);
        anim.play();
    }
});

,同样适用于reverseButton。您还需要声明一些事情finalcreateAnyPlayingBinding方法类似于

private BooleanBinding createAnyPlayingBinding(final List<Animation> transitions) {
    return new BooleanBinding() {
        { 
            for (Animation transition : transitions) {
                this.bind(transition.statusProperty();
            }
        }
        @Override
        protected boolean computeValue() {
            // return true if any of the transitions statuses are equal to RUNNING:
            for (Animation anim : transitions) {
                if (anim.getStatus() == Status.RUNNING) {
                    return true ;
                }
            }
            return false ;
        }
    };

}