Afin de pouvoir "animer" l'aiguille, le programme doit maintenant redessiner périodiquement la fenêtre en positionnant l'aiguille en fonction du temps écoulé.
Nous allons utiliser pour cela la classe AnimationTimer. Ajouter dans la classe EchantillonController les lignes suivantes (le commentaire est facultatif!) :
AnimationTimer mouvement=new AnimationTimer(){
@Override
public void handle(long now){
// le code à exécuter périodiquement est à écrire ici
}
};
et importer la classe AnimationTimer au début du programme (utiliser l'assistant d'Eclipse!).
Une fois l'AnimationTimer démarré, le code écrit à l'emplacement indiqué sera exécuté régulièrement, environ 60 fois par secondes. Le comportement de la méthode "handle" est donc analogue à celui d'une boucle exécutée 60 fois par secondes à partir du moment où le timer sera lancé.
Pour exécuter une action (ici, déplacer l'aiguille) une fois par seconde à l'aide du timer, nous allons utiliser une variable "compteur" de type entier long dont nous incrémenterons la valeur à chaque exécution du handle.
Nous vérifierons ensuite à chaque exécution si cette variable "compteur" est un multiple de 60, et si oui nous calculerons la valeur en secondes en divisant la valeur de "compteur" par 60.
Nous dessinerons alors l'aiguille en fonction de cette valeur en secondes, selon un angle égal au (temps en secondes)*2π/60 = (temps en secondes)*π/30.
On déclare donc une variable "compteur" au début de la classe EchantillonController
long compteur=0;puis on complète le code de l'AnimationTimer ainsi
AnimationTimer mouvement=new AnimationTimer(){ @Override public void handle(long now){ compteur++; if (compteur%60==0){ double secondes=compteur/60; traceCadran(secondes*Math.PI/30); } } };
Il ne reste plus qu'à déclencher le timer en ajoutant à la fin de la méthode initialize la ligne
mouvement.start();juste après la ligne
traceCadran(0);
Cependant si nous testons l'exactitude de notre "chronomètre" nous allons constater qu'elle laisse à désirer. Cela est dû au fait que l'exécution du handle est garantie 60 fois par seconde... environ.
La méthode du compteur n'est donc valable que pour des animations pour lesquelles le temps n'est pas un facteur crucial. Pour obtenir un chronomètre juste, il faut se donner un peu plus de mal.
Tout système informatique possède une horloge interne qui fournit la cadence des opérations effectuées.
En ce qui concerne la classe ActionTimer, la valeur de variable now figurant en paramètre de "handle", donne la date (en nanosecondes) indiquée par l'horloge interne du système.
Nous allons alors déclarer deux nouvelles variables dans la classe EchantillonController (on peut supprimer la variable compteur devenue inutile) :
double tempsDeDepart=0; double tempsCourant;La première, tempsDeDepart, sera destinée à conserver la valeur qu'indiquait l'horloge interne lors de la mise en route du timer.
AnimationTimer mouvement=new AnimationTimer(){ @Override public void handle(long now){ if (tempsDeDepart==0){ //ce bloc est exécuté lors de la mise en route du timer tempsDeDepart=now; tempsCourant=now; } if (now-tempsCourant>1e9){ //les lignes de ce bloc sont exécutées toute les secondes double secondes=Math.floor((now-tempsDeDepart)/1e9); traceCadran(secondes*Math.PI/30); tempsCourant=now;//la valeur de tempsCourant est actualisée } } };
Explications :
Lors de la première exécution de la méthode handle, tempsDeDepart a encore la valeur qui lui a été donnée au lancement du programme, c'est à dire 0.
Si la méthode handle constate que tempsDeDepart vaut 0, cela indique qu'il s'agit de sa première exécution, donc que le timer vient d'être lancé.
Le programme initialise donc DeDepart et tempsCourant avec la valeur donnée à ce moment-là par l'horloge, en y copiant le contenu de la variable "now", qui donne le temps en nanosecondes.
Ensuite, à chaque nouvelle exécution, le programme vérifie si l'indication de l'horloge est supérieur d'au moins 109 à la valeur enregistrée dans tempsCourant.
Si c'est le cas, cela signifie qu'une seconde s'est écoulée depuis la dernière actualisation de la valeur de tempsCourant: Il faut donc "déplacer" l'aiguille.
Le programme calcule alors la durée en secondes écoulée depuis la mise en route du chronomètre, grâce à la ligne double secondes=Math.floor((now-tempsDeDepart)/1e9) puis positionne l'aiguille en fonction de cette durée.
Enfin la valeur de tempsCourant est actualisée à la valeur actuelle du temps système, grâce à la variable "now"
package application; import javafx.animation.AnimationTimer; import javafx.fxml.FXML; import javafx.geometry.VPos; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.image.Image; import javafx.scene.paint.Color; import javafx.scene.paint.ImagePattern; import javafx.scene.text.Font; import javafx.scene.text.TextAlignment; public class SampleController { @FXML Canvas fond; GraphicsContext gc; double largeur; double hauteur; double rayon; double lAiguille; double tempsDeDepart=0; double tempsCourant=0; Image decor=new Image("images/Desert.jpg"); AnimationTimer mouvement=new AnimationTimer(){ @Override public void handle(long now){ if (tempsDeDepart==0){ tempsDeDepart=now; tempsCourant=now; } if (now-tempsCourant>1e9){ double secondes=Math.floor((now-tempsDeDepart)/1e9); traceCadran(secondes*Math.PI/30); tempsCourant=now; } } }; private void traceCadran(double inclinaison){ gc.drawImage(decor,-largeur/2,-hauteur/2,largeur,hauteur); ImagePattern motif=new ImagePattern(decor); gc.setFill(motif); for (int i=0;i<12;i=i+1){ gc.fillOval(rayon*Math.cos(i*Math.PI/6)-8,rayon*Math.sin(i*Math.PI/6)-8, 16, 16); } gc.setFont(new Font(20)); gc.setTextAlign(TextAlignment.CENTER); gc.setTextBaseline(VPos.CENTER); gc.setFill(Color.BEIGE); gc.fillText("0",0,-rayon-20); gc.fillText("15", rayon+20, 0); gc.fillText("30",0, rayon+20); gc.fillText("45", -rayon-20, 0); gc.setStroke(Color.color(0.8,0.9,1)); gc.setLineWidth(4); inclinaison=inclinaison-Math.PI/2; gc.strokeLine(0,0,lAiguille*Math.cos(inclinaison),lAiguille*Math.sin(inclinaison)); } @FXML void initialize(){ gc=fond.getGraphicsContext2D(); largeur=fond.getWidth(); hauteur=fond.getHeight(); rayon=largeur/2.5; lAiguille=rayon*4/5; gc.translate(largeur/2,hauteur/2); traceCadran(0); mouvement.start(); } }Si tout va bien, l'aiguille tourne maintenant bien de 1/60ème de tour à chaque seconde.