Why is there a big performance difference between transition on Alert and Stage?

时间:2016-07-11 20:07:59

标签: java javafx-8

I'm trying to implement some animation in my project. When the user is using the application, sometimes he or she gets Yes/No dialogs (Alert) for confirmation or dialog boxes (Stage) to input data (and press a save button). After the event, normally I would show another Alert with "Success" (if succesful ofcourse).

Now, to eliminate a bunch of extra "useless" windows/screens/popups, I wanted to minimize the Alert or Stage to the lower left corner of the screen where there will appear a "Success" message for about 3 seconds in a status bar. I have implemented this succesfully but I noticed a huge performance difference between the animation on Alert and the animation on Stage.

Alert seems to animate very smoothly, while the Stage is actually very choppy (even on a good pc). I have read about caching and searched related questions, but without much effect or solutions. I tried to make JavaFX (Maven) example (based on some other examples I found) which you can find below.

You will see, when you press the Show alert button, and press Yes in the Alert window, the Alert will go smoothly to the lower left corner of the screen. When you press the Show node button, and press the Close button in the newly opened stage, the animation goes much more choppy compared to Alert.

Is there anything I can do to smoothen the animation of the stage? I also tried to put the top AnchorPane invisible to see if there was any performance improvement, but it was exactly the same.

Scene.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" prefHeight="200" prefWidth="320" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="proj.mavenproject1.FXMLController">
    <children>
    <Button fx:id="button" layoutX="52.0" layoutY="90.0" onAction="#handleButtonAction" text="Show Alert" />
    <Label fx:id="label" layoutX="126" layoutY="120" minHeight="16" minWidth="69" />
      <Button fx:id="button1" layoutX="217.0" layoutY="90.0" onAction="#handleButtonAction2" text="Show Node" />
    </children>
</AnchorPane>

testNode.fxml:

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>


<AnchorPane id="AnchorPane" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.40" fx:controller="proj.mavenproject1.TestNodeController">
   <children>
      <Button layoutX="262.0" layoutY="188.0" mnemonicParsing="false" onAction="#handleButtonAction" text="Close node" />
   </children>
</AnchorPane>

FXMLController.java:

package proj.mavenproject1;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

public class FXMLController implements Initializable {

    @FXML
    private Label label;

    @FXML
    private void handleButtonAction(ActionEvent event) {
    Utilities.showYesNo("test", "this to test the closing animation of an alert", true);        

    System.out.println("You clicked me!");
    label.setText("Hello World!");
    }

    @FXML
    private void handleButtonAction2(ActionEvent event) {
    try {
        URL url = getClass().getResource("/fxml/testNode.fxml");
        Utilities.showDialog(url);
    } catch (IOException ex) {
        Logger.getLogger(FXMLController.class.getName()).log(Level.SEVERE, null, ex);
    }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
    // TODO
    }
}

TestNodeController.java:

package proj.mavenproject1;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;

public class TestNodeController implements Initializable {

    @FXML
    private void handleButtonAction(ActionEvent event) {
    Utilities.closeStage(event, true);
    }

    /**
     * Initializes the controller class.
     */
    @Override
    public void initialize(URL url, ResourceBundle rb) {
    // TODO
    }    

}

Utilities.java:

package proj.mavenproject1;

import java.io.IOException;
import java.net.URL;
import java.util.Optional;
import java.util.ResourceBundle;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableValue;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.CacheHint;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.DialogEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.VBoxBuilder;
import javafx.stage.Modality;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;
import javafx.util.Duration;

public class Utilities {
    public static boolean showYesNo(String title, String content, boolean animation) {
    Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
    alert.setTitle(title);
    alert.setHeaderText(null);
    alert.setContentText(content);

    alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);

    alert.setOnCloseRequest((DialogEvent we) -> {
        if (animation) {
        minimizeAlert(alert, animation);
        we.consume();
        }
    });

    Optional<ButtonType> result = alert.showAndWait();

    return result.get() == ButtonType.YES;
    }

    public static void showDialog(URL url) throws IOException {
    final Stage myDialog = new Stage();
    myDialog.initStyle(StageStyle.UTILITY);
    myDialog.initModality(Modality.APPLICATION_MODAL);

    Node n = (Node) FXMLLoader.load(url);

    Scene myDialogScene = new Scene(VBoxBuilder.create().children(n).alignment(Pos.CENTER).padding(new Insets(0)).build());

    myDialog.setScene(myDialogScene);

    myDialog.showAndWait();

    }

    private static void minimizeNode(Scene scene, boolean animation) {
    final int MILLIS = 750;

    //Node src = (Node) event.getSource();
    AnchorPane rootPane = (AnchorPane) scene.lookup("#rootPane");
    final Stage stage = (Stage) scene.getWindow();

    //animation = false; //TODO: check if this thing slows down the program, seems like context menu slows down because of it
    if (animation) {
        WritableValue<Double> writableHeight = new WritableValue<Double>() {
        @Override
        public Double getValue() {
            return stage.getHeight();
        }

        @Override
        public void setValue(Double value) {
            stage.setHeight(value);
        }
        };
        WritableValue<Double> writableWidth = new WritableValue<Double>() {
        @Override
        public Double getValue() {
            return stage.getWidth();
        }

        @Override
        public void setValue(Double value) {
            stage.setWidth(value);
        }
        };
        WritableValue<Double> writableOpacity = new WritableValue<Double>() {
        @Override
        public Double getValue() {
            return stage.getOpacity();
        }

        @Override
        public void setValue(Double value) {
            stage.setOpacity(value);
        }
        };

        EventHandler onFinished = new EventHandler<ActionEvent>() {
        public void handle(ActionEvent t) {
            stage.close();
        }
        };

        double currentX = stage.getX();
        double currentY = stage.getY();
        DoubleProperty x = new SimpleDoubleProperty(currentX);
        DoubleProperty y = new SimpleDoubleProperty(currentY);
        x.addListener(new ChangeListener<Number>() {
        @Override
        public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            stage.setX(newValue.doubleValue());
        }
        });
        y.addListener(new ChangeListener<Number>() {
        @Override
        public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            stage.setY(newValue.doubleValue());
        }
        });

        KeyFrame keyFrameMove = new KeyFrame(Duration.millis(MILLIS), onFinished, new KeyValue(x, 0d), new KeyValue(y, Screen.getPrimary().getBounds().getMaxY() - 25));
        KeyFrame keyFrameScale = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableWidth, 0d), new KeyValue(writableHeight, 0d));
        KeyFrame keyFrameOpacity = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableOpacity, 0d));
        Timeline timeline = new Timeline(keyFrameMove, keyFrameScale, keyFrameOpacity);

        if (rootPane != null) {
            rootPane.setVisible(false);
            //rootPane.getChildren().clear();
        }

        timeline.play();
    } else {
        stage.close();
    }
    }

    public static void minimizeAlert(Alert alert, boolean animation) {
    final int MILLIS = 750;

    if (animation) {
        WritableValue<Double> writableHeight = new WritableValue<Double>() {
        @Override
        public Double getValue() {
            return alert.getHeight();
        }

        @Override
        public void setValue(Double value) {
            alert.setHeight(value);
        }
        };
        WritableValue<Double> writableWidth = new WritableValue<Double>() {
        @Override
        public Double getValue() {
            return alert.getWidth();
        }

        @Override
        public void setValue(Double value) {
            alert.setWidth(value);
        }
        };

        EventHandler onFinished = new EventHandler<ActionEvent>() {
        public void handle(ActionEvent t) {
            alert.setOnCloseRequest(null);
            alert.close();
        }
        };

        double currentX = alert.getX();
        double currentY = alert.getY();
        DoubleProperty x = new SimpleDoubleProperty(currentX);
        DoubleProperty y = new SimpleDoubleProperty(currentY);
        x.addListener((obs, oldX, newX) -> alert.setX(newX.doubleValue()));
        y.addListener((obs, oldY, newY) -> alert.setY(newY.doubleValue()));

        KeyFrame keyFrameMove = new KeyFrame(Duration.millis(MILLIS), onFinished, new KeyValue(x, 0d), new KeyValue(y, Screen.getPrimary().getBounds().getMaxY() - 25));
        KeyFrame keyFrameScale = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableWidth, 0d), new KeyValue(writableHeight, 0d));
        Timeline timeline = new Timeline(keyFrameMove, keyFrameScale);

        timeline.play();
    } else {
        alert.close();
    }
    }

    public static void closeStage(Event event, boolean animation) {
    Node src = (Node) event.getSource();
    src.setCache(true);
    src.setCacheHint(CacheHint.SPEED);
    minimizeNode(src.getScene(), animation);
    }
}

1 个答案:

答案 0 :(得分:1)

唯一的区别是舞台时keyFrameOpacity动画。如果将其删除,舞台动画将与警报对话框一样平滑。但有趣的是,只有在使用缩放比例改变不透明度时动画才会出现滞后现象。在stage.setScene(null)之前设置timeline.play()也会使动画变得平滑,但看起来不太好 我对JavaFx时间轴内部及其脉冲机制不太熟悉,但我找到了2个解决方案。一种是在不同阶段处理缩放和不透明度变化:

    double currentWidth = stage.getWidth();
    double currentHeight = stage.getHeight();
    WritableValue<Double> writableValue = new WritableValue<Double>() {
        private Double internal = 1.0;
        private boolean flag = true;
        @Override
        public Double getValue() {
            return internal;
        }

         @Override
         public void setValue(Double value) {
            if(flag) {
                stage.setWidth(currentWidth * value);
                stage.setHeight(currentHeight * value);
            } else {
                stage.setOpacity(value);
            }
            internal = value;
            flag = !flag;
        }
    };

    KeyFrame keyFrameMove = new KeyFrame(Duration.millis(MILLIS), onFinished, new KeyValue(x, 0d), new KeyValue(y, Screen.getPrimary().getBounds().getMaxY() - 25));
    KeyFrame keyFrame = new KeyFrame(Duration.millis(MILLIS), new KeyValue(writableValue, 0d));

    Timeline timeline = new Timeline(keyFrame, keyFrameMove);

    timeline.play();

第二种是使用单独的线程来更新所有值。像这样:

    double currentX = stage.getX();
    double currentY = stage.getY();
    double currentWidth = stage.getWidth();
    double currentHeight = stage.getHeight();
    new Thread(()->{
        long initial = System.currentTimeMillis();
        while(true) {
            long current = System.currentTimeMillis();
            long delta = current - initial;
            if(delta > MILLIS) {
                break;
            }
            double prc = 1 - delta/(double)MILLIS;
            Platform.runLater(()->{
                stage.setX(currentX*prc);
                stage.setY(currentY*prc+(1-prc)*(Screen.getPrimary().getBounds().getMaxY() - 25));
                stage.setWidth(currentWidth*prc);
                stage.setHeight(currentHeight*prc);
                stage.setOpacity(prc);
            });

            try {
                Thread.sleep(1000/60);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
            Platform.runLater(()-> stage.close());
    }).start();