【问题标题】:Why is there a big performance difference between transition on Alert and Stage?为什么 Alert 和 Stage 上的转换之间存在很大的性能差异?
【发布时间】:2016-07-11 20:07:58
【问题描述】:

我正在尝试在我的项目中实现一些动画。当用户使用该应用程序时,有时他或她会收到确认/否对话框 (Alert) 或对话框 (Stage) 输入数据(并按下保存按钮)。活动结束后,通常我会显示另一个带有“成功”的Alert(如果成功的话)。

现在,为了消除一堆额外的“无用”窗口/屏幕/弹出窗口,我想将AlertStage 最小化到屏幕的左下角,那里会出现“成功”消息在状态栏中大约 3 秒。我已经成功实现了这一点,但我注意到Alert 上的动画和Stage 上的动画之间存在巨大的性能差异。

Alert 的动画看起来非常流畅,而Stage 实际上非常不稳定(即使在一台好的电脑上)。我已阅读有关缓存和搜索相关问题的信息,但没有太多效果或解决方案。我尝试制作 JavaFX (Maven) 示例(基于我发现的其他一些示例),您可以在下面找到它。

您会看到,当您在Alert 窗口中按显示警报 按钮,然后按 时,Alert 将顺利转到左下方屏幕一角。 当你按下 Show node 按钮,然后在新打开的阶段按下 Close 按钮时,动画比Alert 更不稳定。

我可以做些什么来平滑舞台的动画?我也试过把最上面的AnchorPane不可见,看看有没有性能提升,但是完全一样。

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);
    }
}

【问题讨论】:

    标签: java javafx-8


    【解决方案1】:

    唯一的区别是keyFrameOpacity 动画在舞台的情况下。如果删除它,舞台动画将与警报对话框一样流畅。不过有趣的是,动画只有在使用不透明度变化和缩放时才会滞后。在timeline.play() 之前设置stage.setScene(null) 也可以使动画流畅,但看起来不是很好。
    我对 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();
    

    【讨论】:

    • 谢谢!奇怪了fading的表现这么差。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-31
    • 2013-12-12
    • 2015-02-12
    • 2012-03-29
    • 2013-05-21
    • 1970-01-01
    • 2018-11-13
    相关资源
    最近更新 更多