Настройка временной шкалы с циклом for в JavaFX
Я пытаюсь написать анимированную сортировку для класса, в котором я учусь. Моя цель - отобразить сортировку в виде вертикальных белых полос, которые будут отображаться на созданном мной холсте.
Я пытался сделать это, используя временную шкалу и ключевые кадры, но я получаю пустой белый холст, который через несколько секунд выводит готовый и отсортированный массив.
Я прибег к тому, чтобы просто перебирать код безуспешно, и не могу найти никаких полезных примеров в Интернете. Я надеюсь, что кто-то с большим опытом работы с анимацией 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 ответа
Во-первых, вы устанавливаете момент времени для всех ваших ключевых кадров на одно и то же значение (одну секунду), чтобы они все происходили одновременно. Вам нужно, чтобы каждый ключевой кадр имел разные временные точки, поэтому они происходят последовательно.
Во-вторых, вы сортируете массив, а затем обработчики событий для ключевых кадров ссылаются на массив. Поскольку обработчики событий для ключевых кадров выполняются позже, они видят только отсортированный массив. Вам нужно фактически манипулировать массивом в обработчиках событий для ключевых кадров.
В-третьих, вы играете на временной шкале несколько раз. Вам нужно играть только один раз. Кроме того, вы устанавливаете счетчик циклов в 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);
}
}
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();
}