Если анимацию ключевых кадров можно указать в 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();
}
}
Синтаксис ${reference}
— это привязка выражения , синтаксис $reference
— это ссылка на переменную. Как отметил Слоу, вам, вероятно, понадобится привязка в вашем FXML, если вы хотите заменить привязку, которая у вас сейчас есть в коде. И, вероятно, вы захотите связать translateX
, поскольку обычно анимируется именно он, а не x
(который обычно используется для макета).
Спасибо @Slaw и @jewelsea за догадки. ... = "${count}
терпит неудачу, потому что тогда ему не удается проанализировать строковое представление «DoubleProperty [...]» до двойного значения. Если я создам подкласс SimpleDoubleProperty, чтобы переопределить toString()
с помощью return this.getValue().toString();
, я не получу ошибку при использовании .. = "${count}"
, но анимации по-прежнему не будет.
В результате некоторых экспериментов получилось следующее:
<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>
Как ни странно, но всё равно очень читабельно, здорово!
x = "${count}"
работает (или дажеtranslateX = "${count}"
)?