Анимируйте размер окна, когда вы хотите создать иконку приложения, и прослушайте свойство iconified
, чтобы выполнить обратную анимацию при восстановлении Stage
:
@Override
public void start(Stage primaryStage) {
StageHideAnimator.create(primaryStage);
Button minimize = new Button("minimize");
minimize.setOnAction(evt -> {
StageHideAnimator animator = StageHideAnimator.getStageHideAnimator((Node) evt.getSource());
animator.iconify();
});
Button close = new Button("close");
close.setOnAction(evt -> primaryStage.close());
VBox content = new VBox(minimize, close, new Rectangle(200, 200, Color.BLUE));
content.setPadding(new Insets(10));
content.setStyle("-fx-background-color: green;");
primaryStage.initStyle(StageStyle.TRANSPARENT);
Scene scene = new Scene(content);
primaryStage.setScene(scene);
primaryStage.setOnShown(evt -> {
WindowUtils.placeAtPrimaryScreenBottom(primaryStage);
});
primaryStage.show();
}
public final class WindowUtils {
private WindowUtils() { }
public static void placeAtPrimaryScreenBottom(Stage stage) {
stage.setY(Screen.getPrimary().getVisualBounds().getMaxY() - stage.getHeight());
}
}
public class StageHideAnimator {
// key used for storing animators in the properties map of a Stage
private static final Object PROPERTY_KEY = new Object();
private double sceneHeight;
private double decorationHeight;
private final Stage stage;
private Timeline animation;
// fraction of height relative to full height
private final DoubleProperty height = new SimpleDoubleProperty();
// getter for the animator
public static StageHideAnimator getStageHideAnimator(Stage stage) {
return (StageHideAnimator) stage.getProperties().get(PROPERTY_KEY);
}
// get animator of window containing the node
public static StageHideAnimator getStageHideAnimator(Node node) {
return getStageHideAnimator((Stage) node.getScene().getWindow());
}
private StageHideAnimator(Stage stage) {
this.stage = stage;
stage.iconifiedProperty().addListener((o, oldValue, newValue) -> {
// do reverse hide animation when stage is shown
if (!newValue) {
animation.setRate(-1);
if (animation.getStatus() == Animation.Status.STOPPED) {
animation.playFrom("end");
} else {
animation.play();
}
}
});
height.addListener((o, oldValue, newValue) -> {
// resize stage and put it at the bottom of the primary screen
stage.setHeight(sceneHeight * newValue.doubleValue() + decorationHeight);
WindowUtils.placeAtPrimaryScreenBottom(stage);
});
}
public static StageHideAnimator create(Stage stage) {
if (stage.getProperties().containsKey(PROPERTY_KEY)) {
// don't allow 2 animators
throw new IllegalArgumentException("animator already exists");
}
StageHideAnimator animator = new StageHideAnimator(stage);
stage.getProperties().put(PROPERTY_KEY, animator);
return animator;
}
private void initHeight() {
sceneHeight = stage.getScene().getHeight();
decorationHeight = stage.getHeight() - sceneHeight;
}
public void iconify() {
if (stage.isIconified()) {
return;
}
if (animation == null) {
initHeight(); // save initial height of stage
animation = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(height, 1d, Interpolator.EASE_BOTH)),
new KeyFrame(Duration.seconds(1), new KeyValue(height, 0d, Interpolator.EASE_BOTH)));
animation.setOnFinished(evt -> {
if (animation.getRate() == 1) {
// iconify at end of hiding animation
animation.setRate(-1);
stage.setIconified(true);
}
});
animation.play();
} else {
animation.setRate(1);
if (animation.getStatus() == Animation.Status.STOPPED) {
initHeight(); // save initial height of stage
animation.playFromStart();
} else {
animation.play();
}
}
}
}