2013-05-30 14 views
8

JavaFXの基本フローパネルは、ウィンドウのサイズを変更するたびにアイテムをレイアウトします。しかし、アニメーションはありませんし、結果はむしろびっくりします。レイアウト変更時のアニメーション

FlowPane内の各NodeのlayoutXプロパティとlayoutYプロパティに変更リスナーを追加しましたが、結果は多少なりとも機能しますが、ウィンドウのサイズをすばやく変更すると要素が矛盾した場所に残ることがあります。

私には何が欠けていますか?

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要旨は、読みやすくするために、ここで利用可能であり、テストケースが付属しています:https://gist.github.com/teyc/5668517

答えて

10

は、これは本当にきちんとした考えです。私はこれが好き。新しいレイアウトマネージャーをjfxtrasに寄稿することを検討する必要があります。あなたのもともとの投稿ソリューションは

あなたの問題を動作しないのはなぜ

は移動X/Y値の元の値を記録しようと、あなたはその元の値の推移を翻訳終了の周りのロジックです。

TranslateTransitionが進行中の場合、translateX/Y値が変更されます。現在のコードで、アニメーションが終了する前に画面のサイズをすばやく変更する場合は、TranslateTransitionのtoX/Yプロパティを現在のtranslateX/Y値に設定します。これにより、アニメーションの最後の休憩場所は、ノードの望ましい最終静止場所ではなく、以前の中間点になります(目的の場所は、ノードのtranslateX/Y値が0になる点です)。

修正は簡単です、それを修正する方法 - TranslateTransitionためTOX/Yは常にゼロでなければならないので、遷移は常にそれの現在のレイアウト位置が一緒になっているものは何でもにノードを翻訳して終わりますデルタは翻訳されません。

試料溶液コードスニペットはここ

は、コア更新トランジションコードです:

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

試料溶液出力

さらにいくつかの長方形を追加した後に更新アニメーターの出力と新しいレイアウトを強制するために画面のサイズを変更するには(gistで指定したテストハーネスを使用します):

デバッグアニメーションとアニメーションをデバッグするには、あなたのサンプルコード

とその他の問題の修正に

layoutanimator

、私はあなたがアニメーションを遅らせる場合、それは簡単です見つけます。投稿されたプログラムを修正する方法を理解するために、私はTranslateTransitionを秒に設定して、実際に何が起こっているかを見るのに十分な時間を与えます。

アニメーションをスローダウンすると、リスナーによって生成された実際のアニメーションは、ノードがリレーアウトされた後のフレームまで発生しないように見え、ノードが一時的にターゲットに表示されるその位置に元の位置に戻ってゆっくりと目標位置までアニメーションします。

初期位置決めグリッチの修正は、アニメーションの再生を開始する前に、リスナー内のノードを元の位置に戻すことです。

あなたのコードでは、現在移行中のノードを追跡するためにnodesInTransitionマップを使用しますが、マップには何も入れません。私はnodeXTransitionsとnodeYTransitionsに名前を変更しました。別々のトランジションではなく、別々のトランジションではなく別々のトランジションを使用していたためです。私は、ノードの遷移をマップの中に入れて、新しい遷移を作成するときに古い遷移を止めることができるようにします。これは地図ロジックなしですべてがうまくいくように見えたので厳密には必要ないように見えましたが(おそらくJavaFXはこれを暗黙のうちにアニメーションフレームワークの中に入れています)、安全なもののようです。

上記の変更をすべて行った後、すべてがうまくいくように見えました。

潜在さらなる改善

アニメーションの一部が再生され、再レイアウトが発生した場合、多分あなたは全体のアニメーションを再生したくないようなアニメーションのタイミングのために作ることができるいくつかの改善は、おそらくあります新しい再レイアウトのために最初から。あるいは、アニメーション化されたノードのすべてを、持続時間ではなく一定の同じ速度で移動させたい場合があります。しかし、視覚的にはそれほど問題にはならなかったので、私は本当にそれについて心配しません。

テストシステム

私はJava8b91およびOS X 10.8上で私のテストを行いました。更新されたソリューションため

完全なコード

完全なコード更新レイアウトアニメーターがある:ユーザー移動X/Yセッティングから

一つのレイアウトのアニメーションの変更独立を作ることに

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

現在提示されている解決策の欠点は、カスタムレイアウトマネージャのユーザがtranslateX/Y設定を直接適用した場合レイアウトされたノードは、レイアウトマネージャがすべてのコンテンツを中継する(translateX/Yが0に戻ってしまうため)、それらの値を失います。

ユーザーのtranslateX/Yを保持するには、トランジションを変換するのではなく、カスタムトランジションを使用するようにソリューションを更新することができます。カスタムトランジションはノードtransformへのTranslateのプロパティに適用できます。その後、各ノードの追加の変換だけがレイアウトアニメーションの内部動作の影響を受けます。元の変換X/Y値は影響を受けません。

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


あなたが熱心だった場合は、タブヘッダ、TitledPanesとアコーディオンのためopenjfx codeに目を通すと、これらのコントロールのスキンは、その子ノードのレイアウト変更のアニメーションを処理する方法を見ることができました。

+0

普遍性のために、translateX/Yの元の値を保持することは可能ですか?アニメーションを確認してください.STATUSは役に立たないようです。私はそれがjfxtrasに入る前にそれをきれいにするのが大好きです。 –

+0

translateX/Yの元の値を保存するためのアイデアを提供するために、「レイアウトアニメーションの変更を独立フォームのユーザーTranslateX/Y設定」セクションに追加しました。 – jewelsea

+0

「ユーザーTranslateX/Y設定から独立したレイアウトアニメーションの変更を行う」を実装したソリューションに[フォークリンク](https://gist.github.com/jewelsea/5683558)を追加しました。 – jewelsea

関連する問題