Указание анимации ключевых кадров в FXML

Если анимацию ключевых кадров можно указать в FXML, то многие форматы файлов, содержащие анимацию, можно перенести в FXML без потери информации. Я знаю более простые варианты, такие как RotateTransition.

Самый простой подход к объявлению цели KeyValue как $text.translateX (или других вариантов, которые я мог придумать, приводил к ошибкам проверки типов во время выполнения. После некоторых возни мне удалось создать код, который не выдает ошибок, но привязка к цели, похоже, также не происходит. Я добавил строку в контроллер, чтобы можно было легко протестировать желаемое поведение. Как мне избавиться от этой строки и заставить ее работать только с FXML? Разве это не должно работать и с $text.translateX?

анимированный.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>

Анимированныйконтроллер.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();
    }
}

ПриветПриложение.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();
    }
}

x = "${count}" работает (или даже translateX = "${count}")?

Slaw 19.04.2024 20:18

Синтаксис ${reference} — это привязка выражения , синтаксис $reference — это ссылка на переменную. Как отметил Слоу, вам, вероятно, понадобится привязка в вашем FXML, если вы хотите заменить привязку, которая у вас сейчас есть в коде. И, вероятно, вы захотите связать translateX, поскольку обычно анимируется именно он, а не x (который обычно используется для макета).

jewelsea 19.04.2024 20:28

Спасибо @Slaw и @jewelsea за догадки. ... = "${count} терпит неудачу, потому что тогда ему не удается проанализировать строковое представление «DoubleProperty [...]» до двойного значения. Если я создам подкласс SimpleDoubleProperty, чтобы переопределить toString() с помощью return this.getValue().toString();, я не получу ошибку при использовании .. = "${count}", но анимации по-прежнему не будет.

Johannes Riecken 20.04.2024 09:40
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
3
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В результате некоторых экспериментов получилось следующее:

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

Почему это работает? Не уверен. Мне не удалось найти в документе Введение в FXML что-либо, что объясняло бы это поведение, но, возможно, кто-то еще знает, задокументировано ли это и где. Тем не менее, очевидно, что использование xxxProperty дает вам фактическое свойство, связанное с xxx, а не просто значение свойства.

Обратите внимание, что это решение делает промежуточное свойство ненужным.


Пример

Протестировано с JavaFX 22 (но не с другими версиями JavaFX).

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

Контроллер.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>

Как ни странно, но всё равно очень читабельно, здорово!

Johannes Riecken 22.04.2024 16:33

Другие вопросы по теме