This is a really neat idea. I like it. You should consider introducing your new layout manager into jfxtras .
Why is your original solution not working
Your problem is the logic associated with trying to write the original value for the translateX / Y values and completing the translation to the original value.
When a TranslationTransition is being executed, it changes the values of translateX / Y. With your current code, if you quickly resize the screen before the animation finishes, you set the toX / Y properties of your TranslateTransition to the current translateX / Y values. This makes the final resting place of the animation some preliminary intermediate point, and not the desired final resting place for the node (the desired place is just the point where the translateX / Y values for the node are 0).
How to fix it
The fix is simple - toX / Y for TranslateTransition should always be zero, so the transition always ends with the node switching to the fact that it should not have the current layout position without translation.
Code snippet code example
Here is the basic updated conversion 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();
Sampling decision result
Output of the updated animator after adding a few more rectangles and resizing the screen to create new layouts (using the test harness that you pointed to gist):

Debug animations and fix additional problems with your sample code
In debug animations, I find it easier to slow down the animation. To decide how to fix the published program, I switched TranslateTransition for a second to give enough time to see what was actually happening.
Slowing down the animation helped me understand that the actual animations created by your listeners do not seem to happen until the frame after the node is restored, making a brief glitch when the target position appears in it for a moment, then go back to starting position, then slowly plunge into the target position.
The fix for the initial smoothing of positioning is to put the node in the listener back to its original position before starting the animation.
Your code uses the nodeInTransition map to keep track of the current moving nodes, but it never puts anything on the map. I renamed it to nodeXTransitions and nodeYTransitions (because I use separate x and y transitions, not one transition for both, since using separate ones was easier). I put node transitions on the map as they are created so that I can stop the old transitions when creating new ones. It didn’t seem absolutely necessary, because everything seemed to work fine without map logic (maybe JavaFX is already doing something like this implicitly inside the animation frame), but it seems that it’s safe, so I saved it.
After I made the changes described above, everything seemed to work fine.
Possible additional improvements
There may be some improvements that can be made to synchronize animations, so if the animation is partially playing and relaying, you probably do not want to play the whole animation from the very beginning for the new repeater. Or perhaps you want all the animated nodes to move at a constant, uniform speed, rather than duration. But visually it did not seem to matter much, so I would not worry about it.
Test system
I tested on Java8b91 and OS X 10.8.
Full code for updated solution
Full code for the updated layout animator:
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); } } } } }
When making layout animation changes that are independent of TranslateX / Y user preferences
One of the drawbacks of the solution currently presented is that if the user of the custom layout manager applied the translateX / Y parameters directly to the laid out nodes, they will lose these values because the layout manager transfers all the content (since the X / Y transformation ends with which is set to 0).
To save a custom X / Y translation, the solution can be updated to use custom transitions rather than translating transitions. Custom transitions can be applied to Translate properties on transform nodes. Then only an additional conversion on each node affects the internal operation of the layout animation - the original translateX / Y values of the user are not affected.
I highlighted the point from your question in order to implement the advanced transition enhancement mentioned above .
If you were passionate, you could look at the openjfx code for the tabs, TitlesPanes and Accordions headers and see how the skins for these controls are handled animating layout changes for their child nodes.