Bases de Java FX 8 : Dessinez c'est gagné.
Ceci est une nouveauté de Java 8. Il est donc impératif que vous ayez un JDK 1.8 d'installé.
Ce TP et les suivants sont basés sur plusieurs sources. Si vous êtes curieux, lisez les :
Pour tous les exercices de ce TP, on vous demandera de créer un projet Java simple (comme pour les 2 premiers TP). Nous ne créerons pas de projets directement Java FX ici.
Créez donc :
com.mco
Copiez/coller ensuite cette classe Main :
package com.mco; import javafx.application.Application; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } public void start(Stage theStage) { theStage.setTitle("Hello, World!"); theStage.show(); } }
Exécutez le code. Vous devriez voir apparaître une fenêtre avec "Hello World!" comme titre.
Particularité du code :
Main
hérite de la classe Application
définie dans la bibliothèque javafx.Application
définit la méthode launch
(nous ne l'avons pas écrite) qui prépare notre application javafx. start
(que nous avons écrite) est exécutée (par la méthode launch
) avec la fenêtre principale (de classe Stage) comme paramètre .
Cette forme d'écriture de code consistant à écrire des méthodes à l'intérieur de classes prédéfinies (que l'on sous-classe) est classique lorsque l'on utilise des frameworks web ou graphique. Il y a en effet beaucoup de choses à préparer pour créer une fenêtre et qui sont effectuées dans la classe mère (ici Application
).
Le Stage contient un scene graph contenant tout ce qui est représenté. Ce scene graph est un arbre dont :
Ici nous allons :
package com.mco; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.paint.Color; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } public void start(Stage theStage) { theStage.setTitle("Hello, World!"); //mise en place du Stage : création du scene graph et du noeud racine. Group root = new Group(); //ce ne sera pas une feuille, donc de classe Group. Scene theScene = new Scene(root); //création du scene graph theStage.setScene(theScene); //on le place dans le stage //Création d'un text Text text = new Text(); text.setFont(Font.getDefault()); text.setFill(Color.RED); text.setText("Hello World aussi !"); text.setX(10); text.setY(50); //on l'ajoute à notre scene graph via son père, ici le noeud root root.getChildren().add(text); theStage.show(); } }
Spécificité du code :
Essayez d'autres couleurs et modifiez la position du texte.
Les polices de caractères installées changent d'un ordinateur à l'autre. Pour les connaitre, on peut afficher à l'écran le résultat de Font.getFamilies() :
for (String fontName: Font.getFamilies()){ System.out.println(fontName); }
Pour créer une nouvelle police on peut alors créer une nouvelle police (comic sans MS
est installée chez moi) :
Font comic = Font.font("Comic sans MS", 100);
Puis l'affecter à notre variable text
:
text.setFont(comic);
Choisissez une police installée sur votre ordinateur et essayez là avec des tailles différentes.
Tout comme le Text, un Canvas est une feuille du scene graph. Son but est de dessiner (des boites, traits ou des images). Les possibilités sont nombreuses, voir par exemple la documentation oracle.
Prenons nous pour Malevitch :
package com.mco; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } public void start(Stage theStage) { theStage.setTitle("Malevitch"); //mise en place du Stage : création du scene graph et du noeud racine. Group root = new Group(); //ce ne sera pas une feuille, donc de classe Group. Scene theScene = new Scene(root); //création du scene graph theStage.setScene(theScene); //on le place dans le stage //Création d'un canvas de taille 512x512 pixels Canvas canvas = new Canvas(512, 512); GraphicsContext gc = canvas.getGraphicsContext2D(); //là où on va dessiner //un carré blanc sur fond blanc gc.setFill(Color.WHITE); gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); gc.setFill(Color.GHOSTWHITE); gc.fillRect(100, 100, 300, 300); //on l'ajoute à notre scene graph via son père, ici le noeud root root.getChildren().add(canvas); theStage.show(); } }
Dans un canvas on change des pixels de couleurs via un graphicsContext
Dessinez quatre ronds comme cette figure. Pour cela :
Il y a plusieurs manières de gérer le temps en Java (voir ici par exemple), nous allons utiliser un timer qui s'exécutera toutes les 1/60 secondes.
Pour cela on utilise une classe Loop
qui hérite de la classe AnimationTimer.
La classe AnimationTimer définit une méthode start et stop pour démarrer et arrêter le timer. Nous n'avons qu'à écrire une méthode handle qui sera exécutée environ 60 fois par seconde.
Le code suivant Crée un objet de classe Loop qui hérite de AnimationTimer. De là, après l'utilisation de sa méthode start, la méthode handle est exécutée toutes les 1/60 seconde avec comme paramètre le temps mesuré en nanosecondes.
package com.mco; import javafx.application.Application; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Le temps"); Loop loop = new Loop(); loop.start(); primaryStage.show(); } }
package com.mco; import javafx.animation.AnimationTimer; public class Loop extends AnimationTimer { @Override public void handle(long now) { System.out.println(now); } }
now
n'est pas forcément égal à 0, ni même positif. La seule certitude est a différence entre deux appels successifs est égale au nombre de nanosecondes entre ces deux appels. C'est pourquoi on utilise quasiment toujours le delta entre 2 appels (comme vous le ferez tout de suite).
handle
(pour cela on pourra créer un attribut stockant le temps courant) : vérifiez que le temps écoulé est quasi-constant et qu'il correspond à 1/60 secondes.Scene theScene = new Scene(root, 100, 200);
Le delta de temps (mesuré ici en nanosecondes) entre deux exécution de handle nous permet par exemple de faire bouger de façon réaliste un objet.
Le code suivant fait bouger un carré de gauche à droite.
Attention à l'initialisation pour le delta, car on ne connait pas la première valeur de now
. Pour ne pas avoir de valeur aberrante, on initialise previousTime
à la construction de l'objet avec System.nanoTime().
package com.mco; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { primaryStage.setTitle("Le temps"); Group root = new Group(); Scene theScene = new Scene(root); primaryStage.setScene(theScene); Canvas canvas = new Canvas(512, 512); root.getChildren().add(canvas); Loop loop = new Loop(canvas); loop.start(); primaryStage.show(); } }
package com.mco; import javafx.animation.AnimationTimer; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; public class Loop extends AnimationTimer { private Canvas canvas; private long previousTime; private double x, y; public Loop(Canvas canvas) { this.canvas = canvas; this.previousTime = System.nanoTime(); //évite les effet de bord à l'initialisation de l'application x = 0; y = canvas.getHeight() / 2 - 50; } @Override public void handle(long now) { long delta = now - previousTime; previousTime = now; x += (100 * 1E-9) * delta; //avance de 100px toute les secondes if (x > canvas.getWidth() - 100) { //se cogne à droite de l'écran x = canvas.getWidth() - 100 - 5; } GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(Color.WHEAT); gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); gc.setFill(Color.MAROON); gc.fillRect(x, y, 100, 100); } }
Faites osciller le carré de gauche à droite et de droite à gauche.
Nous avons ici fait un exemple complet en utilisant tout ce que vous avez appris jusque là. On a dans la mesure du possible utilisé des méthodes de code courantes.
Essayez de comprendre le code :
private
.package com.mco; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.stage.Stage; public class Main extends Application { public static void main(String[] args) { launch(args); } public void start(Stage theStage) { theStage.setTitle("Watch Hour"); Group root = new Group(); Scene theScene = new Scene(root); theStage.setScene(theScene); Canvas canvas = new Canvas(512, 512); root.getChildren().add(canvas); Loop mainLoop = new Loop(canvas); mainLoop.start(); theStage.show(); } }
package com.mco; import javafx.animation.AnimationTimer; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.paint.Color; public class Loop extends AnimationTimer { private Canvas canvas; final double ROTATION = (2 * Math.PI) * 1E-9; // une rotation par seconde final Color BACKGROUND_COLOR = Color.ALICEBLUE; final Color WATCH_HAND_COLOR = Color.BISQUE; final int WATCH_HAND_LINE_WIDTH = 10; final double WATCH_HAND_LENGTH = 200; private double angle; private long previousTime; public Loop(Canvas canvas) { currentTime = System.nanoTime(); this.canvas = canvas; } @Override public void handle(long now) { handleTime(now); drawStuff(); } private void handleTime(long now) { changeAngle(now - previousTime); previousTime = now; } private void changeAngle(long delta) { angle += ROTATION * delta; angle %= 2 * Math.PI; } private void drawStuff() { clearCanvas(); drawWatchHand(); } private void clearCanvas() { GraphicsContext gc = canvas.getGraphicsContext2D(); gc.setFill(BACKGROUND_COLOR); gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); } private void drawWatchHand() { GraphicsContext gc = canvas.getGraphicsContext2D(); double middleX = canvas.getWidth() / 2; double middleY = canvas.getHeight() / 2; gc.beginPath(); gc.setLineWidth(WATCH_HAND_LINE_WIDTH); gc.setStroke(WATCH_HAND_COLOR); gc.moveTo(middleX, middleY); gc.lineTo(middleX + WATCH_HAND_LENGTH * Math.cos(angle), middleY + WATCH_HAND_LENGTH * Math.sin(angle)); gc.stroke(); } }
Ajouter la petite aiguille à la montre. Elle doit tourner 12 fois moins vite que la grande aiguille et être 3 fois plus petite.
Normalement, le fait d'avoir utilisé de petites fonctions bien nommée doit vous permettre d'ajouter facilement cette nouvelle aiguille.