2

If keyframe animation can be specified in FXML, then a lot of file formats containing animations can be ported to FXML without information loss. I'm aware of the simpler variants like RotateTransition.

The most straightforward approach of declaring the KeyValue target as $text.translateX (or the other variations I could think of gave type-checking runtime errors. After some fiddling, I managed to come up with code that doesn't give errors, but the target binding also doesn't seem to happen. I've added a line to the controller for that to allow easy testing of what the wanted behavior is. How can I get rid of that line and make it work just with FXML? Shouldn't it also work with $text.translateX?

animated.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.animation.*?>
<?import javafx.beans.property.SimpleDoubleProperty?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.Group?>
<?import javafx.scene.Scene?>
<?import javafx.scene.text.Text?>
<?import javafx.util.Duration?>
<?import java.lang.Double?>
<Scene xmlns:fx="http://javafx.com/fxml" fx:controller="org.example.demo.AnimatedController">
    <height>240.0</height>
    <width>320.0</width>
    <fx:define>
        <SimpleDoubleProperty fx:id="count"/>
        <Timeline fx:id="timeline">
            <keyFrames>
                <fx:define>
                    <Double fx:id="endValue0" fx:value="0.0"/>
                    <Double fx:id="endValue1" fx:value="100.0"/>
                </fx:define>
                <KeyFrame fx:id="keyFrame0">
                    <values>
                        <KeyValue fx:id="keyValue0" endValue="$endValue0">
                            <target>
                                <fx:reference source="count"/>
                            </target>
                        </KeyValue>
                    </values>
                    <time>
                        <Duration fx:constant="ZERO"/>
                    </time>
                </KeyFrame>
                <KeyFrame fx:id="keyFrame1">
                    <values>
                        <KeyValue fx:id="keyValue1" endValue="$endValue1">
                            <target>
                                <fx:reference source="count"/>
                            </target>
                        </KeyValue>
                    </values>
                    <time>
                        <Duration millis="1000"/>
                    </time>
                </KeyFrame>
            </keyFrames>
        </Timeline>
    </fx:define>
    <Group>
        <Text fx:id="text" y="60" x="${count.value}">Hello, World!</Text>
        <Button onAction="#play">Play</Button>
    </Group>
</Scene>

AnimatedController.java

package org.example.demo;

import javafx.animation.Timeline;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.text.Text;

public class AnimatedController {

    @FXML
    private Text text;

    @FXML
    private Timeline timeline;

    @FXML
    private SimpleDoubleProperty count = new SimpleDoubleProperty(0);

    public void play(ActionEvent ignoredActionEvent) {
        text.xProperty().bind(count); // without this line, text.getX() doesn't get updated
        count.addListener((_, oldVal, newVal) -> System.out.printf("%s, %s, %s\n", text.getX(), oldVal, newVal));
        timeline.play();
    }
}

HelloApplication.java

package org.example.demo;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class HelloApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(HelloApplication.class.getResource("animated.fxml"));
        Scene scene = fxmlLoader.load();
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }
}
3
  • Does x="${count}" work (or even translateX="${count}")? Commented Apr 19, 2024 at 18:18
  • The ${reference} syntax is an expression binding, the $reference syntax is variable reference. As pointed out by Slaw, you probably want a binding in your FXML if you want to replace the binding you currently have in code. And probably you want to bind the translateX as that is usually what is animated rather than x (which is usually used for layout). Commented Apr 19, 2024 at 18:28
  • Thanks @Slaw and @jewelsea for the guesses. ...="${count} fails because then it fails to parse the string representation "DoubleProperty [...]" to a double. If I subclass SimpleDoubleProperty to override toString() with return this.getValue().toString();, I don't get an error when using ..="${count}", but still no animation. Commented Apr 20, 2024 at 7:40

1 Answer 1

2

From some experimentation, the following worked:

<KeyValue target="$someNode.translateXProperty" endValue="..."/>

Why does that work? Not sure. I was unable to find anything in the Introduction to FXML document that explained this behavior, but maybe someone else knows if and where it is documented. That said, apparently using xxxProperty gives you the actual property associated with xxx instead of just the property's value.

Note this solution makes the intermediate property unnecessary.


Example

Tested with JavaFX 22 (but not with other JavaFX versions).

Main.java

package com.example;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;

public class Main extends Application {

  @Override
  public void start(javafx.stage.Stage primaryStage) throws Exception {
    var loader = new FXMLLoader();
    loader.setLocation(Main.class.getResource("/Main.fxml"));

    var root = loader.<Parent>load();
    var controller = loader.<Controller>getController();

    primaryStage.setScene(new Scene(root, 600, 400));
    primaryStage.show();

    controller.playAnimation();
  }
}

Controller.java

package com.example;

import javafx.animation.Timeline;
import javafx.fxml.FXML;

public class Controller {

  @FXML private Timeline timeline;

  public void playAnimation() {
    timeline.play();
  }
}

Main.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.Double?>
<?import javafx.animation.Animation?>
<?import javafx.animation.KeyFrame?>
<?import javafx.animation.KeyValue?>
<?import javafx.animation.Timeline?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.util.Duration?>

<StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
    fx:controller="com.example.Controller">

    <Label fx:id="label" text="Hello, World!" />

    <fx:define>
        <Double fx:id="fromValue" fx:value="-100.0" />
        <Double fx:id="toValue" fx:value="100.0" />

        <Duration fx:id="startTime" fx:constant="ZERO" />
        <Duration fx:id="endTime" millis="1000.0" />

        <Timeline fx:id="timeline" autoReverse="true">
            <cycleCount>
                <Animation fx:constant="INDEFINITE" />
            </cycleCount>
            <keyFrames>
                <KeyFrame time="$startTime">
                    <values>
                        <KeyValue target="$label.translateXProperty" endValue="$fromValue" />
                    </values>
                </KeyFrame>
                <KeyFrame time="$endTime">
                    <values>
                        <KeyValue target="$label.translateXProperty" endValue="$toValue" />
                    </values>
                </KeyFrame>
            </keyFrames>
        </Timeline>
    </fx:define>

</StackPane>
Sign up to request clarification or add additional context in comments.

1 Comment

Weird as it is, it's still very readable, nice!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.