JavaFx对具有形状的路径进行动画处理

时间:2018-11-20 20:05:22

标签: javafx path graphicscontext

我正在做一个项目,该项目具有用直线为路径设置动画的功能。因此,当我按下按钮时,一条线穿过画布中的所有圆圈。但我发现我的路线和笔触线略有不同。它不会通过圈子。请恳请指教,谢谢。 这是我的代码:

import java.util.ArrayList;
import java.util.Random;

import javafx.animation.Animation;
import javafx.animation.PathTransition;
import javafx.animation.PathTransition.OrientationType;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class PathVisualization extends Application {

private static double SCENE_WIDTH = 1000;
private static double SCENE_HEIGHT = 500;

private Canvas canvas;
private GraphicsContext gc;
private Button clear; 
private Animation animation;
private ArrayList<Circle> num;

 @Override
public void start(Stage primaryStage) throws Exception {

    BorderPane bp = new BorderPane();
    Pane root = new Pane();
    root.setMaxSize(800,500);
   // path = new Path();
    canvas = new Canvas(SCENE_WIDTH,SCENE_HEIGHT);

    TextField no = new TextField();
    Button add = new Button("Add Circle");
    Button playAnimation = new Button("Play");
    clear = new Button("Clear");
    Button pause = new Button("Pause");

    VBox control = new VBox();
    control.getChildren().add(no);
    control.getChildren().add(add);
    control.getChildren().add(playAnimation);
    control.getChildren().add(clear);
    control.getChildren().add(pause);


    root.getChildren().add(canvas);
    bp.setCenter(root);
    bp.setLeft(control);

    num = new ArrayList<Circle>();

    clear.setOnMouseClicked(event->{
        num.clear();
        gc.clearRect(0, 0, gc.getCanvas().getWidth(), gc.getCanvas().getHeight());
        root.getChildren().clear();
        root.getChildren().add(canvas);
    });

    add.setOnMouseClicked(event->{
        int cirNum = Integer.parseInt(no.getText());
        Random rand = new Random();
        for (int j=0;j<cirNum;j++) {
            int x = rand.nextInt((int)root.getMaxWidth()-1);
            int y = rand.nextInt((int)root.getMaxHeight()-1);
            Circle circle = new Circle(x,y,10);
            num.add(circle);
            root.getChildren().add(circle);
        }

        //Heuristic Change
        Path path = new Path();
        path.setStroke(Color.RED);
        path.getElements().add(new MoveTo(num.get(0).getCenterX(),num.get(0).getCenterY()));
        path.setStrokeWidth(1);

        for (int i=0;i<num.size();i++) {
            try {
                path.getElements().addAll(new LineTo(num.get(i).getCenterX(),num.get(i).getCenterY()));
            }catch(IndexOutOfBoundsException e) {
                path.getElements().addAll(new LineTo(num.get(0).getCenterX(),num.get(0).getCenterY()));
            }
        }


        root.getChildren().addAll(path);

        //create animation
        animation = createPathAnimation(path, Duration.seconds(num.size()/4));

    });

     playAnimation.setOnMouseClicked(e ->{
          animation.play();
    });

     pause.setOnMouseClicked(e->{
         animation.pause();
     });


    primaryStage.setScene(new Scene(bp, SCENE_WIDTH, SCENE_HEIGHT));
    primaryStage.show();

}



private Animation createPathAnimation(Path path, Duration duration) {

    gc = canvas.getGraphicsContext2D();

    // move a node along a path. we want its position
    Circle pen = new Circle(0,0,0);
    // create path transition
    PathTransition pathTransition = new PathTransition(duration, path, pen);
    pathTransition.currentTimeProperty().addListener( new ChangeListener<Duration>() {

      Location oldLocation = null;

        /**
         * Draw a line from the old location to the new location
         */
        @Override
        public void changed(ObservableValue<? extends Duration> observable, Duration oldValue, Duration newValue) {

            // skip starting at 0/0
            if( oldValue == Duration.ZERO)
                return;

            // get current location

            double x = pen.getTranslateX();
            double y = pen.getTranslateY();



         // initialize the location
            if( oldLocation == null) {
                oldLocation = new Location();
                oldLocation.x = x;
                oldLocation.y = y;
                return;
            }

            // draw line
            gc.setStroke(Color.GREENYELLOW);

            gc.setLineWidth(5);

            gc.strokeLine(oldLocation.x, oldLocation.y, x, y);


            // update old location with current one
            oldLocation.x = x;
            oldLocation.y = y;

        }
    });

    pathTransition.setOrientation(OrientationType.ORTHOGONAL_TO_TANGENT);  
    return pathTransition;
}

public static class Location {
    double x;
    double y;
}

public static void main(String[] args) {
    launch(args);
}
}

这是问题。在某些拐角处,描边线不会穿过圆弧,并且会进行怪异的切割。

enter image description here

1 个答案:

答案 0 :(得分:0)

我最初无法复制它,但是经过更多尝试后,我设法复制了它。看来这是由于延迟造成的。

据我所知(我不是动画专家),动画(包括PathTransition)在内部使用计时器,每个刻度通常等效于JavaFX场景图脉冲持续时间。这意味着动画每秒最多更新60次。当发生脉冲时,PathTransition根据计时器上经过的时间来计算新的转换值。因此,这里发生的是,计算出的转换值在两个特定脉冲之间相距很远。通常,您尝试在JavaFX Application Thread上执行的操作越多,发生这种情况的可能性就越大。

如果您尝试根据动画绘制描线,则无法避免这种延迟。但是,通过将其分解为多个动画,可以避免绘制的路径出现拐角的问题。每个动画都将沿一条直线移动,并且保证您的每条描边线都从起点开始,到终点为止。

我试过了,它有效:

// Heuristic Change
// Path path = new Path();
// path.setStroke(Color.RED);
// path.getElements().add(new MoveTo(num.get(0).getCenterX(), num.get(0).getCenterY()));
// path.setStrokeWidth(1);

// for (int i = 0; i < num.size(); i++) {
// try {
// path.getElements().addAll(new LineTo(num.get(i).getCenterX(), num.get(i).getCenterY()));
// }
// catch (IndexOutOfBoundsException e) {
// path.getElements().addAll(new LineTo(num.get(0).getCenterX(), num.get(0).getCenterY()));
// }
// }

List<Path> paths = new ArrayList<>();

for (int i = 0; i < num.size() - 1; i++) {
    Circle current = num.get(i);
    Circle next = num.get(i + 1);

    Path path = new Path();
    path.setStroke(Color.RED);
    path.getElements().addAll(new MoveTo(current.getCenterX(), current.getCenterY()),
            new LineTo(next.getCenterX(), next.getCenterY()));
    path.setStrokeWidth(1);

    paths.add(path);
}

// root.getChildren().addAll(path);

root.getChildren().addAll(paths);

Circle pen = new Circle();

// create animation
// animation = createPathAnimation(path, Duration.seconds(num.size()/4));
animation = createPathAnimation(paths, Duration.millis(200), pen);

然后...

private Animation createPathAnimation(List<Path> paths, Duration duration, Circle pen) {
    SequentialTransition seq = new SequentialTransition();

    // ...

    for (Path path : paths) {
        // The same PathTransition stuff you had

        seq.getChildren().add(pathTransition);
    }

    return seq;
}

唯一改变的是速度在完整路径的每个段上都改变了。如果要保持该常数不变,则必须应用毕达哥拉斯定理来找出距离,并相应地调整持续时间。