Анимация: переходы и Timeline
Анимация в JavaFX — это плавное изменение свойства узла во времени: прозрачности, сдвига, масштаба. Вы задаёте начальное и конечное значение и длительность, а JavaFX сам пересчитывает промежуточные значения на каждом кадре. Классы лежат в пакете javafx.animation, отдельная зависимость не нужна. В записной книжке анимация подскажет пользователю, что произошло: таблица плавно появится после загрузки, сообщение о новой заметке погаснет само, а поле с пустым заголовком «встряхнётся».
Переходы
Переход (transition) — готовая анимация одного свойства одного узла:
| Класс | Что меняет |
|---|---|
FadeTransition | Прозрачность opacity: 0 — невидим, 1 — непрозрачен |
TranslateTransition | Сдвиг translateX и translateY |
ScaleTransition | Масштаб scaleX и scaleY |
RotateTransition | Угол поворота |
Длительность задаёт класс javafx.util.Duration: Duration.millis(600), Duration.seconds(2). Смотрите, чтобы IDE не подставила java.time.Duration — у него нет метода millis. Остальные классы урока импортируются из javafx.animation.
Плавное появление
Весь код этого урока — в MainController.java. В обработчике успешной загрузки из урока о фоновых задачах добавим появление таблицы:
// src/main/java/com/example/notes/MainController.java — в конце task.setOnSucceeded(...)
FadeTransition fade = new FadeTransition(Duration.millis(600), table);
fade.setFromValue(0);
fade.setToValue(1);
fade.play();play() запускает анимацию и сразу возвращает управление — программа не ждёт её окончания. Запустите приложение: таблица с заметками проявится из пустоты за 0,6 секунды.
Исчезающее сообщение
Пусть после добавления заметки в строке состояния появляется «Заметка добавлена: …» и через две секунды гаснет. Один переход создадим в initialize() и будем запускать заново:
// src/main/java/com/example/notes/MainController.java — новое поле, конец initialize() и новый метод
private FadeTransition statusFade;
// в конце initialize():
statusFade = new FadeTransition(Duration.seconds(1), statusLabel);
statusFade.setDelay(Duration.seconds(2));
statusFade.setToValue(0);
private void showStatus(String text) {
statusFade.stop();
statusLabel.setText(text);
statusLabel.setOpacity(1);
statusFade.playFromStart();
}setDelay откладывает начало, а без setFromValue переход стартует с текущей прозрачности. stop() и playFromStart() перезапускают анимацию, если заметки добавляют быстрее, чем гаснет сообщение. В конце addNote() после clearForm() вызовите showStatus("Заметка добавлена: " + title).
Встряхивание поля
Вместо предупреждения Alert из урока о диалогах встряхнём поле заголовка:
// src/main/java/com/example/notes/MainController.java — начало addNote() и новый метод
if (title.isEmpty()) {
shake(titleField);
return;
}
private void shake(Node node) {
TranslateTransition shake = new TranslateTransition(Duration.millis(50), node);
shake.setFromX(0);
shake.setByX(8);
shake.setCycleCount(6);
shake.setAutoReverse(true);
shake.play();
}Один проход сдвигает поле на 8 пикселей вправо, setAutoReverse(true) возвращает его назад, и так 6 раз. Число повторов чётное, поэтому поле останавливается на месте. Введите в заголовок пробел и нажмите «Добавить» — поле дёрнется из стороны в сторону.
Timeline и KeyFrame
Когда нужно несколько свойств или несколько этапов, используют Timeline. Она состоит из ключевых кадров KeyFrame: момента времени и значений KeyValue, которых свойства должны достигнуть к этому моменту. Счётчик «Заметок: N» будет пульсировать при каждом изменении списка:
// src/main/java/com/example/notes/MainController.java — новый метод pulse()
private void pulse(Node node) {
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(node.scaleXProperty(), 1),
new KeyValue(node.scaleYProperty(), 1)),
new KeyFrame(Duration.millis(150),
new KeyValue(node.scaleXProperty(), 1.3),
new KeyValue(node.scaleYProperty(), 1.3)),
new KeyFrame(Duration.millis(300),
new KeyValue(node.scaleXProperty(), 1),
new KeyValue(node.scaleYProperty(), 1)));
timeline.play();
}// src/main/java/com/example/notes/MainController.java — в initialize(), после привязки countLabel
notes.addListener((ListChangeListener<Note>) change -> pulse(countLabel));За 0,15 секунды надпись увеличивается до 130 %, ещё за 0,15 — возвращается. Добавьте или удалите заметку — счётчик «подпрыгнет». У KeyFrame бывает и обработчик: new KeyFrame(Duration.seconds(1), event -> ...) вместе с setCycleCount(Animation.INDEFINITE) выполняет код раз в секунду — так делают часы и таймеры.
| Метод | Что делает |
|---|---|
play(), pause(), stop() | Запуск, пауза, остановка |
playFromStart() | Запуск с начала |
setCycleCount(n) | Сколько раз повторить; Animation.INDEFINITE — бесконечно |
setAutoReverse(true) | Каждый второй проход идёт обратно |
setOnFinished(...) | Код после окончания анимации |
Частая ошибка
Из showStatus убирают строку statusLabel.setOpacity(1). Первое сообщение появляется и гаснет, а все следующие не видны: после первой анимации прозрачность осталась 0, новый текст записывается в невидимую надпись, и переход ведёт её от 0 к 0. Ошибки в терминале нет. Анимация меняет само свойство узла и не возвращает его обратно — восстанавливайте значение сами или задавайте setFromValue.