-
-
Save jewelsea/5683558 to your computer and use it in GitHub Desktop.
Animates transitions involved when relaying out nodes in a FlowPane
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.