我正在努力制作动画,在JavaFX中显示二进制搜索树中的搜索。 目标是将树节点的值与可能性的比较进行可视化:
暂停并随时播放 能够向后播放动画(至少退一步), 让用户能够一步一步地或整体地播放动画。
我的愿景是在一个SequentialTransition( ST )中添加一系列TranslateTransitions( TT )。如果动画被标记为“逐步”,则每个TT在其OnFinished处理程序中暂停整个ST。然而,这种方法仅适用于单向行走。
我的问题是。 在反方向上保持流畅和逐步动画的最佳方法是什么?
我在考虑:
非常感谢您的回答。
答案 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
。您还需要声明一些事情final
。 createAnyPlayingBinding
方法类似于
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 ;
}
};
}