2013-05-30 4 views
8

Der grundlegende FlowPane in JavaFX legt die Elemente in jedem Fenster fest, wenn die Größe des Fensters geändert wird. Es gibt jedoch keine Animation und das Ergebnis ist ziemlich verwirrend.Animation bei Layoutänderungen

Ich habe einen Change-Listener für die layoutX- und layoutY-Eigenschaften jedes Node innerhalb des FlowPane angeschlossen und das Ergebnis funktioniert mehr oder weniger, aber manchmal, wenn ich das Fenster schnell skaliere, bleiben die Elemente an inkonsistenten Stellen.

Was fehlt mir?

package javafxapplication1; 

import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
import javafx.animation.Transition; 
import javafx.animation.TranslateTransition; 
import javafx.beans.property.DoubleProperty; 
import javafx.beans.value.ChangeListener; 
import javafx.beans.value.ObservableValue; 
import javafx.collections.ListChangeListener; 
import javafx.collections.ObservableList; 
import javafx.scene.Node; 
import javafx.util.Duration; 

/** 
* Animates an object when its position is changed. For instance, when 
* additional items are added to a Region, and the layout has changed, then the 
* layout animator makes the transition by sliding each item into its final 
* place. 
*/ 
public class LayoutAnimator implements ChangeListener, ListChangeListener<Node> { 

    private Map<Node, Transition> nodesInTransition; 

    public LayoutAnimator() { 
    this.nodesInTransition = new HashMap<>(); 
    } 

    /** 
    * Animates all the children of a Region. 
    * <code> 
    * VBox myVbox = new VBox(); 
    * LayoutAnimator animator = new LayoutAnimator(); 
    * animator.observe(myVbox.getChildren()); 
    * </code> 
    * 
    * @param nodes 
    */ 
    public void observe(ObservableList<Node> nodes) { 
    for (Node node : nodes) { 
     this.observe(node); 
    } 
    nodes.addListener(this); 
    } 

    public void unobserve(ObservableList<Node> nodes) { 
    nodes.removeListener(this); 
    } 

    public void observe(Node n) { 
    n.layoutXProperty().addListener(this); 
    n.layoutYProperty().addListener(this); 
    } 

    public void unobserve(Node n) { 
    n.layoutXProperty().removeListener(this); 
    n.layoutYProperty().removeListener(this); 
    } 

    @Override 
    public void changed(ObservableValue ov, Object oldValue, Object newValue) { 
    final Double oldValueDouble = (Double) oldValue; 
    final Double newValueDouble = (Double) newValue; 
    final Double changeValueDouble = newValueDouble - oldValueDouble; 
    DoubleProperty doubleProperty = (DoubleProperty) ov; 

    Node node = (Node) doubleProperty.getBean(); 
    final TranslateTransition t; 
    if ((TranslateTransition) nodesInTransition.get(node) == null) { 
     t = new TranslateTransition(Duration.millis(150), node); 
    } else { 
     t = (TranslateTransition) nodesInTransition.get(node); 
    } 

    if (doubleProperty.getName().equals("layoutX")) { 
     Double orig = node.getTranslateX(); 
     if (Double.compare(t.getFromX(), Double.NaN) == 0) { 
     t.setFromX(orig - changeValueDouble); 
     t.setToX(orig); 
     } 
    } 
    if (doubleProperty.getName().equals("layoutY")) { 
     Double orig = node.getTranslateY(); 
     if (Double.compare(t.getFromY(), Double.NaN) == 0) { 
     t.setFromY(orig - changeValueDouble); 
     t.setToY(orig); 
     } 
    } 
    t.play(); 

    } 

    @Override 
    public void onChanged(ListChangeListener.Change change) { 
    while (change.next()) { 
     if (change.wasAdded()) { 
     for (Node node : (List<Node>) change.getAddedSubList()) { 
      this.observe(node); 
     } 
     } else if (change.wasRemoved()) { 
     for (Node node : (List<Node>) change.getRemoved()) { 
      this.unobserve(node); 
     } 
     } 
    } 
    } 
} 

A Gist ist hier zur besseren Lesbarkeit erhältlich und kommt mit einem Testfall: https://gist.github.com/teyc/5668517

Antwort

10

Dies ist eine wirklich nette Idee. Ich mag das. Sie sollten Ihren neuen Layout-Manager zu jfxtras beitragen.

Warum Ihr Zitat Lösung nicht

Ihr Problem funktionierts ist die Logik um versuchen, einen ursprünglichen Wert für die translateX/Y-Werte zu erfassen und Ihr Übergang zu diesem ursprünglichen Wert übersetzen endet.

Wenn eine TranslateTransition ausgeführt wird, werden die translateX/Y-Werte geändert. Wenn Sie bei Ihrem aktuellen Code den Bildschirm schnell ändern, bevor Ihre Animationen beendet werden, legen Sie die toX/Y-Eigenschaften von TranslateTransition auf die aktuellen translateX/Y-Werte fest. Dies macht den letzten Ruheplatz der Animation zu einem früheren Zwischenpunkt als zu dem gewünschten endgültigen Ruheplatz für den Knoten (der gewünschte Ort ist nur der Punkt, an dem die translateX/Y-Werte für den Knoten 0 sind).

wie man es beheben

Die Lösung ist einfach - toX/Y für die TranslateTransition immer Null sein sollte, so dass der Übergang immer übersetzen endet um den Knoten zu, was auch immer es die aktuelle Layout-Position sollte mit sein kein Übersetzungsdelta.

Probenlösung Codeschnipsel

Hier ist der Kern aktualisiert Übergang Code:

TranslateTransition t; 
switch (doubleProperty.getName()) { 
    case "layoutX": 
    t = nodeXTransitions.get(node); 
    if (t == null) { 
     t = new TranslateTransition(Duration.millis(150), node); 
     t.setToX(0); 
     nodeXTransitions.put(node, t); 
    } 
    t.setFromX(node.getTranslateX() - delta); 
    node.setTranslateX(node.getTranslateX() - delta); 
    break; 

    default: // "layoutY" 
    t = nodeYTransitions.get(node); 
    if (t == null) { 
     t = new TranslateTransition(Duration.millis(150), node); 
     t.setToY(0); 
     nodeYTransitions.put(node, t); 
    } 
    t.setFromY(node.getTranslateY() - delta); 
    node.setTranslateY(node.getTranslateY() - delta); 
} 

t.playFromStart(); 

Probenlösung Ausgabe

Ausgabe des aktualisierten Animator nach ein paar Rechtecke hinzufügen und Die Größe des Bildschirms zu ändern, um neue Layouts zu erzwingen, ist (unter Verwendung des Testkabelbaums, den Sie auf gist angegeben haben):

layoutanimator

Debugging Animationen und Beheben Zusätzliche Probleme mit Ihrem Beispielcode

In Debug-Animationen, finde ich es einfacher ist, wenn Sie Animationen verlangsamen. Um herauszufinden, wie das gepostete Programm repariert werden kann, setze ich TranslateTransition auf eine Sekunde, um dem Auge genügend Zeit zu geben, um zu sehen, was tatsächlich passiert.

Verlangsamung der Animation half mir zu sehen, dass die tatsächlichen Animationen von Ihren Zuhörern scheinen nicht stattfinden, bis ein Frame nach dem Knoten neu ausgelöst wurde, macht einen kurzen Fehler, wo der Knoten erscheint kurz in seinem Ziel Position, dann gehen Sie zurück zu seiner ursprünglichen Position, dann animieren Sie langsam zur Zielposition.

Die Lösung für die anfängliche Positionierung Glitch ist, den Knoten im Zuhörer zurück in die ursprüngliche Position zu übersetzen, bevor Sie mit der Animation beginnen.

Ihr Code verwendet eine nodesInTransition-Map, um die aktuell übergehenden Knoten im Auge zu behalten, aber sie fügt nie etwas in die Karte ein. Ich habe ihn in nodeXTransitions und nodeYTransitions umbenannt (weil ich separate x- und y-Übergänge anstelle eines einzelnen Übergangs für beide verwende, da die Verwendung separater einfacher schien). Ich setze die Knotenübergänge in die Map, wenn sie erstellt werden, damit ich alte Übergänge stoppen kann, wenn ich neue erstelle. Dies schien nicht unbedingt notwendig zu sein, da alles ohne die Kartenlogik zu funktionieren schien (vielleicht macht JavaFX so etwas implizit innerhalb seines Animationsrahmens), aber es scheint eine sichere Sache zu sein, also habe ich es behalten.

Nachdem ich die oben beschriebenen Änderungen vorgenommen hatte, schien alles in Ordnung zu sein.

Potential Weitere Verbesserungen

Es gibt vielleicht einige Verbesserungen, die für das Timing von Animationen gemacht werden könnte, so dass, wenn eine Animation teilweise gespielt wird und ein neues Layout auf, vielleicht haben Sie wollen nicht eine ganze Animation spielen von Anfang an für das neue relayout. Oder vielleicht möchten Sie, dass sich alle animierten Knoten mit einer konstanten, identischen Geschwindigkeit und nicht für eine bestimmte Dauer bewegen. Aber visuell schien das nicht so wichtig zu sein, also würde ich mir darüber keine Sorgen machen.

Testsystem

habe ich meine Prüfung auf Java8b91 und OS X 10.8.

vollständigen Code für die aktualisierte Lösung

Voll Code der aktualisierte Layout Animator ist:

import javafx.animation.TranslateTransition; 
import javafx.beans.property.DoubleProperty; 
import javafx.beans.value.ChangeListener; 
import javafx.beans.value.ObservableValue; 
import javafx.collections.ListChangeListener; 
import javafx.collections.ObservableList; 
import javafx.scene.Node; 
import javafx.util.Duration; 

import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 

/** 
* Animates an object when its position is changed. For instance, when 
* additional items are added to a Region, and the layout has changed, then the 
* layout animator makes the transition by sliding each item into its final 
* place. 
*/ 
public class LayoutAnimator implements ChangeListener<Number>, ListChangeListener<Node> { 

    private Map<Node, TranslateTransition> nodeXTransitions = new HashMap<>(); 
    private Map<Node, TranslateTransition> nodeYTransitions = new HashMap<>(); 

    /** 
    * Animates all the children of a Region. 
    * <code> 
    * VBox myVbox = new VBox(); 
    * LayoutAnimator animator = new LayoutAnimator(); 
    * animator.observe(myVbox.getChildren()); 
    * </code> 
    * 
    * @param nodes 
    */ 
    public void observe(ObservableList<Node> nodes) { 
    for (Node node : nodes) { 
     this.observe(node); 
    } 
    nodes.addListener(this); 
    } 

    public void unobserve(ObservableList<Node> nodes) { 
    nodes.removeListener(this); 
    } 

    public void observe(Node n) { 
    n.layoutXProperty().addListener(this); 
    n.layoutYProperty().addListener(this); 
    } 

    public void unobserve(Node n) { 
    n.layoutXProperty().removeListener(this); 
    n.layoutYProperty().removeListener(this); 
    } 

    @Override 
    public void changed(ObservableValue<? extends Number> ov, Number oldValue, Number newValue) { 
    final double delta = newValue.doubleValue() - oldValue.doubleValue(); 
    final DoubleProperty doubleProperty = (DoubleProperty) ov; 
    final Node node = (Node) doubleProperty.getBean(); 

    TranslateTransition t; 
    switch (doubleProperty.getName()) { 
     case "layoutX": 
     t = nodeXTransitions.get(node); 
     if (t == null) { 
      t = new TranslateTransition(Duration.millis(150), node); 
      t.setToX(0); 
      nodeXTransitions.put(node, t); 
     } 
     t.setFromX(node.getTranslateX() - delta); 
     node.setTranslateX(node.getTranslateX() - delta); 
     break; 

     default: // "layoutY" 
     t = nodeYTransitions.get(node); 
     if (t == null) { 
      t = new TranslateTransition(Duration.millis(150), node); 
      t.setToY(0); 
      nodeYTransitions.put(node, t); 
     } 
     t.setFromY(node.getTranslateY() - delta); 
     node.setTranslateY(node.getTranslateY() - delta); 
    } 

    t.playFromStart(); 
    } 

    @Override 
    public void onChanged(Change change) { 
    while (change.next()) { 
     if (change.wasAdded()) { 
     for (Node node : (List<Node>) change.getAddedSubList()) { 
      this.observe(node); 
     } 
     } else if (change.wasRemoved()) { 
     for (Node node : (List<Node>) change.getRemoved()) { 
      this.unobserve(node); 
     } 
     } 
    } 
    } 
} 

Auf Layout-Animation, Änderungen Verselbständigung von Benutzer TranslateX/Y Einstellungen

One Der Nachteil der aktuell dargestellten Lösung ist, dass der Benutzer des benutzerdefinierten Layout-Managers translateX/Y-Einstellungen direkt angewendet hat o die angelegten Knoten, dann verlieren sie diese Werte, da der Layout-Manager den gesamten Inhalt weiterleitet (da das translateX/Y schließlich wieder auf 0 gesetzt wird).

Um das translateX/Y des Benutzers beizubehalten, könnte die Lösung aktualisiert werden, um benutzerdefinierte Übergänge anstelle von Übergängen zu verwenden. Die benutzerdefinierten Übergänge können auf Eigenschaften eines Translate für die Knoten transform angewendet werden. Dann wird nur die zusätzliche Transformation auf jedem Knoten durch die inneren Abläufe der Layoutanimation beeinflusst - die ursprünglichen translateX/Y-Werte des Benutzers werden nicht beeinflusst.

I forked the gist from your question to implement the custom transition enhancement mentioned above.


Wenn Sie daran interessiert waren, könnten Sie die openjfx code für Registerkarte Header schauen über, TitledPanes und Akkordeons und sehen, wie die Häute für diese Kontrollen die Animation von Layout-Änderungen für ihre untergeordneten Knoten behandeln.

+0

Ist es allgemein möglich, die ursprünglichen Werte von translateX/Y beizubehalten? Animation überprüfen. STATUS scheint nicht zu helfen. Ich würde es gerne aufräumen, bevor es in jxtras geht. –

+0

Hinzugefügt "Auf Making Layout-Animation Änderungen unabhängige Form Benutzer TranslateX/Y Einstellungen" Abschnitt, um Ideen zum ursprünglichen Werte von translateX/Y zu erhalten. – jewelsea

+0

Es wurde eine [fork link] (https://gist.github.com/jewelsea/5683558) zu einer Lösung hinzugefügt, die "Making Layout-Animationsänderungen unabhängig von den Benutzer-TranslateX/Y-Einstellungen" implementiert. – jewelsea

Verwandte Themen