2013-05-30 20 views
8

Podstawowy panel FlowPane w JavaFX określa elementy wewnątrz każdej zmiany rozmiaru okna. Jednak nie ma animacji, a wynik jest dość drażniący.Animacja po zmianie układu

Podłączyłem odbiornik zmian we właściwościach layoutX i layoutY każdego węzła w panelu FlowPane, a wynik działa mniej więcej, ale czasami, gdy szybko zmieniam rozmiar okna, elementy pozostawia się w niespójnych miejscach.

Czego mi brakuje?

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

GIST jest dostępny tutaj dla czytelności i pochodzi z testu: https://gist.github.com/teyc/5668517

Odpowiedz

10

To jest naprawdę zgrabny pomysł. Lubię to. Powinieneś rozważyć dodanie nowego menedżera układu do jfxtras.

Dlaczego Twój Cytat nie rozwiązuje problemu

Twój problem jest logika wokół próbując nagrać oryginalną wartość dla translateX/wartości Y, a kończąc swój przełożyć przejście na tej pierwotnej wartości.

Gdy trwa translacja, zmienia ona wartości translateX/Y. Przy twoim bieżącym kodzie, jeśli szybko zmienisz rozmiar ekranu przed zakończeniem animacji, ustaw właściwości toX/Y twojego TranslateTransition na bieżące wartości translateX/Y. To sprawia, że ​​ostateczne miejsce spoczynku animacji jest wcześniejszym punktem pośrednim niż pożądanym miejscem ostatecznego spoczynku dla węzła (pożądanym miejscem jest właśnie punkt, w którym wartości translateX/Y dla węzła wynoszą 0).

Jak naprawić

Rozwiązaniem jest prosty - Tox/Y dla TranslateTransition powinny zawsze być zero, więc przejście zawsze kończy się tłumaczenia węzeł na cokolwiek to jest aktualna pozycja układ ma być z bez tłumaczenia delta.

roztworu próbki Code Snippet

Oto rdzeń zaktualizowany kod przejście:

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

wyjściowy roztwór próbki

Wyjście zaktualizowanego animatora po dodaniu jeszcze kilka prostokątów i zmiana rozmiaru ekranu w celu wymuszenia nowych układów (przy użyciu uprzęży testowej dostarczonej w istocie):

layoutanimator

debugowanie animacji oraz ustalające dodatkowe problemy z Kodeksem Sample

w animacjach debugowania, uważam, że to jest łatwiejsze, jeśli spowolnienie animacji. Aby dowiedzieć się, jak naprawić opublikowany program, ustawię narzędzie TranslateTransition na sekundę, aby dać wystarczająco dużo czasu, aby sprawdzić, co się właściwie dzieje.

Spowolnienie animacji pomogło mi zobaczyć, że rzeczywiste animacje generowane przez twoich słuchaczy nie wydają się mieć miejsca, dopóki klatka po węźle nie zostanie usunięta, co powoduje krótki błąd w miejscu, w którym węzeł chwilowo pojawi się w jego celu pozycji, a następnie wróć do pierwotnej pozycji, a następnie powoli animuj do pozycji docelowej.

Poprawka początkowego błędu pozycjonowania polega na przetłumaczeniu węzła w odbiorniku z powrotem na pierwotną pozycję przed rozpoczęciem odtwarzania animacji.

Twój kod wykorzystuje mapę nodesInTransition do śledzenia aktualnie przesuwających się węzłów, ale nigdy nie umieszcza niczego na mapie. Zmieniłem nazwę na nodeXTransitions i nodeYTransitions (ponieważ używam oddzielnych przejść x i y zamiast pojedynczego przejścia dla obu, ponieważ używanie oddzielnych wydawało się łatwiejsze). Umieszczam przejścia węzłów w mapie podczas ich tworzenia, tak, że mogę zatrzymać stare przejścia, gdy utworzę nowe. Nie wydawało się to bezwzględnie konieczne, ponieważ wszystko wydawało się działać bez logiki mapy (być może JavaFX robi już coś takiego w strukturze animacji), ale wydaje się, że jest to bezpieczne, więc zachowałem to.

Po dokonaniu zmian opisanych powyżej wszystko wydawało się działać poprawnie.

Potencjalne Dalsze ulepszenia

Istnieje być może pewne ulepszenia, które mogą zostać wprowadzone do czasu animacji tak, że jeśli animacja jest częściowo odtworzony oraz relayout występuje, może nie chcą grać całą animację od początku dla nowego przekaźnika. A może chcesz, aby wszystkie animowane węzły poruszały się ze stałą, identyczną prędkością, a nie przez pewien czas. Ale wizualnie to nie miało większego znaczenia, więc nie przejmowałbym się tym.

Test System

odbyłem testy na Java8b91 i OS X 10.8.

Pełny kod na Updated Rozwiązanie

Pełny kod zaktualizowany animator układ jest:

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

Na Making Układ Animacja Changes niezależny od TranslateX użytkownika/Ustawienia Y

One wadą rozwiązania przedstawionego obecnie jest to, że użytkownik niestandardowego menedżera układu zastosował bezpośrednio ustawienia translateX/Y o rozplanowane węzły, następnie stracą te wartości, ponieważ menedżer układu przekaże całą zawartość (ponieważ translateX/Y kończy się z powrotem na 0).

Aby zachować translateX/Y użytkownika, można zaktualizować rozwiązanie, aby korzystało z niestandardowych przejść, a nie tłumaczyć przejścia. Niestandardowe przejścia można zastosować do właściwości Translate do węzłów "transform. Wtedy tylko dodatkowa transformacja na każdym węźle jest zakłócana przez wewnętrzne działanie animacji układu - nie ma to wpływu na oryginalne wartości translateX/Y użytkownika.

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


Gdybyście byli chętni, można spojrzeć na openjfx code nagłówków zakładce TitledPanes i akordeony i zobaczyć, jak skórki do tych kontroli obsłużyć animację zmian układu dla swoich węzłów potomnych.

+0

Czy w ogóle możliwe jest zachowanie oryginalnych wartości translateX/Y? Sprawdzanie animacji.STATUS nie wydaje się pomóc. Chciałbym go wyczyścić, zanim przejdzie do jfxtras. –

+0

Dodano "Przy tworzeniu animacji układu niezależne od ustawień User TranslateX/Y", aby podać pomysły na zachowanie oryginalnych wartości translateX/Y. – jewelsea

+0

Dodano [łącze rozwidlone] (https://gist.github.com/jewelsea/5683558) do rozwiązania implementującego "Wprowadzanie zmian animacji układu niezależnie od ustawień tłumaczącego dla użytkownika/Y". – jewelsea

Powiązane problemy