Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

JavaFX и AnimationTimer

Anna | 2.06.2014 | нет комментариев
Вариант использования увлекательного класса в JavaFX-приложении — предотвращение «заморозки» окна во время долгого процесса. Заодно чуть-чуть об особенностях прорисовки сцен в JavaFX.

Представим, возникло желание (и/или надобность) настрогать десктопное приложение на Java с отображением долгого процесса. Ну, немного ли, есть длинный цикл вычислений, и охота посмотреть итог всякой итерации, правда бы мельком. Удостовериться, что процесс идет в необходимом направлении, может, гистограмму какую возвести.

Представим, интерфейс решено делать на JavaFX. Как-нибудь так:

файл Main.java

package romeogolf.example1;

import java.util.ArrayList;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font;
import javafx.stage.Stage;

public class Main extends Application {
	// массив знакомест
	ArrayList<Label> aLabels = new ArrayList<Label>();
	// число знакомест
	final private int digitCount = 5; // цифр будет   1
	// лимитация процесса
	final private int maxCount = 12345;

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

	@Override
	public void start(Stage primaryStage) {
		primaryStage.setTitle("Example");
		VBox vbox = new VBox();
		vbox.setAlignment(Pos.CENTER);
		vbox.setSpacing(20);
		HBox hbox = new HBox();
		hbox.setAlignment(Pos.CENTER);
		hbox.setSpacing(20);
		for(int i = 0; i <= digitCount; i  ){
			aLabels.add(new Label("X"));
			aLabels.get(i).setFont(new Font("Arial", 30));
			aLabels.get(i).setStyle("-fx-padding: 5;"
					  "-fx-border-color: rgb(49, 89, 23);"
					  "-fx-border-radius: 5;");
			hbox.getChildren().add(aLabels.get(i));
		}
		// кнопка, запускающая процесс
		Button button = new Button("Start");
		button.setOnAction(new EventHandler<ActionEvent>() {
			@Override public void handle(ActionEvent e) {
				longProcess();
			}
		});

		vbox.getChildren().add(hbox);
		vbox.getChildren().add(button);

		primaryStage.setScene(new Scene(vbox, 55 * (digitCount   1), 100));
		primaryStage.show();
	}

	// заготовка для процесса
	private void longProcess(){
		// заготовка
	}
}

Окошко

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

	private void longProcess(){
		int digit;
		for(int i = 0; i <= this.maxCount; i  ){
			for(int j = 0; j <= digitCount; j  ){
				digit = (int) (i % (Math.pow(10.0, (double)(j   1))));
				digit = (int) (digit / (Math.pow(10.0, (double)j)));
				this.aLabels.get(digitCount - j).setText(Integer.toString(digit));
			}
		}
	}

Тут некое число i бесхитростно разбирается на цифры и выводится в знакоместа. Запустим это творение безрассудного программистского гения и нажмем исключительную кнопку. Дюже стремительно получим итог, примерно мигом:

Результат

Дюже радует, что так стремительно. Но незадача в том, что задача не в этом. Целью-то было посмотреть каждый процесс, правда бы и медлительно. Вот бы как-то притормозить всякий проход цикла и дать команду отобразить результат… Что-то типа repaint() в Swing, либо какие-нибудь refresh, update, может, дельфовоеAppllication.ProcessMessages.

Не предусмотрено. В Swing при вызове перерисовки она здесь же и осуществляется. В JavaFX требование перерисовки отражается в графе сцены, а сцена перерисовывается, когда придет время, когда тикнет pulse. Pulse — это событие, которое информирует графу сцены, что пришла пора рисоваться. Оно тикает не Почаще 60 fps, если запущена анимация, и по необходимости, если в графе сцены случились метаморфозы. Это так называемый Retained Mode, что зачастую переводят, как «абстракный режим», подразумевая, что данный подход повышает ярус абстракции, в различие от Immediate Mode — непосредственного режима.

Статья Retained Mode Versus Immediate Mode описывает разницу между подходами на примере Direct2D и WPF. Переведу конечный абзац:

Retained Mode API проще в применении, потому что это API исполняет за вас огромнее работы: инициализацию, поддержание состояния, чистку. Но с иной стороны, такой подход нередко менее эластичен, так как это API навязывает свою собственную модель сцены. Помимо того, Retained Mode API может иметь повышенные требования к памяти, так как это нужно для обеспечения модели сцены всеобщего назначения. Применяя Immediate Mode API вы можете осуществить целевую оптимизацию.

Что же делать? Испробуем так. Перенесем счетчик из переменной цикла, сделаем ее полем класса (возможен, прямо перед новым способом):

	private int counter = 0;

Сделаем новейший способ чуть напротив (дозволено легко изменить ветхий, но если оставим, то будет проще переключаться между ветхим и новым вариантами). Уберем внешний цикл вовсе:

	private void longProcess2(){
		int digit;
		// операции для отображения процесса
		for(int j = 0; j <= digitCount; j  ){
			digit = (int) (counter % (Math.pow(10.0, (double)(j   1))));
			digit = (int) (digit / (Math.pow(10.0, (double)j)));
			this.aLabels.get(digitCount - j).setText(Integer.toString(digit));
		}
		// *******
		// собственно процесс, подлежащий отображению:
		counter  ;
		// *******
	}

Добавим в конце класса Main такой код:

    protected AnimationTimer at = new AnimationTimer(){
        @Override
        public void handle(long now) {
        	longProcess2();
        }
    };

Для запуска процесса в обработке кнопки взамен

				longProcess();

поставим

				at.start();

А для своевременной остановки вставим в сам способ longProcess2(), реализующий процесс, такие строчки (в самое предисловие):

		if(counter > maxCount){
			at.stop();
			return;
		}

Сейчас о том, для чего это все было необходимо. В итоге создается at — экземпляр класса AnimationTimer. Его способ handle() вызывается всякий раз при перерисовке окна приложения. То есть, вызвав longProcess2(), мы скомандовали перерисовать знакоместа цифр. Вызвана надобность перерисовки сцены. Вызывается handle(), и вновь запускает longProcess2(). И так до тех пор, пока в самом способе longProcess2() не возникнет условие остановки для at.stop().

Сейчас при нажатии кнопки в «окошках» будут мелькать циферки. Мелькать с различной скоростью, в зависимости от цены разряда.

Процесс

Если, к примеру, увеличить digitCount до 7, а maxCount — до 98765432, разница между вариантами без таймера и с таймером станет значительно невидимее. В исходном варианте придется созерцать крестики в знакоместах до тех пор, пока процесс не будет закончен, и даже закрыть окно традиционным методом («крестиком» в правом верхнем углу) не получится. В варианте с таймером Удивительно отображается процесс, окошко дозволено таскать за заголовок и закрыть в всякий момент, но сам процесс растягивется невообразимо. Здесь уж вопрос, что огромнее необходимо — скорость либо отображение.

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

		counter  ;

что-то типа

		for(int skip = 0; skip < 100000; skip  ){
			if(counter >= maxCount){
				break;
			}
			counter  ;
		}

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

Дозволено было бы еще добавить обнуление счетчика перед всяким запуском таймера, недопустимость вторичного запуска таймера при теснее работающем (а это допустимо!) и еще какие-нибудь пригодные свистелки, но для демонстрации примера это перебор.

Немножко об AnimationTimer: некто Mike в своем блоге написал о нем дюже недурную статью Using the JavaFX AnimationTimer.

Майк считает, что было не особенно отличной идеей так назвать данный класс. чай его дозволено применять вдалеке не только для анимации: для измерения fps-rate, для выявления коллизий, для подсчета шагов моделирования, в качестве основного цикла в играх и т. д. Лично он Почаще каждого видит использования AnimationTimer, вообще не имеющие отношения к анимации. AnimationTimer дает Исключительно примитивную, но дюже эластичную и пригодную фишку. Таймер разрешает определить способ, тот, что будет вызываться для всякого кадра. Что данный способ будет делать, не только не ограничено, но, как упомянуто выше, может не иметь ничего всеобщего с анимацией. Исключительное требование — данный способ должен быть довольно стремительным, напротив он легко станет тесным местом в системе.

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

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB