Comment créer un composant personnalisé avec Xamarin.Forms ?

Gaëtan Marrot
BeTomorrow
Published in
6 min readNov 20, 2018

Xamarin.Forms est une librairie complémentaire du framework Xamarin permettant d’écrire des interfaces utilisateurs cross-platform. L’un de ses avantages est qu’elle propose un rendu natif sur les différents systèmes ciblées par vos applications.
Elle fournit de base un certain nombre de composants graphiques mais il arrive assez vite qu’on ressente le besoin de les personnaliser ou même d’en créer de nouveaux nous-même.

Je vous propose dans cet article de voir quelques bases pour personnaliser un composant en utilisant la version 3.3.0 de Xamarin.Forms.

Le composant Xamarin.Forms

Imaginons que nous ayons dans notre application un bouton dont on souhaite personnaliser les couleurs de fond et de texte utilisées pour l’effet appuyé. Le bouton proposé par la librairie ne permettant pas de réaliser cela, nous devons passer par un composant personnalisé.

Notre objectif va donc être d’avoir un bouton utilisable dans une page XAML selon le format suivant :

Afin d’obtenir cela, nous allons commencer par créer notre classe PressedStateButton :

Notre nouveau composant va hériter du bouton de Xamarin.Forms afin d’avoir accès à ses propriétés et comportements de base.

Nos deux nouvelles propriétés sont définies en tant que BindableProperty. Les composants graphiques définissent ainsi leurs propriétés afin qu’elles puissent être affectées au moyen de styles Xamarin.Forms et que leurs changements de valeurs puissent être écoutés. Dans notre cas, nous avons défini deux propriétés de type Color ayant pour valeur par défaut Color.Default.

Allons un peu plus loin dans ce composant. Le bouton Xamarin.Forms a la particularité de ne pas avoir le même style par défaut sur Android et iOS (quand aucune propriété n’est surchargée). Nous allons ajouter d’autres propriétés en lecture seule pour faire en sorte d’avoir un même style par défaut entre les plateformes et gérer le fait que la valeur Color.Default ne soit pas une couleur valide.
Cette étape de mutualisation n’est pas obligatoire mais elle permet de simplifier le code spécifique à chaque plateforme et d’éviter les différences non souhaitées.

Nous modifions donc notre composant en conséquence :

Il possède désormais quatre nouvelles propriétés en lecture seule :

  • ActualBackgroundColor qui va nous permettre de choisir la couleur de fond par défaut du bouton lorsque BackgroundColor vaut Color.Default.
  • ActualPressedBackgroundColor qui définit la couleur utilisée pour le fond du bouton dans son état appuyé. Si PressedBackgroundColor vaut Color.Default, on lui affecte la couleur de fond à laquelle on applique une opacité.
  • ActualTextColor dont le rôle est de définir une couleur de texte par défaut si TextColor vaut Color.Default.
  • ActualPressedTextColor pour la couleur de texte du bouton lorsqu’il est dans son état appuyé. Si PressedTextColor vaut Color.Default, alors cette propriété aura comme valeur la couleur du dans son état normal.

Pour gérer la mise à jour de ces nouvelles propriétés, nous avons deux méthodes mettant respectivement à jour les couleurs de fond et de texte.
Elles sont appelées :

  • à la construction du composant pour prendre les bonnes valeurs par défaut.
  • lorsqu’une des propriétés dont elles dépendent change grâce à la surcharge de la méthode OnPropertyChanged, appelée lorsqu’une des BindableProperty du composant voit sa valeur modifiée.

Le rendu natif de notre composant

Une fois le composant créé, il va falloir faire en sorte que les affectations et changements de ses nouvelles propriétés soient transmis à la vue native associée à notre bouton.
Pour réaliser cela, nous allons devoir définir un “renderer” dans le projet de chaque plateforme de notre application et l’associer à notre composant.

Comme notre bouton hérite de celui de Xamarin.Forms, nous allons faire en sorte que nos renderers héritent également du renderer de ce bouton.

Par convention, les renderers sont nommés selon le format <nom_du_composant>Renderer. Nous pouvons également en trouver une liste dans la documentation de Xamarin.Forms.
Nous allons donc hériter du ButtonRenderer de chaque plateforme.

Commençons par créer les classes de nos renderers :

Nous les faisons hériter de leurs renderers parents et nous ajoutons une instruction d’assembly au-dessus du namespace de la classe :

[assembly: ExportRenderer(typeof(PressedStateButton), typeof(PressedStateButtonRenderer))]

Cette instruction a pour but d’associer notre composant avec son renderer. C’est grâce à elle que Xamarin.Forms saura qu’il faut utiliser le PressedStateButtonRenderer à chaque fois qu’il réalise le rendu d’un PressedStateButton.

Nous allons maintenant pouvoir utiliser nos propriétés supplémentaires dans chaque renderer afin de mettre à jour la vue native. Pour cela, nous avons besoin de surcharger deux méthodes de nos renderers :

  • void OnElementChanged(ElementChangedEventArgs<Button> e) qui est appelée lorsqu’un composant Xamarin.Forms est associé au renderer. Elle nous permet donc de définir l’état initial de notre bouton natif.
  • void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) qui est appelée lorsqu’une propriété du composant a changé.

Nos renderers possèdent également deux propriétés dont nous allons nous servir :

  • Element qui permet de récupérer le composant Xamarin.Forms actuellement associé à notre renderer.
  • Control qui permet de récupérer la vue native associée au renderer. Dans notre cas, il s’agit d’un UIButton sous iOS et d’un Button sous Android.

Avec ces éléments, nous pouvons maintenant mettre à jour notre renderer Android :

Nous ajoutons deux méthodes mettant respectivement à jour les couleurs de fond et les couleurs de texte du bouton. Nous les appelons ensuite lors de l’affectation d’un nouvel élément Xamarin.Forms et lorsqu’une des propriétés utilisées dans la méthode change.

Au niveau de la mise à jour des couleurs de fond du bouton Android, nous passons par un StateListDrawable qui nous permet de définir une couleur pour l’état appuyé du bouton et une autre comme couleur par défaut pour tous les autres états.

La mise à jour des couleurs de texte fonctionne sur le même principe mais en utilisant une ColorStateList pour définir les couleurs en état appuyé et par défaut.

Passons maintenant au renderer iOS.

Par défaut, l’UIButton créé par le ButtonRenderer de Xamarin.Forms est de type UIButtonType.System ce qui ne permet pas de personnaliser la couleur du texte de l’effet appuyé. Il va donc faire en sorte que le type du bouton natif soit UIButtonType.Custom pour notre renderer.

Pour cela, Xamarin.Forms propose une méthode que l’on peut redéfinir depuis sa version 3.0 : TNativeView CreateNativeControl(). Cette méthode est appelée par le renderer pour créer la vue native dont il va se servir. Elle existe aussi bien sous iOS que sous Android.

Modifions notre renderer pour gérer les différentes couleurs de texte :

En plus de la surcharge de la méthode de création de la vue native, nous avons créé une méthode pour la mise à jour de chaque couleur de texte. Chacune appelle la méthode SetTitleColor de l’UIButton pour affecter sa couleur à l’état correspondant.
Ces deux méthodes sont appelés à l’affectation d’un nouveau composant Xamarin.Forms et lors de la mise à jour de la couleur de texte qu’elles utilisent.

Il ne nous reste plus qu’à gérer les couleurs de fond de notre bouton natif. Le composant UIButton que nous utilisons ne possédant pas de méthode pour affecter une couleur de fond pour un état donné, nous allons devoir le gérer manuellement.

Commençons par créer des variables dans notre renderer pour stocker les couleurs des états du bouton et une méthode mettant à jour la couleur de fond de l’élément natif suivant l’état de ce dernier.
À cela, nous pouvons ajouter les méthodes de mises à jour de chaque variable lors de l’affectation du composant Xamarin.Forms au renderer et lors de la mise à jour de ses propriétés. Ces deux méthodes en profiteront pour mettre à jour la couleur de fond du bouton natif en fonction de son état courant.

Il va ensuite nous falloir gérer le changement de couleur de fond du bouton lorsqu’on appuie dessus. Pour cela, nous allons nous brancher sur les différents évènements “touch” de l’UIButton :

Nous avons donc modifié notre surcharge de la méthode CreateNativeControl pour nous abonner sur les évènements du bouton natif. De plus, pour éviter tout problème de fuite mémoire, nous surchargeons la méthode Dispose du renderer pour nous désabonner de ces évènements lorsque le renderer va être libéré de la mémoire.

Au niveau de la gestion du “touch” sur le bouton, nous considérons qu’il est appuyé lorsqu’il reçoit un évènement TouchDown, et qu’il ne l’est plus suite aux évènements TouchUpInside, TouchUpOutside ou TouchCancel.

Conclusion

Nous avons ainsi réalisé un bouton personnalisé pour Xamarin.Forms. Les méthodes surchargées dans nos renderers sont disponibles dans tous les renderers proposés par Xamarin.Forms et servent donc de base à tout composants personnalisés.

Il reste cependant des cas particuliers comme les composants de type layout, qui possèdent des vues filles. Mais ceci fera l’objet d’un prochain article.

Vous avez aimé cet article ? Cliquez sur 👏 en bas de page pour que d’autres lecteurs puissent le trouver !

BeTomorrow est une agence de conseil, design et développement. Pour aller plus loin, nous vous invitons à lire nos derniers articles ou nous contacter directement.

--

--