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

Klasa pomocnicza do obliczania FPS

import java.util.ArrayDeque;
 
public class MovingAverage {
    int windowSize;
    ArrayDeque<Long> 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;i<N;i++){
            int x=random.nextInt(w)*SIZE;
            int y=random.nextInt(h)*SIZE;
            Rectangle r = new Rectangle(x,y,SIZE,SIZE);
            var color = Color.color(random.nextDouble(), random.nextDouble(),random.nextDouble());
            r.setFill(color);
            group.getChildren().add(r);
        }
 
    }
 
 
 
    @Override
    public void start(final Stage primaryStage) {
        root.getChildren().add(group);
        final Scene scene = new Scene(root, 800, 600, Color.BLACK);
        root.setOpacity(1);
        updateView();
        primaryStage.setScene(scene);
        primaryStage.show();
 
        new AnimationTimer() {
            long lastPaint=-1;
            double fps=0;
            int counter=0;
            MovingAverage mave = new MovingAverage(300);
 
            void updateFps(){
                double diff = mave.getMean();
                diff/=1e9;
                if(diff>0)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);
    }
 
}
pz1/animacja-zderzenia-sprezyste-javafx.txt · Last modified: 2022/11/25 23:56 by pszwed
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0