Настройка временной шкалы с циклом for в JavaFX - PullRequest
0 голосов
/ 12 мая 2018

Я пытаюсь написать анимированную сортировку для класса, в котором я учусь. Моя цель - отобразить сортировку в виде вертикальных белых полос, которые будут отображаться на созданном мною холсте.

Я пытался сделать это, используя Временную шкалу и ключевые кадры , но я получаю пустой белый холст, который через несколько секунд выводит готовый и отсортированный массив.

Я прибегаю к тому, чтобы просто безрезультатно перебирать код, и не могу найти никаких полезных примеров в Интернете. Я надеюсь, что кто-то с большим опытом работы с анимацией JavaFX поможет мне узнать больше о том, как правильно настроить это!

Обратите внимание, что в приведенном ниже коде массив, созданный методом random, закомментирован, поскольку при использовании он приводит к зависанию программы на длительное время. Код будет выведен в разумные сроки с меньшим массивом.

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);
    }
}

Ответы [ 2 ]

0 голосов
/ 12 мая 2018

Свойство time в KeyFrame обозначает время с начала анимации, а не время с ранее добавленного KeyFrame. Вам нужно позиционировать. Вам нужно передать разное время конструктору KeyFrame.

Кроме того, вы "сортируете" новый массив вместо использования поля. Также ваш массив 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();
}
0 голосов
/ 12 мая 2018

Во-первых, вы устанавливаете момент времени для всех ваших ключевых кадров на одно и то же значение (одну секунду), чтобы они все происходили одновременно. Вам нужно, чтобы каждый ключевой кадр имел разные временные точки, поэтому они происходят последовательно.

Во-вторых, вы сортируете массив, а затем обработчики событий для ключевых кадров ссылаются на массив. Поскольку обработчики событий для ключевых кадров выполняются позже, они видят только отсортированный массив. Вы должны фактически манипулировать массивом в обработчиках событий для ключевых кадров.

В-третьих, вы играете на временной шкале несколько раз. Вам нужно играть в нее только один раз. Кроме того, вы устанавливаете счетчик циклов на 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);
    }
}
...