Dans cet article "from scratch" en deux parties, je vous présente une manière simple de créer une image circulaire avancée et réutilisable à partir de zéro, sans custom renderer, ni bibliothèque tierce.
Partie 1 :
- Créer une image circulaire à l'aide de la propriété
Clip
et desGeometry
récemment apparues dans Xamarin.Forms - Afficher un indicateur d'activité pendant le chargement de l'image
- Afficher une image de substitution en cas d'absence d'image ou d'url invalide en utilisant la technique de superposition dans une
Grid
- Afficher une bordure autour de l'image à l'aide des
Shape
, autre nouveauté de Xamarin.Forms - Utiliser des fonctionnalités expérimentales avec la version stable actuelle de Xamarin.Forms
Partie 2 :
-
Créer un contrôle réutilisable et compatible MVVM
- Définir des propriétés bindables (
BindableProperty
) - Utiliser les
Converters
- Définir des propriétés bindables (
Les présentations sont faites, place au concret.
Certaines fonctionnalités comme les Shapes et leurs Geometries nécessitent Xamarin.Forms 4.8 ou supérieure, pensez bien à mettre-à-jour le package après avoir créé votre solution !
Commençons par la fin
Pas de suspens, je vous livre d'emblée un visuel pour que vous sachiez où je vous amène. Celui-ci simule une page de contact :
Niveau contenu, la page affiche une liste de contacts dont chaque photo provient d'une source différente :
- Une photo chargée à partir d'une url
- Une photo ajoutée au projet en tant que ressource
- Pas de photo
- Une photo pointant sur une url retournant une erreur 404
- Une photo pointant sur une url inaccessible
Les trois derniers cas présentent une image de substitution par défaut car aucune photo valide ne peut être affichée.
Les contacts enregistrés comme favoris sont signalés par une bordure jaune autour de la photo.
Pour finir, si vous observez bien l'animation vous remarquerez un bref indicateur de chargement sur les deux dernières photos, le temps que Xamarin.Forms échoue à charger les images.
Mais je vous vois frétiller d'impatience, vous êtes venu ici pour voir du code, alors allons-y
Clip clip clip, découpage de l'image
Plutôt que de vous livrer de gros blocs de code illisibles sur une page web, je vais dans cet article aller directement à l'essentiel, vous trouverez le code complet sur mon compte GitHub, comme d'habitude.
Pour commencer, comment afficher une image circulaire ?
Nous allons utiliser deux fonctionnalités apparues dernièrement dans Xamarin.Forms, la propriété Clip (découper en anglais) et les Geometries.
Clip
est une propriété des VisualElements, de type Geometry
et qui définit le contour du contenu d'un élément visuel.
Ce n'est pas clair ? Un exemple, celui qui nous concerne : j'ai une image carrée et je souhaite afficher uniquement son contenu inscrit dans un cercle. Je vais donc définir une Geometry
circulaire et l'appliquer à Clip
pour découper l'image selon cette forme.
Attention les yeux, voici le code :
<Image Source="monimage.png" width="200" Height="200">
<Image.Clip>
<EllipseGeometry RadiusX="100"
RadiusY="100"
Center="100,100" />
</Image.Clip>
</Image>
La géométrie circulaire n'existant pas, nous nous baserons sur une ellipse. Celle-ci se définit à partir d'un centre et de deux rayons, horizontal (RadiusX
) et vertical (RadiusY
). Pour obtenir un cercle, il suffit que RadiusX
et RadiusY
aient la même valeur. Center
permet de positionner l'ellipse par rapport à son contenant.
Pour simplifier le calcul et le positionnement, autant partir d'une image carrée.
Pour obtenir un cercle centré et inscrit dans le carré, les rayons seront moitié plus petits que le côté de l'image et le centre du cercle sera au centre de l'image.
Voilà, obtenir une image circulaire c'est devenu aussi simple que ça avec Xamarin.Forms. Beaucoup de texte et d'explications pour quelque chose devenu finalement assez trivial !
L'image de substitution
Comment faire pour afficher une image par défaut quand il n'y a pas d'image à afficher ?
On va utiliser une technique ancienne et bien connue mais toujours très utile : l'empilement dans une Grid
. Les éléments contenus dans une même cellule d'une Grid se superposent les uns sur les autres, le dernier déclaré dans le fichier Xaml étant au-dessus, la lecture de celui-ci étant séquentielle.
Nous allons donc définir une Grid à une seule cellule et y ajouter deux Images
: la première est destinée à afficher l'image de substitution, la seconde à afficher l'image proprement dite.
Pour que les deux images se superposent sans décalage, il est nécessaire que les deux composants Image
et leurs ellipses aient les mêmes dimensions.
Nous renseignerons également les propriétés HorizontalOptions
et VerticalOptions
de la Grid avec des valeurs qui forceront celle-ci à adopter les mêmes dimensions que son contenu.
DRY - Je vais avoir besoin de créer deux fois une image circulaire simple, et il est probable que ce besoin se répète dans d'autres contextes. J'ai donc créé un contrôle
CircleImage
simple encapsulant le code précédent. Nous verrons dans la seconde partie de l'article comment créer un contrôle réutilisable.
<Grid HorizontalOptions="Center" VerticalOptions="Center">
<local:CircleImage Source="monPlaceholder.png" ImageSize="128" />
<local:CircleImage Source="monImage.png" ImageSize="128" />
</Grid>
De cette manière, l'image vient cacher le placeholder si elle est disponible, sinon on voit ce dernier à la place.
L'indicateur de chargement
Aujourd'hui c'est atelier empilage, nous allons encore superposer : ajoutons un ActivityIndicator
à notre Grid.
L'ActivityIndicator
a un visuel légèrement différent sous Android et iOS mais le principe reste le même : c'est un bidule qui tourne de façon hypnotique pour faire oublier le temps qui passe à l'utilisateur. Pour le voir tourner, il suffit d'affecter True
à sa propriété IsRunning
.
<Grid>
<local:CircleImage x:Name="monPlaceholder" ImageSize="128" />
<local:CircleImage x:Name="monImage" ImageSize="128" />
<ActivityIndicator IsRunning="True"
VerticalOptions="Center"
HorizontalOptions="Center"
/>
</Grid>
Nous avons donc un indicateur qui tourne au centre de notre image. C'est bien, mais il tourne en permanence et nous aurions besoin de l'activer uniquement pendant le chargement de l'image.
Cela tombe bien, le contrôle Image
expose une propriété IsLoading
qui vaut True
pendant le chargement de l'image et False
une fois le chargement terminé, peu importe que l'opération ce soit bien déroulée ou non.
Reste une question : comment lier la propriété IsRunning
de l'indicateur à la propriété IsLoading
de l'image ? Par un Binding
bien entendu ! Mais comment binder une propriété d'un contrôle à une propriété d'un autre contrôle ? En précisant la source du Binding !
Le plus simple ici est de donner un nom explicite à notre contrôle Image (monImage par exemple) et de se référer à ce nom comme source du Binding. La propriété visée sera définie comme Path
du Binding : "{Binding Source={x:Reference monImage}, Path=IsLoading}"
Avec un peu plus de contexte, cela donne :
<Grid>
<local:CircleImage x:Name="monPlaceholder" ImageSize="128" />
<local:CircleImage x:Name="monImage" ImageSize="128" />
<ActivityIndicator IsRunning="{Binding Source={x:Reference monImage}, Path=IsLoading}"
VerticalOptions="Center"
HorizontalOptions="Center"
/>
</Grid>
De cette façon, nous verrons un indicateur de chargement tournoyer au centre de l'image durant le chargement de celle-ci.
La bordure
Pour afficher une bordure autour de l'image, nous allons tirer parti des Shapes
encore sous forme expérimentale sous Xamarin.Forms 4.8.
Si la version de Xamarin.Forms que vous utilisez n'intègre pas encore les Shapes
de façon officielle, vous devrez déclarer la fonctionnalité expérimentale dans le constructeur de la classe App.xaml.cs
sous peine de lever une InvalidOperationException
au lancement de l'application :
public App()
{
Device.SetFlags(new string[] { "Shapes_Experimental" });
InitializeComponent();
MainPage = new MainPage();
}
Les Shapes
comme leur nom l'indique sont des formes que l'on peut ajouter en tant que VisualElement
sur la page. La forme qui nous intéresse est là encore une Ellipse
.
Attention à ne pas confondre l'
EllipseGeometry
qui est la description d'un objet géométrique, avec l'Ellipse
qui est un contrôle Visuel !
Stroke
et StrokeThickness
sont les deux propriétés exposées par l'Ellipse qui définissent la couleur et l'épaisseur de son contour. Nous n'aurons pas besoin des autres propriétés, mais nous pourrions affiner en définissant le type de contour (pointillé...) ou la couleur de remplissage de l'ellipse.
<Ellipse Stroke="Yellow" StrokeThickness="2" />
Ensuite, c'est juste une histoire d'empilage dans la Grid
, vous commencez à avoir l'habitude.
<Grid>
<local:CircleImage x:Name="monPlaceholder" ImageSize="128" />
<local:CircleImage x:Name="monImage" ImageSize="128" />
<Ellipse Margin="0"
HorizontalOptions="Center"
VerticalOptions="Center"
Stroke="Yellow"
StrokeThickness="2"
HeightRequest="128"
WidthRequest="128"
/>
<ActivityIndicator [...] />
</Grid>
Ce qui donne :
Fin de (première) partie
L'article s'avère bien plus détaillé que je ne l'avais prévu au départ et ça commence à être bien consistant. Je vous laisse donc digérer tout ça et vous donne rendez-vous pour la seconde partie dans laquelle nous verrons comment assembler tout ceci pour en faire un contrôle réutilisable avec des propriétés Bindable pour le rendre "MVVM friendly".
Top comments (0)