在 JavaFX 中使用 for 循环设置时间轴

Setting up a Timeline with a for loop in JavaFX

我正在尝试为我所在的 class 编写动画排序。我的目标是将排序显示为将在 canvas I 上呈现的垂直白条已创建。

我尝试使用 时间轴和关键帧 来完成此操作,但我得到的输出是一片空白 canvas,片刻之后输出完成的和排序数组。

我只是改写代码但没有成功,而且在网上找不到任何有用的示例。我希望对 JavaFX 动画有更多经验的人可以帮助我了解更多关于如何正确设置它的信息!

请注意,在下面的代码中,使用随机方法创建的数组被注释掉了,因为使用它会使程序挂起很长时间。该代码将在合理的时间内以较小的数组输出。

import java.util.Random;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

public class Project06 extends Application
{
    final int SCALE = 16;

    final int WIDTH = 64;
    final int HEIGHT = 32;

    int[][] pixels;

    Timeline sortLoop;

    int[] myArray = {32,28,22,20,16,13,10,9,5,3};


    @Override
    public void start(Stage primaryStage) throws Exception
    {
        pixels = new int[WIDTH][HEIGHT];

        int[] myArray = new int[WIDTH];

        /*
        Random rand = new Random();
        for (int i = 0; i < WIDTH; i++)
        {
            myArray[i] = rand.nextInt((HEIGHT) + 1);
        }
        */

        Canvas display = new Canvas (WIDTH*SCALE, HEIGHT*SCALE);

        GraphicsContext gc = display.getGraphicsContext2D();

        GridPane grid = new GridPane();
        grid.add(display, 0, 0);

        primaryStage.setTitle("Sort");
        primaryStage.setScene(new Scene(grid, WIDTH*SCALE, HEIGHT*SCALE));
        primaryStage.show();

        sortLoop = new Timeline();
        sortLoop.setCycleCount(Timeline.INDEFINITE);

        // Sort array
        for (int i = 0; i < myArray.length - 1; i++)
        {        
            if (myArray[i] > myArray[i + 1])
            {
                int swap = myArray[i];
                myArray[i] = myArray[i + 1];
                myArray[i + 1] = swap;
                i = -1;
            }

            // Clear screen by zeroing out pixel array
            for (int k = 0; k < WIDTH; k++)
            {
                for (int j = 0; j < HEIGHT; j++)
                {
                    pixels[k][j] = 0;
                }
            }

            // Draw array with vertical bars (assign values to canvas array)
            for (int k = 0; k < myArray.length; k++)
            {
                for (int j = (HEIGHT - 1); j > ((HEIGHT - myArray[k]) - 1); j--)
                {
                    pixels[k][j] = 1;   
                }
            }

            KeyFrame kf = new KeyFrame(Duration.seconds(1), actionEvent -> {

                // Render canvas
                for (int k = 0; k < WIDTH; k++)
                {
                    for (int j = 0; j < HEIGHT; j++)
                    {
                        if (pixels[k][j] == 1)
                        {
                            gc.setFill(Color.WHITE);
                            gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
                        }
                        else
                        {
                            gc.setFill(Color.BLACK);
                            gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
                        }
                    }
                }

            });

            sortLoop.getKeyFrames().add(kf);
            sortLoop.play();
        }
    }

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

首先,您将所有关键帧的时间点设置为相同的值(一秒),以便它们同时发生。您需要每个关键帧都有不同的时间点,以便它们按顺序发生。

其次,您对数组进行排序,然后关键帧的事件处理程序引用该数组。由于关键帧的事件处理程序稍后执行,因此它们只能看到排序后的数组。您需要在关键帧的事件处理程序中实际操作数组。

第三,你多次播放时间轴。你只需要玩一次。此外,您将循环计数设置为 INDEFINITE:我认为您只需要为排序设置一次动画。 (如果你真的想让它重复,你需要将数组设置回它在时间线开始时的原始顺序。)

最后,您在实际排序算法的实现中遇到了一些问题。这就是当您随机填充数组时您的应用程序挂起的原因。我不会在这里解决这些问题,因为问题是关于如何执行动画。

以下代码执行逐步动画。它可能不是正确实现的排序,因此您不会看到您必然期望的动画,但它至少重绘了动画中交换的效果:

import java.util.Random;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.util.Duration;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;

public class AnimatedSort extends Application
{
    final int SCALE = 16;

    final int WIDTH = 64;
    final int HEIGHT = 32;

    int[][] pixels;

    Timeline sortLoop;

    int[] myArray = {32,28,22,20,16,13,10,9,5,3};


    @Override
    public void start(Stage primaryStage) throws Exception
    {
        pixels = new int[WIDTH][HEIGHT];

//        int[] myArray = new int[WIDTH];
//
//        
//        Random rand = new Random();
//        for (int i = 0; i < WIDTH; i++)
//        {
//            myArray[i] = rand.nextInt((HEIGHT) + 1);
//        }


        Canvas display = new Canvas (WIDTH*SCALE, HEIGHT*SCALE);

        GraphicsContext gc = display.getGraphicsContext2D();

        GridPane grid = new GridPane();
        grid.add(display, 0, 0);

        primaryStage.setTitle("Sort");
        primaryStage.setScene(new Scene(grid, WIDTH*SCALE, HEIGHT*SCALE));
        primaryStage.show();

        sortLoop = new Timeline();
//        sortLoop.setCycleCount(Timeline.INDEFINITE);

        // Sort array
        for (int index = 0; index < myArray.length - 1; index++)
        {        

            final int i = index ;

            KeyFrame kf = new KeyFrame(Duration.seconds(i+1), actionEvent -> {

                if (myArray[i] > myArray[i + 1])
                {
                    int swap = myArray[i];
                    myArray[i] = myArray[i + 1];
                    myArray[i + 1] = swap;
//                    i = -1;
                }

                // Clear screen by zeroing out pixel array
                for (int k = 0; k < WIDTH; k++)
                {
                    for (int j = 0; j < HEIGHT; j++)
                    {
                        pixels[k][j] = 0;
                    }
                }

                // Draw array with vertical bars (assign values to canvas array)
                for (int k = 0; k < myArray.length; k++)
                {
                    for (int j = (HEIGHT - 1); j > ((HEIGHT - myArray[k]) - 1); j--)
                    {
                        pixels[k][j] = 1;   
                    }
                }

                // Render canvas
                for (int k = 0; k < WIDTH; k++)
                {
                    for (int j = 0; j < HEIGHT; j++)
                    {
                        if (pixels[k][j] == 1)
                        {
                            gc.setFill(Color.WHITE);
                            gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
                        }
                        else
                        {
                            gc.setFill(Color.BLACK);
                            gc.fillRect((k*SCALE), (j*SCALE), SCALE, SCALE);
                        }
                    }
                }

            });

            sortLoop.getKeyFrames().add(kf);
        }

        sortLoop.play();
    }

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

KeyFrametime属性表示从动画开始的时间,而不是从之前添加的KeyFrame开始的时间。你需要定位。您需要将不同的时间传递给 KeyFrame 构造函数。

此外,您 "sort" 一个新数组而不是使用该字段。此外,您的 pixels 数组包含从头开始的最后一个值。

最好将交换保存到数据结构中并使用 Timeline 逐步呈现更改:

final int SCALE = 16;

final int WIDTH = 64;
final int HEIGHT = 32;

Timeline sortLoop;

int[] myArray = {32, 28, 22, 20, 16, 13, 10, 9, 5, 3};

private void renderBar(GraphicsContext gc, int barIndex, double canvasHeight, int barHeight) {
    double rectHeight = barHeight * SCALE;
    gc.fillRect(SCALE * barIndex, canvasHeight - rectHeight, SCALE, rectHeight);
}

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

    int[] myArray = this.myArray.clone(); // copy field (you may also simply delete this declaration, if you never reuse the initial array)

    class Swap {

        final int lowerIndex, value1, value2;

        public Swap(int lowerIndex, int value1, int value2) {
            this.lowerIndex = lowerIndex;
            this.value1 = value1;
            this.value2 = value2;
        }

    }

    final double canvasHeight = HEIGHT * SCALE;
    Canvas display = new Canvas(WIDTH * SCALE, canvasHeight);
    GraphicsContext gc = display.getGraphicsContext2D();

    primaryStage.setTitle("Sort");
    primaryStage.setScene(new Scene(new Group(display)));
    primaryStage.show();

    // render initial state
    gc.setFill(Color.WHITE);
    gc.fillRect(0, 0, display.getWidth(), canvasHeight);
    gc.setFill(Color.BLACK);
    for (int i = 0; i < myArray.length; i++) {
        renderBar(gc, i, canvasHeight, myArray[i]);
    }

    // Sort array & save changes
    List<Swap> swaps = new ArrayList<>();
    for (int i = 0; i < myArray.length - 1; i++) {
        if (myArray[i] > myArray[i + 1]) {
            int swap = myArray[i];
            myArray[i] = myArray[i + 1];
            myArray[i + 1] = swap;
            swaps.add(new Swap(i, myArray[i], swap));
            i = -1;
        } else {
            // no change
            swaps.add(null);
        }
    }

    sortLoop = new Timeline();
    sortLoop.setCycleCount(swaps.size());
    final Iterator<Swap> iter = swaps.iterator();
    sortLoop.getKeyFrames().add(new KeyFrame(Duration.seconds(1), evt -> {
        Swap swap = iter.next();

        if (swap != null) {
            // clear old area
            gc.setFill(Color.WHITE);
            gc.fillRect(SCALE * swap.lowerIndex, 0, SCALE * 2, canvasHeight);

            // draw new bars
            gc.setFill(Color.BLACK);
            renderBar(gc, swap.lowerIndex, canvasHeight, swap.value1);
            renderBar(gc, swap.lowerIndex+1, canvasHeight, swap.value2);
        }
    }));

    sortLoop.play();
}