首页 > 解决方案 > JavaFX:基于时间函数的动画

问题描述

我没有 MWE,因为我不知道如何开始。我想我的问题主要是关于哪些是最适合这项工作的工具。

我有一个相当于 a 的对象Function<Double, VectorXYZ>,它输出给定时间的对象的位置。它处理自己的插值。我想知道是否有一种方法可以处理 a 的功能Timeline而不必使用KeyFrames。我希望既能向前播放,又能使用Slider.

我想有一个DoubleProperty以某种方式链接到的Timeline,与更新包含对象Listener的翻译属性的a 相关联。Group但我不知道该怎么做。

谢谢你的帮助!

标签: animationjavafx

解决方案


所以是的,这是一个非常模糊的问题,但正如@James_D 所说,AnimationTimer这是我正在寻找的工具。基本上我正在寻找对动画循环的低级访问,这似乎就是它。这是沿着场景中的某些路径的对象的 MWE。它将系统时间与场景时间(存储为 DoubleProperty)分开,以便可以暂停和重新启动,也可以通过 Slider 设置时间。

import com.google.common.base.Function;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.control.Slider;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class AnimationTestApp extends Application {

    private static final double durationSeconds = 5;

    private static final double screenWidthMeters = 10;
    private static final double screenHeightMeters = 10;
    private static final double pixelsPerMeter = 50;

    private static final double squareSizeMeters = 0.5;

    private static final double screenWidthPixels = pixelsPerMeter * screenWidthMeters;
    private static final double screenHeightPixels = pixelsPerMeter * screenHeightMeters;

    private static final double squareSizePixels = pixelsPerMeter * squareSizeMeters;

    private static final double originXPixels = screenWidthPixels/2;
    private static final double originYPixels = screenHeightPixels/2;

    private final Rectangle square = new Rectangle(squareSizePixels, squareSizePixels, Color.RED);

    private long lastTime = -1;

    private boolean isStopped = true;

    private double t = 0;

    private DoubleProperty timeProperty;
    private DoubleProperty timeProperty() {
        if (timeProperty == null) {
            timeProperty = new SimpleDoubleProperty();
            timeProperty.addListener((obs, ov, nv) -> {updateScene();});
        }
        return timeProperty;
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
            final SubScene subscene = new SubScene(new Group(square), screenWidthPixels, screenHeightPixels);
            Slider timeSlider = new Slider(0, 5, 1);
            timeSlider.valueProperty().bindBidirectional(timeProperty());

            VBox root = new VBox(timeSlider, subscene);
            Scene scene = new Scene(root);
            primaryStage.setScene(scene);
            primaryStage.show();

            AnimationTimer animationTimer = buildTimer();
            handleKeyboard(scene, animationTimer);
    }

    private AnimationTimer buildTimer() {
        AnimationTimer animationTimer = new AnimationTimer() {
            @Override
            public void handle(long now) {
                double elapsedNS = now - lastTime;
                double dt = elapsedNS * 1E-9;
                if (timeProperty().get() + dt > durationSeconds) {
                    stop();
                }
                timeProperty().set(timeProperty().get() + dt);
                updateScene();//timeProperty.get());
                lastTime = now;
            }

            @Override
            public void start() {
                lastTime = System.nanoTime();
                isStopped = false;
                if (timeProperty().get() > durationSeconds) {
                    timeProperty().set(0);
                }
                super.start();
            }

            @Override
            public void stop() {
                isStopped = true;
                super.stop();
            }
        };
        return animationTimer;
    }

    private void updateScene() {
        double t = timeProperty().get();
        Point2D point = positionFunction().apply(t);

        double xPixels = originXPixels + point.getX() * pixelsPerMeter;
        double yPixels = originYPixels + point.getY() * pixelsPerMeter;

        square.setTranslateX(xPixels);
        square.setTranslateY(yPixels);
    }

    private Function<Double, Point2D> positionFunction() {
        double radius = 3;
        double period = 2;
        return (t) -> new Point2D(radius * Math.sin(2*Math.PI*t/period), radius * Math.cos(2*Math.PI*t/period));
    }

    private void handleKeyboard(Scene scene, AnimationTimer timer) {
        scene.setOnKeyPressed((ke) -> {
            if (ke.getCode().equals(KeyCode.SPACE)) {
                if (isStopped) {timer.start();}
                else {timer.stop();}
            }
        });
    }   
}

推荐阅读