Graphismes avec Java FX

Ajout d'une animation tenant compte du temps écoulé

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 (déplacer l'aiguille) une fois par seconde à l'ade 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); 

Si tout va bien l'aiguille tourne.

 

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.

Nous allons pour cela utiliser la valeur de variable now figurant en paramètre de "handle", car elle indique le temps du système en nanosecondes.
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 à contenir le temps du système lors de la mise en route du timer.
La seconde, tempsCourant sera destinée à contenir le temps actuel du système et devra donc être régulièrement actualisée.
Puis nous allons modifier le code de l'AnimationTimer ainsi :
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;
    }
  }
};
 

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 à la valeur actuelle du temps système, 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 le temps actuel est supérieur d'au moins 109 au dernier temps enregistré 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"


Le code complet contenu dans EchantillonController est alors :
 
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.
 
Licence Creative Commons
licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé
Auteur : Nathalie Bonnin
Professeur de Physique, Chimie, Informatique au lycée La Martinière Monplaisir (Lyon 8ème)
Contact :
nathalie.bonnin (chez) scientillula.net
Licence Creative Commons
La totalité du contenu du site Scientillula.net appartient à Nathalie Bonnin et est mise à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé