====== Wątki - animacja w JavaFx ====== ==== Opis ==== Celem jest realizacja przykładu z kulkami za pomocą biblioteki JavaFX. 1. Aplikacja ma składać się z dwóch komponentów *Modelu zawierającego kulki *Widoku - obiektu ''javafx.scene.layout.Pane'' wraz z elementami potomnymi 2. Model jest animowany przez wątek. Okresowo, np. co 10ms przesuwa kulki. Zapewniamy wzajemne wykluczanie za pomocą semafora lub umieszczając wszelkie operacje na kulkach w bloku synchronicznym ''synchronized(balls){}'' 3. Widok jest animowany przez wbudowany watek JavaFX. Realizowane jest to za pomocą obiektu klasy ''AnimationTimer''. W metodzie, którą nazwiemy ''updateView()'': * usunięte zostaną wszystkie węzły potomne widoku (czyli kulki z poprzedniej iteracji) * zachowując wzajemne wykluczanie odczytane zostaną położenia i kolory kulek * na ich podstawie zostaną utworzone węzły JavaFx w odpowiednim miejscu ==== Uwagi techniczne ==== *JavaFX nie jest częścią standardowej biblioteki. Musisz wygenerować projekt typu JavaFX korzystający z Mavena lub Gradle. Polecam Mavena. *JavaFX może definiować układ komponentów w XML. Nie korzystamy z tego. Po wygenerowaniu wyrzuć plik *.fxml z zasobów oraz usuń ''HelloControler'' *Możesz zbudować układ GUI bezpośrednio w kodzie - wraz z menu, komponentem Toolbar, polem tekstowym pełniącym rolę komponentu statusbar, itp. *Drzewo węzłów JavaFX maożna uaktualniać wyłącznie w wątku JavaFX. W przeciwnym przypadku generowane będą wyjątki. *Aby uaktualnić pewne własności komponentów - wyłączane przyciski, uaktualnienie rozmiarów - musisz połączyć ze sobą własności ''property'', np. child.widthProperty().bind(parent.widthProperty()); // gdzieś w kodzie SimpleBooleanProperty isRunning = new SimpleBooleanProperty(false); // gdzieś indziej button3.disableProperty().bind(somewhereInTheCode.isRunning); ==== Przykład ==== {{ :pz1:rectangles.png?direct&400 |}} === Klasa pomocnicza do obliczania FPS === import java.util.ArrayDeque; public class MovingAverage { int windowSize; ArrayDeque values; public MovingAverage(int windowSize){ this.windowSize=windowSize; values =new ArrayDeque<>(windowSize); } public void add(long value){ while(values.size()>=windowSize) values.poll(); values.offer(value); } public double getMean(){ if(values.isEmpty())return 0; double mean = values.stream().mapToDouble(a->a).average().getAsDouble(); return mean; } } === Aplikacja z widokiem i wątkiem uaktualniającym widok === import java.util.Locale; import java.util.Random; import javafx.animation.AnimationTimer; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Stage; public class RandomRectangles extends Application { private final Random random = new Random(); static final int N = 100; static final int SIZE=20; Group group = new Group(); Pane root = new Pane(); MovingAverage mave = new MovingAverage(300); void updateView(){ // usuń wszystkie węzły potomne grupy group.getChildren().clear(); int w = (int)(root.getWidth()/SIZE); int h = (int)(root.getHeight()/SIZE); // wygeneruj nowe węzły na podstawie modelu for(int i=0;i0)fps = 1/diff; } void addFPS(){ Text text = new Text(); text.setFont(new Font(40)); text.setFill(Color.WHITE); text.setText(String.format(Locale.US,"%.2f FPS",fps)); text.setX(10); text.setY(50); group.getChildren().add(text); } @Override public void handle(long now) { if(lastPaint>0)mave.add(now-lastPaint); lastPaint=now; counter++; updateView(); if(counter%50==0)updateFps(); addFPS(); } }.start(); } public static void main(String[] args) { launch(args); } }