Skip to content

Instantly share code, notes, and snippets.

@jewelsea
Forked from teyc/LayoutAnimator.java
Last active May 24, 2020 09:53
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jewelsea/5683558 to your computer and use it in GitHub Desktop.
Save jewelsea/5683558 to your computer and use it in GitHub Desktop.
Animates transitions involved when relaying out nodes in a FlowPane
import javafx.animation.Transition;
import javafx.beans.property.DoubleProperty;
import javafx.beans.value.*;
import javafx.collections.*;
import javafx.event.*;
import javafx.scene.Node;
import javafx.scene.transform.Translate;
import javafx.util.Duration;
import java.util.*;
/**
* 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, MoveXTransition> nodeXTransitions = new HashMap<>();
private Map<Node, MoveYTransition> 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();
switch (doubleProperty.getName()) {
case "layoutX":
MoveXTransition tx = nodeXTransitions.get(node);
if (tx == null) {
tx = new MoveXTransition(node);
nodeXTransitions.put(node, tx);
}
tx.setFromX(tx.getTranslateX() - delta);
tx.playFromStart();
break;
default: // "layoutY"
MoveYTransition ty = nodeYTransitions.get(node);
if (ty == null) {
ty = new MoveYTransition(node);
nodeYTransitions.put(node, ty);
}
ty.setFromY(ty.getTranslateY() - delta);
ty.playFromStart();
}
}
private abstract class MoveTransition extends Transition {
private final Duration MOVEMENT_ANIMATION_DURATION = new Duration(1000);
protected final Translate translate;
public MoveTransition(final Node node) {
setCycleDuration(MOVEMENT_ANIMATION_DURATION);
translate = new Translate();
node.getTransforms().add(translate);
}
public double getTranslateX() {
return translate.getX();
}
public double getTranslateY() {
return translate.getY();
}
}
private class MoveXTransition extends MoveTransition {
private double fromX;
public MoveXTransition(final Node node) {
super(node);
}
@Override protected void interpolate(double frac) {
translate.setX(fromX * (1 - frac));
}
public void setFromX(double fromX) {
translate.setX(fromX);
this.fromX = fromX;
}
}
private class MoveYTransition extends MoveTransition {
private double fromY;
public MoveYTransition(final Node node) {
super(node);
}
@Override protected void interpolate(double frac) {
translate.setY(fromY * (1 - frac));
}
public void setFromY(double fromY) {
translate.setY(fromY);
this.fromY = fromY;
}
}
@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);
}
}
}
}
// todo unobserving nodes should cleanup any intermediate transitions they may have and ensure they are removed from transition cache to prevent memory leaks.
}
import java.util.Random;
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
/**
* Creates a FlowPane and adds some rectangles inside.
* A LayoutAnimator is set to observe the contents of the FlowPane for layout
* changes.
*/
public class TestLayoutAnimate extends Application {
public static void main(String[] args) {
Application.launch(TestLayoutAnimate.class);
}
@Override
public void start(Stage primaryStage) {
final Pane root = new FlowPane();
// Clicking on button adds more rectangles
Button btn = new Button();
btn.setText("Add Rectangles");
final TestLayoutAnimate self = this;
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
self.addRectangle(root);
}
});
root.getChildren().add(btn);
// add 5 rectangles to start with
for (int i = 0; i < 5; i++) {
addRectangle(root);
}
root.layout();
LayoutAnimator ly = new LayoutAnimator();
ly.observe(root.getChildren());
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("Flow Layout Test");
primaryStage.setScene(scene);
primaryStage.show();
}
protected void addRectangle(Pane root) {
Random rnd = new Random();
Rectangle nodeNew = new Rectangle(50 + rnd.nextInt(20), 40 + rnd.nextInt(20));
// for testing pre-translated nodes
// nodeNew.setTranslateX(rnd.nextInt(20));
// nodeNew.setTranslateY(rnd.nextInt(15));
nodeNew.setStyle("-fx-margin: 10;");
String rndColor = String.format("%02X", rnd.nextInt(), rnd.nextInt(), rnd.nextInt());
try {
Paint rndPaint = Paint.valueOf(rndColor);
nodeNew.setFill(rndPaint);
} catch (Exception e) {
nodeNew.setFill(Paint.valueOf("#336699"));
}
nodeNew.setStroke(Paint.valueOf("black"));
root.getChildren().add(0, nodeNew);
}
}
@jewelsea
Copy link
Author

Answer to StackOverflow Question: Animation upon layout changes

@justjake
Copy link

One of my favorite stack overflow answers. I'm writing my first (simple) java program with JavaFX and this Q/A demonstrates well the observable pattern and animations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment