2016-07-11 18 views
6

Sto provando a implementare alcune animazioni nel mio progetto. Quando l'utente utilizza l'applicazione, a volte lui o lei ottiene le finestre di dialogo Sì/No (Alert) per la conferma o le finestre di dialogo (Stage) per immettere i dati (e premere un pulsante di salvataggio). Dopo l'evento, normalmente mostrerei un altro Alert con "Success" (se di successo del corso).Perché c'è una grande differenza di prestazioni tra la transizione su Alert e Stage?

Ora, per eliminare un gruppo di extra "inutili" finestre/schermi/popup, ho voluto ridurre al minimo il Alert o Stage verso l'angolo in basso a sinistra dello schermo dove vi apparirà un messaggio di "successo" per circa 3 secondi in una barra di stato. L'ho implementato con successo, ma ho notato un'enorme differenza di prestazioni tra l'animazione su Alert e l'animazione su Stage.

Alert sembra animare molto agevolmente, mentre lo Stage è in realtà molto mosso (anche su un buon pc). Ho letto sulla cache e ha cercato domande correlate, ma senza molti effetti o soluzioni. Ho provato a creare l'esempio di JavaFX (Maven) (basato su alcuni altri esempi che ho trovato) che puoi trovare di seguito.

Vedrete, quando si preme il pulsante di allarme Visualizza, e premere nella finestra Alert, il Alert andrà liscio verso l'angolo in basso a sinistra dello schermo. Quando si preme il pulsante Mostra nodo e si preme il pulsante Chiudi nella fase appena aperta, l'animazione diventa molto più irregolare rispetto a Alert.

C'è qualcosa che posso fare per attenuare l'animazione del palco? Ho anche provato a mettere in primo piano l'invisibile AnchorPane per vedere se ci fosse un miglioramento delle prestazioni, ma era esattamente lo stesso.

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

risposta

1

L'unica differenza è keyFrameOpacity animazione in caso di fase. Se lo rimuovi, l'animazione dello stage sarà liscia come per la finestra di avviso. Ciò che è interessante, tuttavia, è che l'animazione è rallentata solo quando si utilizza il cambio di opacità con il ridimensionamento. L'impostazione stage.setScene(null) prima dello timeline.play() rende l'animazione uniforme, ma non sembra molto buona.
Non ho molta familiarità con gli interni della timeline di JavaFx e la sua meccanica degli impulsi, ma ho trovato 2 soluzioni.Uno è quello di gestire il ridimensionamento e modifica l'opacità in diverse fasi:

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

La seconda è quella di utilizzare thread separato per aggiornare tutti i valori. Qualcosa del genere:

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(); 
+0

Grazie! Strano che lo sbiadimento abbia una prestazione così scarsa. – Perneel

Problemi correlati