Création d'une boussole
Date de publication : 13 février 2010 , Date de mise à jour : 3 avril 2010
Par
Davy Leggieri (Page de l'auteur)
Ce tutoriel va vous présenter, de façon globale, comment créer une vue et l'animer.
I. Introduction
I-A. Le résultat
I-B. Version du SDK utilisé
II. Création de la vue
II-A. Spécification
II-B. Réalisation
II-B-1. La méthode onMeasure
II-B-2. La méthode onDraw
II-B-2-a. Canvas save et restore
II-B-2-a-i. Intérêt
II-B-2-a-ii. Fonctionnement
III. Exemple d'utilisation de la vue boussole
IV. Mettre à jour la vue grâce à la boussole numérique
IV-A. Listener exécuté lorsque la boussole numérique possède une nouvelle orientation
IV-B. Lier le changement d'orientation de la boussole numérique à notre listener
V. Animation de la boussole
V-A. La technique utilisée
V-B. Les modifications du code
V-C. Améliorer l'animation
V-D. Forcer le sens de rotation de l'aiguille
V-E. Critique
VI. Conclusion
VII. Remerciements
VIII. Liens
I. Introduction
Ce tutoriel a pour objectif de vous permettre de créer une boussole. Celle-ci réagira en fonction de
l'orientation de votre téléphone, pourvu que votre mobile soit muni d'une boussole numérique.
Outre le résultat final, l'intérêt majeur de ce tutoriel est d'aborder la création d'une vue et son animation.
Nous allons ainsi aborder les principes de bases nécessaires à la création et l'animation d'une vue
personnalisée, mais également les manipulations élémentaires d'un canevas sous Android.
I-A. Le résultat

La boussole que nous allons créer
À la fin de ce tutoriel, vous obtiendrez la boussole ci-dessus. Celle-ci ne brille pas forcément de par
son design, mais a le mérite d'être animée et de laisser libre cours à votre imagination pour l'agrémenter.
I-B. Version du SDK utilisé
Ce tutoriel a été compilé avec la version 1.5 du SDK, et le code utilisé reste inchangé jusqu'à (au moins)
la version 2.1 du SDK.
II. Création de la vue
Dans cette partie, nous allons présenter uniquement la création de la vue de la boussole. Ainsi, on abordera
l'héritage des classes de bases du SDK, ainsi que les méthodes élémentaires à redéfinir.
II-A. Spécification
Notre classe CompassView devra être capable d'afficher une boussole pointant son aiguille vers
le nord. Pour ce faire, notre vue aura deux méthodes : la première pour indiquer l'orientation du
nord, la seconde pour récupérer cette valeur. La classe possédera donc un attribut privé northOrientation
qui permettra de sauvegarder en interne l'orientation du nord en degrés. Cette orientation sera
comprise entre 0 et 360, et suivra la convention suivante :

Convention adoptée pour l'orientation
II-B. Réalisation
Tous les éléments graphiques de bases sous Android (TextView, CheckBox, etc.) héritent de la classe View.
Cette classe View est la brique de base permettant de construire l'interface homme-machine
d'une application. C'est elle qui dessine sur l'écran et qui intercepte les actions de l'utilisateur
(toucher une zone de l'écran par exemple). Pour créer la vue de notre boussole, nous allons créer
une classe CompassView qui va hériter de la classe View. D'après les spécifications
de notre vue présentées précédemment, nous pouvons aboutir à la classe incomplète suivante :
| CompassView.java |
public class CompassView extends View {
private float northOrientation=0;
public CompassView(Context context) {
super(context);
}
public CompassView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CompassView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public float getNorthOrientation() {
return northOrientation;
}
public void setNorthOrientation(float rotation) {
if (rotation != this.northOrientation)
{
this.northOrientation = rotation;
this.invalidate();
}
}
}
|
Notre classe contient à présent les données et les méthodes élémentaires qui seront utilisées pour la
manipuler. Nous allons maintenant nous concentrer sur le coeur de métier de cette classe, c'est à
dire l'affichage de la boussole en elle-même. Une grande partie du code va être concentrée dans
deux méthodes héritées de la classe View :
La première méthode, onMeasure, est appelée lors de l'instanciation de notre vue par son parent.
Cette méthode permet à notre vue de déclarer la taille sur l'écran qui lui est nécessaire pour se
dessiner. La seconde méthode, onDraw, est appelée lorsque la vue doit dessiner son contenu,
c'est dans celle-ci que nous userons du pinceau pour dessiner notre boussole.
Voici un tableau résumant ce que nous venons de dire :
|
Catégorie
|
Méthodes
|
Description
|
|
Layout
|
onMeasure(int, int)
|
Appelée pour déterminer la taille de la vue (et de ses enfants)
|
|
Drawing
|
onDraw(Canvas)
|
Appelée quand la vue doit dessiner son contenu
|
II-B-1. La méthode onMeasure
La méthode onMeasure permet à notre vue de déclarer, à la vue qui est hiérarchiquement supérieure,
la taille qui lui est nécessaire pour se dessiner à l'écran. Il faut savoir que si cette méthode
n'est pas redéfinie, notre vue fera par défaut 100x100 pixels et que par conséquent, quand bien
même vous dessineriez de grandes choses, vous ne verrez rien au-delà de ce carré de 100x100 pixels
que forme votre vue.
Dans le cas de la boussole, nous allons laisser notre vue être un carré aussi grand que possible, c'est
à dire, aussi grand que le propose la vue parente de notre boussole. Comment est-ce possible
? Il nous suffit de regarder du côté des paramètres d'appel de onMeasure. Les
deux paramètres widthMeasureSpec et heightMeasureSpec contiennent l'éventuelle taille
que le parent se propose d'offrir à notre vue. Vous avez bien lu "éventuelle ", car c'est à ce
niveau que les choses se complexifient. Il nous faudra donc tester ces paramètres, et forcer une
taille par défaut si le parent ne nous permet pas de recueillir les informations concernant la
taille disponible.
| CompassView.java |
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int measuredWidth = measure(widthMeasureSpec);
int measuredHeight = measure(heightMeasureSpec);
int d = Math.min(measuredWidth, measuredHeight);
setMeasuredDimension(d, d);
}
private int measure(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.UNSPECIFIED) {
result = 150;
} else {
result = specSize;
}
return result;
}
|
II-B-2. La méthode onDraw
Après avoir déclaré la taille de notre vue (via la méthode onMeasure), nous allons dessiner à
l'intérieur de celle-ci en utilisant la méthode onDraw. Cette méthode sera appelée "automatiquement"
lors du premier dessin de la fenêtre (ou à chaque fois que l'on appellera explicitement la méthode
invalidate de notre vue).
Avant de procéder à la phase du dessin, nous devons repenser notre façon de programmer : notre code doit
être optimisé autant que possible, car la fluidité de notre animation dépendra de cette portion
de code. Notre petit robot vert est muni d'un Garbage Collector assez simple qui, pour faire le
ménage, bloque l'exécution du programme pendant 100 ms (plus spécifiquement l'exécution du thread
responsable de l'interface graphique, l'UI thread). La résultante du déclenchement du Garbage Collector
au moment de la procédure de dessin est un affichage saccadé (très visible dans les animations).
Une téléportation des différents éléments fera place à l'animation proprement dite, ce qui n'est
pas très esthétique. La règle d'or est donc d'éviter autant que possible d'instancier de nouveaux
objets dans le code de la méthode onDraw.
Pour cela, nous allons commencer par créer une méthode privée initView qui sera appelée dans les
constructeurs de CompassView.java et qui aura pour but d'instancier tous les objets manipulés
dans la méthode onDraw. De cette manière, on instanciera tout ce dont nous aurons besoin
pour dessiner, et ainsi notre méthode onDraw n'aura que peu de contenu dynamique ce qui
limitera l'exécution du GC.
| CompassView.java |
public CompassView(Context context) {
super(context);
initView();
}
|
Pour dessiner notre boussole, 4 objets nous sont nécessaires/indispensables. Tout d'abord nous aurons
besoin d'un Path trianglePath, qui sera un ensemble de lignes formant un triangle pour dessiner
la forme de nos aiguilles. Puis nous aurons besoin de circlePaint, northPaint et southPaint qui
seront des " pinceaux " utilisés pour dessiner respectivement l'arrière-plan de la boussole, l'aiguille
du nord et celle du sud. Nous déclarerons trois "pinceaux" différents car ils nous permettront
de dessiner avec des couleurs différentes.
| CompassView.java |
private Paint circlePaint;
private Paint northPaint;
private Paint southPaint;
private Path trianglePath;
private void initView() {
Resources r = this.getResources();
circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
circlePaint.setColor(r.getColor(R.color.compassCircle));
northPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
northPaint.setColor(r.getColor(R.color.northPointer));
southPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
southPaint.setColor(r.getColor(R.color.southPointer));
trianglePath = new Path();
}
|
Remarque : vous pouvez constater que les couleurs ne sont pas codées en dur mais font référence à un
fichier de ressources " R.color.xxx " décrit ci-dessous.
| color.xml |
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="northPointer">#FF0000</color>
<color name="southPointer">#000000</color>
<color name="compassCircle">#CCCCFF</color>
</resources>
|
Voilà, le terrain est à présent prêt et il ne nous reste plus qu'à écrire le corps de la fonction pour
dessiner. onDraw possède un seul paramètre : le canevas étant l'équivalent d'une toile sur
laquelle nous allons dessiner. Toutes les actions de dessins vont donc avoir lieu sur cet objet.
Nous allons ainsi utiliser drawCircle pour dessiner un cercle (représentant le fond de notre
boussole) et des drawPath pour dessiner des formes composées par nous-mêmes (représentant
les aiguilles de notre boussole).
| CompassView.java |
@Override
protected void onDraw(Canvas canvas) {
int centerX = getMeasuredWidth() / 2;
int centerY = getMeasuredHeight() / 2;
int radius = Math.min(centerX, centerY);
canvas.drawCircle(centerX, centerY, radius, circlePaint);
canvas.save();
canvas.rotate(-northOrientation, centerX, centerY);
trianglePath.reset();
trianglePath.moveTo(centerX, 10);
trianglePath.lineTo(centerX - 10, centerY);
trianglePath.lineTo(centerX + 10, centerY);
canvas.drawPath(trianglePath, northPaint);
canvas.rotate(180, centerX, centerY);
canvas.drawPath(trianglePath, southPaint);
canvas.restore();
}
|
Sous Android, le canevas se manipule comme sous Swing, c'est-à-dire qu'il s'agit d'une toile qu'on peut
déplacer un peu comme une feuille de papier. Vous remarquerez que nous avons déplacé le canevas
avec "rotate", ce qui nous a permis de le faire tourner autour d'un point. Le centre choisi pour
la rotation est tout simplement le centre du cercle. La première manipulation du canevas est une
rotation amenant le nord en haut de notre canevas. Ensuite, nous avons simplement dessiné la première
aiguille, puis nous avons fait une rotation de 180 ° pour dessiner la deuxième aiguille (celle du
Sud) opposée à la première.
II-B-2-a. Canvas save et restore
Vous remarquerez que nous avons utilisé les deux méthodes save et restore de l'objet canevas qui ne sont
pas utiles dans notre exemple du dessin de la boussole, mais que nous allons tout de même introduire
et expliquer.
II-B-2-a-i. Intérêt
Ces méthodes permettent de sauvegarder la position initiale du canevas, ainsi toute manipulation de ce
dernier (déplacement ou rotation) pourra être annulée.
II-B-2-a-ii. Fonctionnement
-
Save permet de sauvegarder la position actuelle de votre canevas (ne sauvegarde pas le contenu).
-
Restore permet de restaurer une position de votre canevas.
La sauvegarde et la restauration fonctionnent comme une pile LIFO, c'est-à-dire que si on sauvegarde
une position A puis une position B, le premier appel à restore va restaurer la position
B et le second la position A.
Voilà pour la petite histoire sur save et restore, retour donc à la vue de notre boussole. Notre vue
est à présent finalisée, il ne nous reste plus qu'à observer le rendu.
III. Exemple d'utilisation de la vue boussole
C'est bien beau tout ce travail, mais on aimerait bien en voir la couleur, n'est-ce pas ? Pour ce faire,
il suffit de créer une Activity qui va instancier sa propre interface graphique à partir d'un fichier
XML.
| Boussole.java |
public class Boussole extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
|
Déclarons le XML main.xml qui va contenir uniquement notre vue:
<?xml version="1.0" encoding="utf-8"?>
<!-Pour utiliser notre vue, il suffit de mettre le nom complet du paquetage de notre classe CompassView-->
<com.bydavy.boussole.view.CompassView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/compassView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
|
Petite parenthèse : vous remarquerez que nous affichons notre vue avec la propriété xmlns :android="http://schemas.android.com/apk/res/android".
Ceci vient du fait que notre Activity ne comportera que la vue de la boussole, cependant, nous pourrions
très bien mettre notre vue dans un LinearLayout ou tout autre Layout.
Si nous exécutons le programme, nous devrions avoir une jolie boussole qui s'affiche sur l'écran. Pour
le moment, l'aiguille du nord pointe stupidement vers le haut. Nous allons donc tenter de modifier
l'orientation de l'aiguille du nord. Pour ce faire, nous allons changer le code de notre activity
de la manière suivante :
| Boussole.java |
public class Boussole extends Activity {
private CompassView compassView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
compassView = (CompassView)findViewById(R.id.compassView);
compassView.setNorthOrientation(45);
}
}
|

Boussole pointant vers 45 °
On pourra remarquer que l'appel "compassView.setNorthOrientation(45)" a bien modifié l'orientation du
nord signalé par notre vue. On peut donc conclure que notre classe BoussoleView.java répond bien
aux spécifications que nous avions formulées. Nous pouvons maintenant la lier aux données de la boussole
numérique.
IV. Mettre à jour la vue grâce à la boussole numérique
Une vue fonctionnelle c'est très bien, mais une vue qui montre des données utiles c'est encore mieux.
Pour récupérer les informations de la boussole numérique, nous avons besoin de demander au SensorManager
la liste des capteurs de type boussole dont il dispose. Celui-ci va nous retourner une liste de capteurs
et nous allons garder uniquement le premier (tout appareil est muni d'une seule boussole numérique,
mais le SDK nous impose ce comportement). Ensuite nous allons créer un SensorEventListener
qui sera exécuté à chaque fois que notre boussole numérique connaîtra une nouvelle orientation du
téléphone mobile. Pour finir, nous allons lier ce SensorEventListener à la boussole numérique.
Voici de manière schématique comment les choses vont se passer :
IV-A. Listener exécuté lorsque la boussole numérique possède une nouvelle orientation
Nous allons déclarer le listener :
| Boussole.java |
private final SensorEventListener sensorListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
compassView.setNorthOrientation(event.values[SensorManager.DATA_X]);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
|
IV-B. Lier le changement d'orientation de la boussole numérique à notre listener
Pour lier le changement d'orientation de la boussole numérique à notre listener, nous devons commencer
par déclarer le gestionnaire de capteurs et un capteur qui sera la boussole numérique.
| Boussole.java |
private SensorManager sensorManager;
private Sensor sensor;
|
Ensuite, nous allons demander au gestionnaire de capteurs les capteurs de type boussole dont il dispose.
| Boussole.java |
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
compassView = (CompassView)findViewById(R.id.compassView);
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
List<Sensor> sensors =sensorManager.getSensorList(Sensor.TYPE_ORIENTATION);
if (sensors.size() > 0) {
sensor = sensors.get(0);
}
}
|
Pour finir, nous allons demander au gestionnaire de capteurs de lier notre SensorEventListener
aux événements de la boussole numérique lorsque l'application s'affiche. Et inversement, nous allons
lui demander de défaire ce lien lorsque l'on quitte l'application.
| Boussole.java |
@Override
protected void onResume(){
super.onResume();
sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onStop(){
super.onStop();
sensorManager.unregisterListener(sensorListener);
}
|
Nous disposons maintenant d'une vue qui est mise à jour à chaque fois qu'une nouvelle orientation est
connue par la boussole numérique.
V. Animation de la boussole
Notre application fonctionne parfaitement, mais on observe une certaine tendance à la téléportation de
notre aiguille, notamment lorsqu'on fait brusquement changer le téléphone de direction. Ceci est
dû au simple fait qu'on change l'orientation de l'aiguille brutalement. Ainsi, la boussole passe sans
transition de l'ancienne valeur à la nouvelle. Pour remédier à ce problème, je propose qu'on fasse
évoluer l'aiguille entre l'ancienne et la nouvelle orientation du nord de manière incrémentale et
fluide.
V-A. La technique utilisée
Lorsqu'une nouvelle orientation sera fournie à notre vue grâce à la méthode SetNorthOrientation,
nous sauvegarderons l'orientation courante et l'orientation fournie en paramètre, que nous appellerons
respectivement startNorthOrientation et endNorthOrientation. Nous redessinerons ainsi
la vue plusieurs fois en faisant varier NorthOrientation entre ces deux valeurs, de telle
manière que NorthOrientation parte de startNorthOrientation et se rapproche de plus
en plus de endNorthOrientation.
Pour redessiner périodiquement notre vue, nous devrons utiliser un Timer. Chaque exécution de
la tâche du timer fera évoluer la position courante de l'aiguille et redessinera la vue de la boussole.
Ce Timer sera appelé toutes les 20ms pendant 1 seconde. Ce qui signifie que l'animation durera 1
seconde et qu'au maximum il y aura 1 000/20 = 50 images par seconde pour donner l'impression d'une
animation. Ne pouvant prédire exactement combien d'images par seconde seront affichées au cours
de cette seconde d'animation, nous allons déterminer la position de l'aiguille à afficher en fonction
du pourcentage d'évolution de notre animation. Pour produire de telles informations, nous utiliserons
le temps écoulé depuis le début de l'animation.
Précédemment, j'ai dit que nous utiliserons un Timer pour dessiner périodiquement la vue. Cependant,
le recours à un Timer implique par définition l'utilisation d'un nouveau thread. Un thread pour
de l'animation est-ce bien raisonnable ? Développant pour téléphone portable, nous devons économiser
les ressources à notre disposition or la création d'un thread pour une animation parait démesurée.
Nous profiterons donc des fonctionnalités du SDK Android, et nous utiliserons un Handler.
Le Handler est capable d'exécuter, en différé, un objet de type Runnable. Ce dernier
sera exécuté dans le UI thread lorsque ce thread n'aura rien à faire.
 |
Les vues sont déjà munis d'un Handler. Les appels aux méthodes post, postDelay et removeCallbacks
du Handler seront donc accessible directement depuis notre classe CompassView.
|
V-B. Les modifications du code
Nous allons ajouter les attributs suivants à notre vue:
| CompassView.java |
private final int DELAY = 20;
private final int DURATION = 1000;
private float startNorthOrientation;
private float endNorthOrientation;
private long startTime;
|
Et nous allons, également, changer la méthode setNorthOrientation de la manière suivante :
| CompassView.java |
public void setNorthOrientation(float rotation) {
if (rotation != this.northOrientation) {
removeCallbacks(animationTask);
this.startNorthOrientation = this.northOrientation;
this.endNorthOrientation = rotation;
startTime = SystemClock.uptimeMillis();
postDelayed(animationTask, DELAY);
}
}
|
La méthode setNorthOrientation va donc sauvegarder la position courante, la nouvelle position
désirée de l'aiguille, la date de début de l'animation (en ms) et va programmer une première exécution
de la tâche animationTask du handler.
Voici à présent le code de la tâche chargée de l'animation :
| CompassView.java |
private Runnable animationTask = new Runnable() {
public void run() {
long curTime = SystemClock.uptimeMillis();
long totalTime = curTime - startTime;
if (totalTime > DURATION) {
northOrientation = endNorthOrientation;
removeCallbacks(animationTask);
} else {
postDelayed(this, DELAY);
}
invalidate();
}
};
|
Vous remarquerez que dans cette tâche nous essayons de déterminer s'il y a plus d'une seconde écoulée
depuis le début de l'animation. Si tel est le cas, l'animation devra être terminée, nous mettons
alors l'aiguille du nord dans la position finale et nous arrêtons toutes les prochaines animations
programmées s'il y en a (pas forcément utile).
Dans le cas où notre animation se déroule depuis moins d'une seconde, nous allons changer la position
de l'aiguille, reprogrammer un appel à cette tâche dans DELAY ms pour continuer l'animation et enfin
redessiner la vue.
Pour le moment, notre code ne fait pas encore évoluer la position de l'aiguille entre chaque appel à
la tâche d'animation. Notre aiguille est seulement déplacée à sa position finale après 1 seconde.
Nous allons maintenant mettre en place l'évolution de sa position. Admettons que notre aiguille
soit à 5°, et que l'on désire la ramener à 13°. Lorsque notre tâche d'animation est appelée nous
allons déterminer à quel pourcentage de l'animation nous nous trouvons :
| CompassView.java |
perCent = ((float) totalTime) / DURATION;
|
Et nous allons ainsi modifier la position de l'aiguille pour qu'elle soit à x perCent entre sa position
initiale de 5° et sa position finale de 13°, ce qui nous donne :
| CompassView.java |
northOrientation = (float) (startNorthOrientation + perCent * (endNorthOrientation - startNorthOrientation));
|
Au final, nous arrivons donc au code suivant :
| CompassView.java |
if (totalTime > DURATION) {
?
} else {
float perCent = ((float) totalTime) / DURATION;
perCent = Math.min(perCent, 1);
northOrientation = (float) (startNorthOrientation + perCent * (endNorthOrientation - startNorthOrientation));
postDelayed(this, DELAY);
}
invalidate();
|
Le code étant complet, nous pouvons tester l'animation de notre boussole fraîchement créée. Vous remarquerez
que l'aiguille ne se téléporte plus lors du soudain changement d'orientation. Cependant, l'aiguille
évolue de manière bizarre, puisque qu'elle évolue toujours à la même vitesse pour aller de sa position
de départ à sa position finale, peut importe qu'elle soit très loin du nord magnétique ou juste
à côté. Pour le moment, le résultat est bien loin d'être réaliste.
V-C. Améliorer l'animation
Pour donner plus de crédibilité à notre animation nous allons essayer de trouver une fonction mathématique,
non linéaire, qui évolue entre 0 et 1 (valeur de perCent). Cette fonction devra croître rapidement
au voisinage de 0, puis réduire sa croissance à l'approche de 1. Pour cela, il semble intéressant
d'utiliser la fonction sinus qui a le comportement recherché. Nous devrons juste modifier son amplitude
pour s'assurer que pour la valeur 1 (de notre perCent) la fonction nous retourne bien 1, c'est-à-dire
qu'à 100% de notre animation nous affichons bien la fin de l'animation. Ainsi, la fonction retenue
sera sinus(x * 1.5).

Sinus(x*1.5)
Valeurs prises entre 0 et 1 :
|
X
|
0
|
0.2
|
0.4
|
0.6
|
0.8
|
1
|
|
Y
|
0
|
0.29
|
0.56
|
0.78
|
0.93
|
1
|
Nous avons donc une forte accélération entre 0 et 0,6 ce qui permet de faire évoluer rapidement notre
aiguille vers la position finale en début d'animation, alors qu'en fin d'animation, sa vitesse de
déplacement va fortement s'altérer jusqu'à arriver à destination, le nord.
Nous pouvons donc remplacer notre portion de code par ceci :
| CompassView.java |
if (totalTime > DURATION) {
?
} else {
float perCent = ((float) totalTime) / DURATION;
perCent = (float) Math.sin(perCent * 1.5);
perCent = Math.min(perCent, 1);
northOrientation = (float) (startNorthOrientation + perCent * (endNorthOrientation - startNorthOrientation));
postDelayed(this, DELAY);
}
invalidate();
|
V-D. Forcer le sens de rotation de l'aiguille
Nous voici donc en présence d'une jolie boussole dont le comportement est proche de celui d'une vraie
boussole, cependant, notre animation possède toujours un "bug", ou plutôt une réaction étrange.
Par exemple, lorsqu'elle passe de l'orientation 5° à 350° celle-ci décide d'emprunter le chemin
suivant :

Rotation non réaliste
Il va nous faudra donc forcer le sens d'évolution de notre aiguille, en augmentant par exemple, de manière
virtuelle, l'orientation de départ ou d'arrivée de 360°. Si nous reprenons le cas de l'exemple précédent,
nous augmenterons de 360° la position de départ qui devient 365°. Ainsi notre aiguille, évoluant
entre 365° et 350°, empruntera le chemin le plus court, qui se trouve également être le plus réaliste.
Voici les modifications apportées à la méthode SetNorthOrientation :
| CompassView.java |
public void setNorthOrientation(float rotation) {
if (rotation != this.northOrientation) {
removeCallbacks(animationTask);
this.startNorthOrientation = this.northOrientation;
this.endNorthOrientation = rotation;
if ( ((startNorthOrientation + 180) % 360) > endNorthOrientation)
{
if ( (startNorthOrientation - endNorthOrientation) > 180 )
{
endNorthOrientation+=360;
}
} else {
if ( (endNorthOrientation - startNorthOrientation) > 180 )
{
startNorthOrientation+=360;
}
}
startTime = SystemClock.uptimeMillis();
postDelayed(animationTask, DELAY);
}
}
|
A présent, nous allons simplifier la valeur de l'orientation lorsque l'on atteint la position finale,
tout simplement car l'astuce utilisée juste ci-dessus génère des valeurs supérieures à 360°, c'est-à-dire,
plus d'un tour de cadran, ce qui est quelque peu gênant.
| CompassView.java |
private Runnable animationTask = new Runnable() {
public void run() {
?
if (totalTime > DURATION) {
northOrientation = endNorthOrientation % 360;
removeCallbacks(animationTask);
} else {
?
}
?
}
};
|
V-E. Critique
Je tiens à apporter une critique à l'animation que nous avons produite. Le résultat est satisfaisant
d'autant plus s'il s'agit de vos premiers pas, mais il reste améliorable. Pour comprendre pourquoi
l'animation est perfectible, je tiens à revenir au fonctionnement de la boussole numérique et plus
particulièrement au listener qui est lié au changement d'orientation. Lorsque vous tournez votre
téléphone mobile, ce dernier ne change pas brusquement d'orientation mais détecte plusieurs états
intermédiaires. Par conséquent, notre programme se voit délivrer x fois par seconde une nouvelle
orientation. Vous comprendrez donc que, dans ces conditions, notre animation s'étalant sur une seconde,
elle soit prise de court et ne peut se terminer complètement. Visuellement cela n'a que peu d'importance
car la fin de l'animation est provoquée par l'arrivée d'une nouvelle orientation qui induit le début
d'une nouvelle animation.
Une approche envisageable serait de ne plus faire évoluer l'orientation de notre aiguille entre une position
de départ et celle d'arrivée, mais plutôt d'attribuer une vitesse de rotation à notre aiguille en
fonction de sa distance par rapport au nord. Le résultat sera plus facilement prévisible et contrôlable,
ainsi nous serons en adéquation avec la réaction réelle d'une boussole.
VI. Conclusion
La création d'une vue sous Android est assez simple. Il suffit de redéfinir les méthodes onDraw et onMeasure.
L'alimentation de celle-ci grâce aux données issues des capteurs est possible grâce aux SensorEventListener.
L'animation par contre à recours à des handlers qui déterminent le pourcentage d'évolution de l'animation
et redessinent la vue.
En guise de conclusion à ce tutoriel, je vais revenir sur le choix de la boussole. Le résultat est une
boussole réagissant en fonction de l'orientation de votre téléphone portable à la manière d'une boussole
réelle. Mise à part le résultat assez plaisant et amusant, le choix de cette réalisation ne fut qu'un
prétexte permettant de vous présenter la création et l'animation de vues. J'espère tout simplement
avoir rempli ma mission.
VII. Remerciements
Je tiens à remercier les membres de l'équipe de Developpez.com,
pixelomilcouleurs,
jacques_jean, Kévin F.
et Thomas G. pour leur aide à la relecture de ce tutoriel.
VIII. Liens
Voici les liens pour télécharger le code source et le package Android :


Copyright © 2010 Davy Leggieri. Aucune reproduction, même partielle, ne peut être faite
de ce site et de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur.
Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 €
de dommages et intérêts.
Cette page est déposée.