Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Cross-plateforme : images avec zones cliquables avec MonoDroid/Xamarin vs C#/Xaml

[new:30/06/2013]Comprendre les différences entre les OS est essentiel lorsqu’on veut créer des applications cross-plateformes. Petit exercice pratique avec Xamarin 2.0 : créer des images cliquables avec Xamarin/MonoDroid. Ceci sera le prétexte pour aborder Android et Xamarin 2.0 par un exemple réel.

C#/XAML

Je n’écrirais jamais assez de billets pour dire tout le bien que je pense que C# et de XAML, à quel point ce couple est parfait, agréable et d’une puissance à faire pleurer tous les éditeurs de langages du monde pour encore un bon moment…

Blend une UI de SF pour créer du rêve…

Faire des zones cliquables sur une image ou des fonds vectoriels en XAML est un jeu d’enfant. Avec le Visual State Manager et les Behaviors pas même besoin de code C# pour créer des visuels interactifs. Je ne ferais pas l’offense à mes lecteurs fidèles d’expliquer comment et je renvois les autres aux plus de 600 billets qui traitent majoritairement de XAML d’une façon ou d’une autre…

Donc avec C# et XAML, créer une zone cliquable sur une image est un jeu d’enfant. parce que XAML est hyper évolué et vectoriel. C’est un véritable langage de description d’UI.

Des avantages de la rusticité

Mais il n’en va pas de même sous d’autres OS plus “rustiques” au niveau description des UI. D’ailleurs, comme me le faisait remarquer Valérie (mon infographiste) il y quelques heures, dans “rustique” il y a “russe”… Et c’est vrai que passer de Windows Phone à Android ou iOS donne un peu l’impression de passer de la navette américaine aux bricolages ingénieux des cosmonautes russes…

J’ai toujours adoré l’esprit Russe, cette façon de faire aussi bien et parfois mieux avec trois fois rien. Android c’est un peu ça, même si c’est américain. Mais d’iOS ou d’Android s’il fallait dire lequel est le plus “rustique” pour un développeur vous seriez étonné du résultat…

Sous Android, et même avec Xamarin, point de vectoriel ni de databinding two-way ni rien de tout cela. XAML n’existe pas. Mais en retour : des astuces, des conventions (noms de répertoires par exemple pour les différentes capacités de l’unité mobile) et une robustesse basée sur un Linux complété d’une JVM, la célèbre Dalvik (qui sonne un peu comme Dalek, puissantes et rustiques machines que les amis de la SF sauront reconnaitre, avec un look presque… Russe !).

C’est rustique mais ça marche. Et avec un peu d’imagination, c’est même joli, fluide, et, en tout cas, cela semble séduire une masse impressionnante et toujours grandissante de gens dans le monde entier, raison principal de notre propre intérêt !

N’exagérons rien non plus !

Pour être franc je me dois de dire que j’ai forcé le trait en brossant rapidement ce portrait d’Android. Pour être juste cet OS est plein d’astuces intelligentes et a su évoluer rapidement pour prendre en charge l’explosion des form factors différents et parfois déroutants (dont les phablets) là où les autres se contentent de supporter un modèle voire deux et se tâtent encore pour savoir s’il faudrait envisager de faire de mieux… L’esprit bricoleur à la russe, Google a répondu “chiche!” à toutes les idées de tous les constructeurs et Android supporte une quantité redoutable de machines toutes différentes. Et ça marche très bien. C’est un exploit qu’il faut noter.

Alors rustique, oui peut-être dans l’esprit “russe-tic”, mais évolué et performant.

Android est peut-être le Soyouz des OS, mais l’un comme l’autre s’envolent vers les étoiles alors que les autres restent au sol… Pour un ingénieur ce qui compte, c’est le résultat !

 

Dans les trois PhoneSat’s lancés par la Nasa dernièrement, trois téléphones sous Android, 2 HTC One et un Samsung Nexus S. Tous ont rempli leur mission parfaitement et une seconde vague est prévue pour bientôt. Android est peut-être finalement le plus Hi-Tech des OS mobile…

Des images cliquables…

On revient 5 minutes sur terre car notre propos était de gérer des images cliquables. On a rapidement vu qu’en XAML les choses étaient évidentes. Mais sous Android ça peut devenir plus tricky.

Les UI sont créées à l’aide d’un langage balisé qui a quelques ressemblances avec XAML. On y retrouve un formalisme XML avec des balises qui en contiennent d’autres, ce qui créée l’arbre visuel. A l’intérieur des balises on retrouve des noms de classes qui seront instanciées au runtime et des propriétés qui serviront à initialiser ces instances. C’est vraiment très proche.

La seule différence notable, mais elle est de taille, c’est que XAML est vectoriel alors que l’UI de Android est bitmap. XAML possède aussi le data binding et les extensions de balises, mais je me suis rendu compte que finalement beaucoup de développeurs le trouvait complexe et je ne parle pas de Blend que bien plus encore n’arrivent pas à “piger”, même si je le considère comme le summum des EDI pour la conception visuelle d’une application. Après tout, le succès d’Android est peut-être qu’il est plus simple tout court et donc à comprendre aussi…

Dans un tel environnement on retrouve des techniques de mise en page plus proche de HTML que de WPF ou Silverlight. Un monde dans lequel les images jouent un rôle plus grand, où il faut fournir ces images dans toutes les résolutions supportées pour éviter la pixellisation, etc. Avantage : Android a été conçu dans un esprit minimaliste pour supporter des smartphones à la puissance vraiment réduite à sa sortie, alors quand il tourne sur des machines récentes de type S3 à 4 cœurs, c’est une bombe !

Xamarin Studio ou Visual Studio ?

Pour développer notre exemple nous avons le choix entre les deux EDI, VS étant un EDI de grande qualité dans lequel j’ai aussi ma gestion de version et ReSharper, autant le choisir. Mais les dernières versions de Xamarin Studio sont vraiment très agréables et complètes. On est loin de la … rusticité (encore elle !) de MonoDevelop qui a servi de base à cet EDI (mais qui grâce à cet héritage est cross-plateforme PC/Mac/Linux encore un avantage de l’esprit russe-tic !). Que cela soit sous XS ou VS on dispose désormais d’un éditeur visuel pour les UI. Le choix est donc vraiment ouvert, chacun fera comme il préfère (et selon sa licence de Xamarin aussi).

Le code exemple

D’abord il faut créer un nouveau projet. Je choisi de développer ici pour Ice Cream Sandwich, ICS, version 4.0 API niveau 14 parce que je le vaut bien Sourireet qu’ici je me fiche de la compatibilité avec Gingerbread (version 2.3, API niveau 10).

image

Choisir la version d’Android

Vous allez me dire “c’est quoi toutes ces versions ?” … On entend d’ailleurs souvent les mauvaises langues parler de “fragmentation” d’Android, il y aurait des tas de versions dans la nature ce qui rendrait le développement presque impossible. Je vous remercie d’avoir poser la question car elle va me permettre de clarifier les choses (c’est bien d’avoir des lecteurs intelligents !).

D’une part je ferai remarquer non sans perfidie mais aussi amertume (car je le regrette vivement) que chez Microsoft il y a eu 3 versions majeures : Windows mobile, Windows Phone 7 et Windows Phone 8 et qu’aucune n’est compatible avec la précédente alors qu’une application écrite pour Android Donut de 2009 fonctionnera sur un Galaxy S4 utilisant la dernière version. Ca rend plus humble d’un seul coup quand on veut parler “fragmentation”… Et je ne parle pas des incompatibilités matérielles chez Apple où il faut racheter tous ses accessoires et câbles à chaque nouvelle version du _même_ téléphone du _même_ constructeur !

Mais un principe de droit nous dit que “la turpitude des uns n’excuse pas celle des autres”. En gros si on vous prend en train de voler le dernier CD de Justin Bieber au Super U du coin, en dehors de vous coller la honte pour le restant de votre vie (à cause du choix du CD plus que du vol !), vous ne pourrez pas vous défendre en disant que vous connaissez plein de gens qui le font aussi même si c’est vrai et même si vous pouvez le prouver. Votre délit reste un délit même si d’autres le commettent.

Je me dois donc de faire face honnêtement à la critique sans répondre à celle-ci par d’autres critiques. La réalité est que Google a compliqué inutilement les choses en donnant un nom de sucrerie à chaque nouvelle version (dans l’ordre alphabétique) ce qui est rigolo, mais qui soit aurait du être assez, soit est de trop ou aurait du se limiter au “nom de code” des versions avant leur sortie. Car ce nom de version se décline aussi en un vrai numéro de version (la 2.3, la 4.0.1 …) ce qui est une exigence technique naturelle pour suivre les versions releasées de n’importe quel soft. Mais tout cela n’était pas suffisant puisque la version n’indique pas forcément s’il y a eu des changement dans l’API, ce qui intéresse les développeurs. De fait cette dernière est aussi numérotée.

Au final on a un nom de sucrerie qui correspond à une ou plusieurs versions numérotées le tout en correspondance avec un ou plusieurs niveaux d’API.

On trouve ainsi, par exemple, un Android Gingerbread (pain d’épice) qui suit en toute logique Froyo (G vient après F), le Froyo étant un yaourt glacé, et venant avant Honeycomb (H vient après G), Honeycomb étant un rayon de miel. Mais Gingerbread a connu plusieurs versions techniques qui sont numérotées : la 2.3 puis les 2.3.3 jusqu’à 2.3.7. Toutefois la plupart de ces versions n’ont pas touché aux API, sauf la 2.3.3, du coup la première release de Gingerbread, version 2.3, supportait l’API de niveau 9 alors que dès la sortie de la 2.3.3 l’API passa en niveau 10 pour y rester jusqu’à la sortie de Honeycomb…

Forcément ça embrouille un peu. Mais en tant que développeur une seule information vous intéresse en réalité : le niveau de l’API supportée, le reste est pur décorum.

Enfin, pour répondre complètement à la question qui était légitime, et je vous remercie encore de l’avoir poser, il faut parler vrai à propos de la fameuse fragmentation, c’est à dire la présence sur le marché d’utilisateurs ayant des machines sous plein de versions différentes ce qui serait un enfer.

La réalité est ici toute autre. Google tient à jour une analyse précise des machines en activité (avec un recul de 15 jours) qu’il est possible de consulter : le Dashboard Android. Pour les derniers 15 jours l’analyse est la suivante :

image

On voit qu’en fait 58,6 % du marché est en version ICS (4.x) ou supérieure et qu’il reste 36.4% en Gingerbread (2.3).

Si vous visez 60% du marché vous pouvez travailler directement en Ice Cream Sandwich (4.x).

Si vous désirez balayer près de 95% du marché vous devrez travailler en Gingerbread (2.3).

Les différences sont importantes pour l’UI (beaucoup de choses ont été peaufinées dans les versions 4) mais on peut faire fonctionner de très belles applications en supportant 2.3.
Une majorité d’applications étant dans cette version justement pour couvrir le marché. Ce qui enquiquine bien entendu Google qui aimerait bien se débarrasser des versions sous la 4.0 car cela freine les développeurs dans leur utilisation des dernières nouveautés surtout sur le marché grand public. Du coup certaines applications semblent moins belles et moins ergonomiques qu’elles le devraient ce qui laisserait supposer qu’Android n’est pas au top, faisant ainsi du tort à l’image du produit.
Mais la qualité d’une app dépend bien plus de son design que de la version de l’OS utilisé…

Finalement il faut se rappeler que pour l’instant (car les choses évoluent vite malgré tout) soit on vise les machines récentes, en gros 60% du marché, et on peut travailler directement en API de niveau 15, soit on vise 95% du marché et on doit alors utiliser l’API de niveau 10.

La “fragmentation” de Android est, on le voit ici, bien plus un argument d’une concurrence en difficulté qu’un véritable problème technique.

La structure de la solution

La structure d’un projet (et de sa solution) est habituelle avec ses propriétés, ses références éventuelles, ses classes, et ses ressources. On trouve bien entendu des spécificités liées à Android comme la présence d’un répertoire “Resources” contenant les “Drawable” (les ‘dessinables’) et les déclinaisons de ces ressources selon les densités d’écran supportées.

Ici l’image doit changer lorsqu’on cliquera sur les zones sensibles que nous allons créer, pour l’illustrer plusieurs variantes de l’image par défaut ont été réalisées. Elles sont en 800x480 et rangées dans le répertoire ‘drawable-hdpi’ c’est à dire que nous les considérons comme des images visant un écran haute densité. Par notre choix intermédiaire (autant sur la taille des images que par leur classification) nous allons pouvoir couvrir sans gros problèmes toutes les autres résolutions car partant d’une situation “moyenne” les éventuels zoom (avant ou arrière) qu’Android fera automatiquement pour adapter les images n’introduira pas de distorsion gênante.

Cette démarche est valable dans le contexte de cette démonstration mais pourrait être appliquée dans une situation réelle, c’est l’une des façons de gérer les grandes différences entre tailles et résolutions des écrans sous Android, un problème finalement bien plus délicat que le choix de la version des API.

Mais il y a tellement à dire à ce sujet que j’en resterais là pour ce billet !

image

Les images ont été empruntées à un article de Bill Lahti, un blogger du monde Android. Ce sont des PNG représentant une fusée dans les étoiles avec les variantes qui dépendent de la situation (feu sortant de la fusée ou non, un petit alien sorti de la fusée ou non, etc). Rien d’extraordinaire ni d’artistique, juste des images de test.

On trouve bien entendu une Activité (Activity1.cs) qui est l’unité d’exécution sous Android pour afficher quelque chose. Une Activité peut être vue comme le code-behind d’une page écran. Mais ce n’est qu’une approximation. Ici ce n’est pas le code du visuel qui se charge avec l’Activité mais uniquement une instance contenant le code gérant l’affichage, l’Activité devant charger le visuel qu’elle veut, voire en changer en cours d’exécution. L’Activité contient du code uniquement. La partie affichage est définie en XML et ce n’est qu’une description. Ici, le code visuel qui sera chargé par l’Activité se trouve dans le répertoire “layout” des “Resources” (c’est une convention qu’on doit respecter) et porte le nom de “Main.axml”. En réalité l’extension devrait être “xml” mais pour charger le designer visuel spécifique dans Visual Studio notamment il fallait adopter une extension différente (sinon c’est l’éditeur XML qui serait chargé par VS). Le fichier est donc totalement “natif” et pourrait être réutilisé en Java, à la nuance de son extension. Ce fichier de définition d’une mise en page instanciera des contrôles qui portent des noms (un ID), comme en XAML, et il sera possible d’y accéder depuis le code de l’Activité mais pas aussi directement (le nom de l’objet XML ne créée pas automatiquement une variable du même nom dans le code C#, il faut localiser l’objet graphique avec une méthode de recherche simple pour obtenir son pointeur et travailler dessus comme on le fait parfois en HTML).

Le projet contient aussi du code utilitaire, comme on en trouve dans les applications C# de tout type (ici la classe Tools).

J’aurais l’occasion de revenir sur certains détails d’un projet Android, ici nous avons l’essentiel pour comprendre la structure de l’exemple.

L’astuce des zones cliquables

Il est temps d’aborder le cœur de l’exercice même si, vous l’avez compris, celui-ci n’est qu’un prétexte pour présenter la création d’application sous Android avec Xamarin.

Comme nous avons pu le comprendre dès l’introduction les choses ne se présentent pas aussi simplement qu’on pourrait le faire en XAML. Mais rien n’interdit d’être astucieux.

La solution la plus simple qui vient à l’esprit serait de positionner des boutons transparents sur les zones cliquables et de gérer leur Click. C’est simple à comprendre mais pas forcément aussi simple à réaliser.
En effet le placement des boutons ne peut se faire en pixels ou avec des “margins” comme on le fait en XAML. Les outils de mise en page sont différents et la variété des écrans à supporter rend le jeu difficile. Il existe bien une sorte de “canvas” XAML sous Android, surface sur laquelle on positionne les objets par des coordonnées X,Y mais ce conteneur est déprécié et ne doit plus être utilisé. Le foisonnement des résolutions et tailles des écrans ont eu raison de cette façon trop simpliste de procéder qui réclamerait finalement beaucoup de code et de calcul pour s’adapter à toutes les situations.

Il nous faut donc trouver autre chose. L’idée reprise par de nombreux auteurs consiste à gérer deux images. La première, visible, est l’image “cliquable” (du point de vue de l’utilisateur), la seconde pourrait être appelée le “masque des régions” mais un masque se place par dessus de quelque chose alors qu’ici cette image sera placée sous l’autre. Elle sera invisible pour l’utilisateur. On lui gardera le nom de “masque” pour simplifier en ayant conscience de cet abus de langage.

Cette image “masque des régions” consistera en une image de même taille que l’image utilisateur avec un fond uniforme blanc. Les zones seront simplement dessinées comme des formes pleines d’une couleur franche et unie : un rouge, un bleu, etc.

Pour bien comprendre regardons l’image “utilisateur” par défaut :

image

Trois zones cliquables sont définies, une première au niveau de la tuyère de la fusée, une seconde au niveau du hublot et une troisième à la place de l’étoile se trouvant au centre bas à gauche.

Voici à quoi ressemble l’image masque :

image

La bordure noire est ajoutée ici pour faire ressortir l’image et n’en fait pas partie.

Les trois zones sont définies par des rectangles de tailles différentes, ceci est arbitraire pour l’exercice même si ces zones tentent de recouvrir les trois objets (étoile, hublot et tuyère). En réalité ces formes seront parfois des rectangles, parfois des ellipses ou des cercles ou éventuellement le contour précis d’une forme de l’image principale. Vous avez bien entendu compris le principe.

On remarque que chaque forme possède une couleur et que celle-ci est différente des autres. Cela fait partie du mécanisme en permettant de savoir quelle zone a été cliquée.

La conception du masque peut s’avérer délicate mais dans une application moderne et bien designée tout (ou presque) doit passer par Photoshop et par quelqu’un qui sait s’en servir… Dans un tel cas ce n’est pas un problème. L’astuce reste abordable même à débutant, elle consiste en réalité à utiliser l’image “utilisateur” comme fond et à créer l’image masque comme un calque placé au dessus avec une transparence de calque pour voir le fond. Une fois le masque au point il suffit de copier son layer sur une nouvelle image et de la sauvegarder. Rien de sorcier.

Armés des deux images, l’image “utilisateur” et l’image “masque”, comment s’en servir pour résoudre notre problème ?

La première étape consiste à placer chaque image dans un ImageView, un contrôle Android spécialisé dans l’affichage des images. Ces deux conteneurs d’image sont placés dans un même conteneur générique de telle façon à ce que le masque soit en dessous et non visible. L’équivalent d’un “visibility=hidden” en XAML et non pas d’un “collapsed” et ce pour les mêmes raisons : dans ce dernier mode le système supprime l’image de l’arbre visuel pour gagner de la place, alors qu’en mode non visible elle reste à sa place mais sans être affichée et nous avons besoin que l’image masque soit exactement étirée et positionnée comme l’image utilisateur.

Une fois les deux images superposées il faut écouter l’évènement Touch de l’image “utilisateur”. Comme tout évènement de ce type on récupère des arguments qui contiennent des informations pertinentes, notamment la position X,Y du Touch (équivalent du MouseUp ou Down) et le sens de l’action (justement Up ou Down).

Partant de là, et puisque l’image se trouve exactement à la même position, zoomée exactement de la même façon, il nous faut récupérer le pixel se trouvant à la position indiquée mais dans l’image masque.

Ce pixel possède une couleur, et grâce à celle-ci nous savons quelle zone a été cliquée ou non ! Ensuite le programme décide de lancer les actions qu’il veut, par exemple passer à une autre page, exécuter un traitement, peu importe.

ici nous changerons tout simplement l’image “utilisateur” pour donner l’impression que le clic allume ou éteint la fusée, fait sortir l’alien de la fusée ou non, allume ou éteint l’étoile de gauche. Nous ferons apparaitre aussi trois cercles sur les trois zones cliquables pour faire comprendre à l’utilisateur que ces zones existent (et où elles sont placées) lorsqu’il clique sur une zone non cliquable (donc dans du “blanc” dans l’image masque).

Le résultat

l’application peut être lancée dans le simulateur pour la mettre au point, mais surtout pour quelque chose de visuel il est essentiel de la tester aussi sur un vrai smartphone. Ici j’ai utilisé mon Galaxy S3 mis en mode debug relié par USB au PC. Ca marche tout de suite. Le S3 possède un gestuel particulier qui permet de faire une capture écran, ce sont donc des captures du S3 ci-dessous et non des captures du simulateur.

Screenshot_2013-06-17-23-34-43

L’alien est sorti !

Screenshot_2013-06-17-23-34-53

Le moteur est allumé !

L’application fonctionne aussi bien en mode portait qu’en mode paysage mais elle rend mieux dans ce dernier d’où les captures effectuées dans ce mode.

Il y a toujours quelque chose de magique dans le fait de voir s’exécuter son code sur un vrai smartphone et non dans le simulateur… Et surtout cela m’a permis de voir qu'un oubli dans ce code faisait planter l’application sur le S3 alors que cela ne réagissait pas pareil sur le simulateur (j’avais oublié de faire un Dispose() de l’image intermédiaire utilisée pour obtenir le pixel “cliqué” et sur le smartphone réel, ça plante très vite !).

Le code

Le XML du layout se présente comme suit :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:p1="http://schemas.android.com/apk/res/android"
    p1:minWidth="25px"
    p1:minHeight="25px"
    p1:layout_width="fill_parent"
    p1:layout_height="fill_parent"
    p1:id="@+id/frameLayout1">
    <ImageView
        p1:src="@drawable/p2_ship_mask"
        p1:layout_width="fill_parent"
        p1:layout_height="fill_parent"
        p1:scaleType="fitCenter"
        p1:visibility="invisible"
        p1:id="@+id/ImageMask" />
    <ImageView
        p1:src="@drawable/p2_ship_default"
        p1:layout_width="fill_parent"
        p1:layout_height="fill_parent"
        p1:scaleType="fitCenter"
        p1:id="@+id/ImageMain" />
</FrameLayout>

 

Comme expliqué plus haut le layout est formé d’un conteneur générique, ici un FrameLayout conçu pour ne contenir généralement qu’un seul élément, ce qui nous arrange (une seule image est visible, la seconde ne pose pas de problème de mise en page puisqu’elle est cachée).

Dans ce conteneur on trouve deux ImageView, la première (donc la plus en dessous dans la pile visuelle) contiendra l’image masque, la seconde (qui est au dessus) contiendra l’image “utilisateur”.

Les deux images sont placées de la même façon et c’est très important : la position X,Y de tout point de l’image utilisateur doit absolument correspondre à la même position dans le masque. L’image masque est marquée “invisible” pour ne pas être affichée toutefois elle est présente et possède son espace propre.

Le code principal de l’Activité est le suivant :

[Activity(Label = "ClickArea", MainLauncher = true, 
Icon = "@drawable/icon", ScreenOrientation = ScreenOrientation.Sensor)] public class Activity1 : Activity { private ImageView imageView; private ImageView imageMask; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); // Set our view from the "main" layout resource SetContentView(Resource.Layout.Main); imageView = FindViewById<ImageView>(Resource.Id.ImageMain); imageMask = FindViewById<ImageView>(Resource.Id.ImageMask); if (imageView == null || imageMask == null) { Log.Debug("INIT", "images not found"); Finish(); return; } imageView.Touch += imageViewTouch; Toast.MakeText(this, "Touchez pour découvrir les zones", ToastLength.Long).Show(); }

 

deux variables sont utilisées pour localiser les deux contrôles images. Le OnCreate() commence par afficher l’image utilisateur par défaut puis initialise les deux variables en cherchant les contrôles dans le layout en cours.

L’évènement Touch de l’image principal est géré par la méthode imageViewTouch. En fin de OnCreate() un toast est affiché pour signaler à l’utilisateur qu’il peut toucher l’image pour découvrir les zones cliquables.

Rien de sorcier dans tout cela.

Le code de la gestion du Touch :

void imageViewTouch(object sender, View.TouchEventArgs e)
{
  if (imageView == null) return;
  if (e.Event.Action != MotionEventActions.Up) return;
  var touchColor = getHotSpotColor(e.Event.GetX(), e.Event.GetY());
  if (Tools.CloseMatch(Color.Red, touchColor)) 
     displayImage(Resource.Drawable.p2_ship_alien);
  else if (Tools.CloseMatch(Color.Blue, touchColor)) 
          displayImage(Resource.Drawable.p2_ship_powered);
  else if (Tools.CloseMatch(Color.Yellow, touchColor)) 
          displayImage(Resource.Drawable.p2_ship_no_star);
  else if (Tools.CloseMatch(Color.White, touchColor)) 
          displayImage(Resource.Drawable.p2_ship_pressed);
 }

 

Peu de subtilités ici : on contrôle l’action de Touch, la seule qui nous intéresse est le Up (quand le doigt est levé après avoir touché, c’est l’équivalent d’un Mouse button up) dans le cas contraire on sort de la méthode.

Le traitement de l’astuce des zones cliquables apparait ensuite : on obtient la couleur du point touché par l’utilisateur par la méthode getHotSpotColor() que nous verrons plus bas. Ensuite on teste si cette couleur est l’une de celles utilisées pour les trois zones cliquables (rouge, jaune et bleu), si la couleur est blanche (fond neutre du masque) on affiche l’image par défaut (par le biais de la méthode displayImage (à voir plus bas).

C’est très simple. La méthode Tools.CloseMatch() est conçue pour autoriser une certaine “dérive” de la couleur des pixels. Les ajustement de taille à l’écran effectués par Android peuvent en effet modifier légèrement la couleur d’un pixel. On accepte donc une tolérance sur la valeur testée.

Cette méthode est la suivante :

namespace ClickArea
{
    public static class Tools
    {
        public static bool CloseMatch(Color color1, Color color2, 
                                      int tolerance = 25)
        {
            return !((Math.Abs(color1.A - color2.A) > tolerance) ||
                     (Math.Abs(color1.R - color2.R) > tolerance) ||
                     (Math.Abs(color1.G - color2.G) > tolerance) ||
                     (Math.Abs(color1.B - color2.B) > tolerance));
        }
    }
}

 

La méthode getHotSpotColor() qui permet d’obtenir la couleur du pixel en X,Y :

private Color getHotSpotColor(float x, float y)
{
   imageMask.DrawingCacheEnabled = true;
   try
     {
       using (var hotspots = Bitmap.CreateBitmap(imageMask.GetDrawingCache(false)))
       {
         if (hotspots == null)
          {
            Log.Debug("HS", "hotspots are null.");
            imageMask.DrawingCacheEnabled = false;
            return Color.Black;
          }
          return new Color(hotspots.GetPixel((int)x, (int)y));
        }
     }
     finally { imageMask.DrawingCacheEnabled = false; }
}

 

La façon d’obtenir le pixel fait intervenir le cache image de l’image masque, l’obtention d’un bitmap à partir de ce cache et ensuite la lecture de la couleur du point X,Y dans la bitmap obtenue. Il est essentiel de relâcher cette dernière d’où l’utilisation d’un bloc “using” qui effectuera un Dispose() automatiquement. De la même façon un try/finally s’assure que le mode cache du contrôle image est bien repassé à false.

Les raisons de cette construction s’éloignent de l’objectif du présent article, nous aurons certainement l’occasion de revenir sur ce point d’une façon ou d’une autre une prochaine fois.

Conclusion

Voir l’application fonctionner sur un vrai smartphone change la donne, n’hésitez pas à brancher en USB votre téléphone pour toujours déboguer de cette façon. Mon PC est ultra rapide, ce n’est pas une configuration "standard", et même ainsi équipé l’émulateur Android est moins rapide que l’exécution du débogue sur mon Galaxy S3. Donc n’hésitez vraiment pas, l’émulateur n’est pas parfait comme tous les émulateurs mais surtout il est plus lent qu’un bon smartphone. Ne craignez pas de “polluer” votre téléphone, sous Android il n’y a pas de bricolage comme la Registry dont Microsoft n’arrive pas à se débarrasser : quand on désintalle une application, elle est supprimée sans laisser un million d’entrées dans des endroits sournois, Linux a toujours été plus sain de ce côté là que Windows… (Bien entendu si l’application s’amuse, avec les droits qui vont avec, à coller des fichiers de partout, il faudra les supprimer, mais ce n’est pas le cas de l’exemple étudié).

L’application que nous venons de voir n’est pas très sophistiquée, elle reste dans l’esprit de l’introduction : russe-tic Souriremais elle a permis de parler de plein de choses et surtout de vous montrer à quel point Xamarin 2.0 est une plateforme séduisante et pratique pour développer sous Android. Elle l’est aussi pour iOS mais je ne peux pas parler de tout…

J’espère que tout cela aura excité votre curiosité et vous aura prouvé que vous aussi vous pouvez franchir le pas !

Ce n’est pas fini alors… Stay Tuned !

blog comments powered by Disqus