0

我想以这样的方式进行路径转换,即使在动画期间改变了大小,路径的行为也相对于窗口大小。此外,当动画完成时,动画元素应该使用停留在相对目标位置。

tileWidthProperty并且tileHeightProperty是我为了方便而添加的班级成员,他们只是将主窗格的大小分成八份。

这是我的代码:

public void applyMove(final Ellipse toBeMoved, final int startCol, final int startRow, final int endCol, final int endRow)
{

    Platform.runLater(() ->
    {
        final Path path = new Path();

        final MoveTo startingPoint = new MoveTo();
        final LineTo endPoint = new LineTo();

        startingPoint.xProperty().bind(tileWidthProperty.multiply(startCol).add(tileWidthProperty.divide(2)));
        startingPoint.yProperty().bind(tileHeightProperty.multiply(startRow).add(tileHeightProperty.divide(2)));

        endPoint.xProperty().bind(tileWidthProperty.multiply(endCol).add(tileWidthProperty.divide(2)));
        endPoint.yProperty().bind(tileHeightProperty.multiply(endRow).add(tileHeightProperty.divide(2)));

        path.getElements().add(startingPoint);
        path.getElements().add(endPoint);

        toBeMoved.centerXProperty().unbind();
        toBeMoved.centerYProperty().unbind();

        PathTransition transition = new PathTransition(Duration.millis(10000), path, toBeMoved);
        transition.setOrientation(PathTransition.OrientationType.NONE);
        transition.setCycleCount(1);
        transition.setAutoReverse(false);

        //bind the node at the destination.
        transition.setOnFinished(event ->
        {
            toBeMoved.centerXProperty().bind(tileWidthProperty.multiply(endCol).add(tileWidthProperty.divide(2)));
            toBeMoved.centerYProperty().bind(tileHeightProperty.multiply(endRow).add(tileHeightProperty.divide(2)));
            toBeMoved.setTranslateX(0.0);
            toBeMoved.setTranslateY(0.0);
        });

        transition.play();
    });
}

col 和 row 参数是已知为 0 <= x < 8 的整数。它们是 8*8 网格中 tile 位置的列和行。

然而,MoveTo 和 LineTo 元素的绑定似乎没有任何效果。

4

1 回答 1

2

首先,一旦Animation启动任何标准,它就不会改变参数。因此,如果有任何变化,您必须停止动画并使用新参数重新启动它。

我注意到您正在尝试绑定centerX,并且centerY在转换完成后,这是错误的:使用andPathTransition移动元素。translateXtranslateY

为了更好地调试,您实际上可以添加Path到场景图中以查看元素的去向。

Path path = new Path();
parent.getChildren().add(path);

我假设tileWidthProperty并使用tileHeightProperty以下内容绑定到实际parent大小:

tileWidthProperty.bind(parent.widthProperty().divide(8));
tileHeightProperty.bind(parent.heightProperty().divide(8));

所以我创建了一个例子来展示它的样子。

import javafx.animation.PathTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

    private Pane parent;
    private final DoubleProperty
            tileWidthProperty = new SimpleDoubleProperty(),
            tileHeightProperty = new SimpleDoubleProperty();
    private EllipseTransition ellipseTransition;

    @Override
    public void start(Stage primaryStage) {
        this.parent = new Pane();

        tileWidthProperty.bind(parent.widthProperty().divide(8));
        tileHeightProperty.bind(parent.heightProperty().divide(8));

        // create ellipse
        final Ellipse ellipse = new Ellipse(25., 25.);
        parent.getChildren().add(ellipse);

        // show the stage
        primaryStage.setScene(new Scene(parent, 800, 800));
        primaryStage.show();

        // create listeners that listen to size changes
        InvalidationListener sizeChangeListener = l -> {
            if(ellipseTransition != null) {
                // refreshAndStart returns null if transition is completed
                // let's call it delayed cleanup :)
                ellipseTransition = ellipseTransition.refreshAndStart();
            } else {
                System.out.println("ellipseTransition cleaned up!");
            }
        };
        // add listeners to the corresponding properties
        tileWidthProperty.addListener(sizeChangeListener);
        tileHeightProperty.addListener(sizeChangeListener);

        // move ellipse 0,0 -> 7,7
        applyMove(ellipse, 0, 0, 7, 7, Duration.millis(5000));

        // interrupt transition at the middle, just for fun
        /*new Thread(() -> {
            try {
                Thread.sleep(2500);
            } catch (InterruptedException e) {
                return;
            }
            applyMove(ellipse, 2, 3, 4, 5, Duration.millis(1000));
        }).start();*/
    }


    public void applyMove(final Ellipse toBeMoved, final int startCol, final int startRow, final int endCol, final int endRow, final Duration duration) {
        Platform.runLater(() -> {
            // if transition is still up, then stop it
            if(ellipseTransition != null) {
                ellipseTransition.finish();
            }
            // and create a new one
            ellipseTransition = new EllipseTransition(toBeMoved, startCol, startRow, endCol, endRow, Duration.ZERO, duration, 0);
            // then start it
            ellipseTransition.start();
        });
    }

    // I decided to write separate class for the transition to make it more convenient
    private class EllipseTransition {
        // these variables are the same you used in your code
        private final Path path;
        private final Ellipse ellipse; // this one was "toBeMoved"
        private final int startCol, startRow, endCol, endRow;
        private final Duration duration;
        private final PathTransition transition;

        // if we change parent size in the middle of the transition, this will give the new transition information about where we were.
        private Duration startTime;

        // we call System.currentTimeMillis() when we start
        private long startTimestamp;

        // if true, transition would not start again
        private boolean finished;

        public EllipseTransition(Ellipse ellipse, int startCol, int startRow, int endCol, int endRow, Duration startTime, Duration duration, long realStartTimestamp) {
            this.path = new Path();
            this.ellipse = ellipse;
            this.startCol = startCol;
            this.startRow = startRow;
            this.endCol = endCol;
            this.endRow = endRow;
            this.startTime = startTime;
            this.duration = duration;
            this.transition = new PathTransition();

            // applyMove passes 0, because we don't know our start time yet
            this.startTimestamp = realStartTimestamp;
        }

        // this is called right before starting the transition
        private void init() {
            // show path for debugging
            parent.getChildren().add(path);

            // binding values here is useless, you can compute everything in old-fashioned way for better readability
            final MoveTo startingPoint = new MoveTo();
            startingPoint.setX(tileWidthProperty.get() * startCol + tileWidthProperty.get() / 2.);
            startingPoint.setY(tileHeightProperty.get() * startRow + tileHeightProperty.get() / 2.);

            final LineTo endPoint = new LineTo();
            endPoint.setX(tileWidthProperty.get() * endCol + tileWidthProperty.get() / 2);
            endPoint.setY(tileHeightProperty.get() * endRow + tileHeightProperty.get() / 2);

            path.getElements().clear(); // clear paths from the last time
            path.getElements().add(startingPoint);
            path.getElements().add(endPoint);

            ellipse.translateXProperty().unbind();
            ellipse.translateYProperty().unbind();

            transition.setNode(ellipse);
            transition.setDuration(duration);
            transition.setPath(path);
            transition.setOrientation(PathTransition.OrientationType.NONE);
            transition.setCycleCount(1);
            transition.setAutoReverse(false);

            transition.setOnFinished(event ->
            {
                // bind ellipse to the new location
                ellipse.translateXProperty().bind(tileWidthProperty.multiply(endCol).add(tileWidthProperty.divide(2)));
                ellipse.translateYProperty().bind(tileHeightProperty.multiply(endRow).add(tileHeightProperty.divide(2)));

                // cleanup
                stop();

                // mark as finished
                finished = true;
            });
        }

        // stops the transition
        private void stop() {
            // remove debug path ( added it in init() )
            parent.getChildren().remove(path);
            transition.stop();
        }

        // starts the transition
        public void start() {
            if(finished) {
                return;
            }

            init(); // initialize parameters

            // start from the place where previous we stopped last time
            // if we did not stop anywhere, then we start from beginning (applyMove passes Duration.ZERO)
            this.transition.playFrom(startTime);

            // applyMove passes 0, as it doesn't know when transition will start
            // but now we know
            if(this.startTimestamp == 0) {
                this.startTimestamp = System.currentTimeMillis();
            }
        }

        // stops the transition
        public void finish() {
            stop();
            finished = true;
        }

        // stops and refreshes the transition.
        // that will continue transition but for new values
        private void refresh() {
            // stop the transition
            stop();

            // determine how much time we spend after transition has started
            long currentDuration = System.currentTimeMillis() - startTimestamp;

            // update startTime to the current time.
            // when we call start() next time, transition will continue, but with new parameters
            this.startTime = Duration.millis(currentDuration);
        }

        // this method is called from change listener
        public EllipseTransition refreshAndStart() {
            if(finished) {
                // return null to the listener
                // we want to cleanup completely
                return null;
            }
            // refresh new values and start
            refresh(); start();
            return this;
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
于 2018-07-02T23:12:25.200 回答