Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

Xamarin.Forms : Utiliser des contrôles natifs avec binding !

Insérer des contrôles natifs directement en XAML ? Si c’est possible ! Avec support du Binding ? Encore oui ! Mais comment ? …

L’ouverture à son maximum, le respect des plateformes aussi !

Xamarin.Forms est livré avec plus de 40 contrôles (pages, conteneurs, contrôles…) qu’on peut combiner pour créer des interfaces utilisateur multi-plateformes natives avec un seul code centralisé. C’est déjà une prouesse ! Mais l’une des facettes qui rend Xamarin.Forms si puissant et si unique c’est que nous avons accès à 100% des contrôles et des API pour iOS, Android et Windows (et d’autres OS s’étant ajoutés avec le temps). Pour mettre en œuvre des fonctionnalités spécifiques à la plate-forme telles que la géolocalisation ou le Bluetooth, nous pouvons tirer parti du DependencyService. Pour ajouter des contrôles spécifiques à la plate-forme tels que FloatingActionButton sur Android, nous disposons de nombreuses options différentes, des rendus personnalisés aux effets en passant par l’intégration native .

L’équipe Xamarin.Forms va encore plus loin en nous proposant la déclaration de vue native qui permet d'ajouter des vues bindables iOS, Android et Windows directement au code XAML. Plutôt que d'avoir à écrire un moteur de rendu personnalisé, vous pouvez simplement ajouter le contrôle directement à XAML sans configuration supplémentaire.

Déclaration de vue native

Une vue native est un contrôle propre à la plateforme cible. Par exemple sous Xamarin.Forms nous avons des abstractions comme Label pour écrire un bout de texte, ce “Label” n’existe nulle part, il est remplacé à la compilation par un contrôle natif, c’est ce qui fait de Xamarin.Forms le seul environnement cross-plateformes respectueux de ces dernières. On peut, comme évoqué plus haut, modifier la façon dont le choix du contrôle est fait, ou en modifier le comportement (avec les Effets ou les Rendus personnalisés). Mais parfois on aimerait tout simplement utiliser directement et sans détour un contrôle natif.

Prenons le Switch par exemple. Il s’avère que ce contrôle est déjà présent sous Xamarin.Forms et qu’il est déjà traduit en natif, ce qui diminue un peu l’intérêt de le substituer à la main je vous l’accorde mais c’est un contrôle simple présent sur toutes les plateformes qui permettra d’illustrer le mécanisme de déclaration native de façon très simple. Bien entendu dans ce cas précis cela n’a guère d’intérêt puisque XF le fait tout seul, mais pour que l’exemple reste court et compréhensible c’est un bon choix. A vous d’imaginer d’importe quel autre contrôle natif à la place de celui-là dans l’exemple qui suit !

Une vue native est donc un contrôle présent uniquement sur une plateforme donnée. Il possède un nom de classe et des propriétés.

La déclaration de vue native va nous permettre d’insérer directement un tel contrôle dans le XAML d’une page, d’un ContentView ou autre comme si il s’agissait d’un contrôle Xamarin.Forms ! Et même mieux, les propriétés de ce contrôle native vont pouvoir supporter le Binding aussi directement qu’un contrôle Xamarin.Forms !

Exemple

J’aime commencer par la démo visuelle qui permet de mieux situer ce dont on parle. Avant de voir le code regardons ces deux run de la même application, sous Android et sous UWP (oui j’ai eu la flemme de mettre en route le Mac juste pour faire une capture écran… mais ça marche tout pareil).

NVAndroid


Attardons-nous un instant sur cette animation. En haut de la page nous avons un titre, utilisant un Label classique de XF. En dessous nous avons une zone de saisie, elle aussi tout ce qu’il y a de plus classique, un Entry Xamarin.Forms donc.

Sous le Entry se trouve une ligne “Enable Entry ?” se terminant par un switch. C’est là que les choses sont intéressantes :

  • Ce Switch est un contrôle natif sous chaque plateforme qui sera déclaré directement en XAML (à voir plus bas)
  • Mieux, l’état de disponibilité de l’Entry est lié par Binding à l’état du Switch !

Enfin on notera une ligne de texte “TextView uniquement sous Android !” qui est une ligne utilisant directement un TextView natif Android et qui ne sera visible QUE sous Android (puisque le TextView n’existe ni sous iOS ni sous UWP). Et comme nous n’avons pas multiplié les déclarations pour les autres plateformes, ce TextView n’apparaitra que dans la mise en page sous Android.

Le texte “Dot.Blog…”’ est une watermark ajoutée au GIF lors de la capture, cela n’a aucun intérêt pour la démo…

Regardons maintenant la même application lancée sous UWP :

NVUWP


S’agissant de la même application Xamarin.Forms on retrouve bien entendu exactement le même comportement et la même mise en page à quelques différences près (le texte du Switch est au-dessus de ce dernier sous UWP au lieu d’être sur la même ligne sous Android, ce qui pourrait s’arranger mais ce n’est pas le propos de ce papier).

Alors comment ça marche ?

Il est vrai, je le répète, que si vous utiliser un Switch Xamarin.Forms de façon classique vous ne verrez pas de différence avec cette démo car le Switch Xamarin.Forms est déjà traduit en natif. Mais il faut imaginer qu’on peut utilise n’importe quelle classe spécifique à chaque plateforme et que rien ne nous oblige à ce que le contrôle natif ait exactement le même aspect sous chaque plateforme. On peut par exemple utiliser un color picker très complet qu’une plateforme offrirait et ne mettre qu’un Entry pour saisir le code de la couleur sous une autre plateforme moins riche en contrôles…

Bon, comment ça marche donc ?

De la façon la plus simple qu’il soit… Tout tient dans la déclaration des bon namespaces XAML c’est tout…

Voici le code de la page XAML de l’exemple :

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
		xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
		xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"
		xmlns:androidWidget="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
        xmlns:androidLocal="clr-namespace:NativeSwitch.Droid;assembly=NativeSwitch.Droid;targetPlatform=Android"
        xmlns:formsandroid="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.Platform.Android;targetPlatform=Android"
		xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
		x:Class="NativeSwitch.NativeSwitchPage">
	<StackLayout Margin="20">
		<Label Text="Native Views Demo" FontAttributes="Bold" HorizontalOptions="Center" />
		<Entry Placeholder="This Entry is bound to the native switch" IsEnabled="{Binding IsSwitchOn}" />
		<ios:UISwitch On="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=ValueChanged}" 
		              OnTintColor="{x:Static ios:UIColor.Red}" 
		              ThumbTintColor="{x:Static ios:UIColor.Blue}" />
		<androidWidget:Switch x:Arguments="{x:Static androidLocal:MainActivity.Instance}" 
		                      Checked="{Binding Path=IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=CheckedChange}" 
		                      Text="Enable Entry?" />
		<win:ToggleSwitch Header="Enable Entry?" OffContent="No" OnContent="Yes" 
		                  IsOn="{Binding IsSwitchOn, Mode=TwoWay, UpdateSourceEventName=Toggled}" />
        <androidWidget:TextView 
            Text="TextView uniquement sous Android !" x:Arguments="{x:Static formsandroid:Forms.Context}" />
	</StackLayout>
</ContentPage>
Comme vous le voyez tout se trouve dans les namespaces XAML… et dans l’ajout ensuite de balises pour chaque contrôle comme s’il s’agissait de contrôles Xamarin.Forms…

Selon la plateforme les déclarations peuvent varier. pour UWP c’est d’une simplicité déconcertante, regarder la ligne “<win:ToggleSwtich…” pour vous en convaincre.

Pour iOS aussi les choses sont aussi simples qu’en UWP (ligne “<ios:UISwitch…”)

En revanche pour Android il y a une astuce à mettre en oeuvre, passer le contexte. On le voit dans les deux exemples Android (le TextViewx à la fin et le androidWidget:Switch). Pour passer le contexte on utilise une référence à l’instance de MainActivity (via l’extension XAML x:Static) mais pour accéder à cette instance il faut ajouter un namespace qu’on voit plus haut qui est “formsandroid”. Sinon le mécanisme est le même que pour les autre plateformes.

Conclusion

Remplacer un Switch n’a pas d’intérêt, Xamarin.Forms le fait déjà pour nous depuis toujours. Mais il faut imaginer à la place de ce Switch n’importe quel contrôle natif de n’importe quelle plateforme cible… Et là ça devient tout de suite très intéressant, au moins dans certains cas.

Et de toute façon, c’est une fois de plus une ouverture supplémentaire que nous offre Xamarin.Forms et il serait bien bête de ne pas l’apprécier à sa juste valeur !

Bon dev natif loin de tous les bricolages proposés ici et ailleurs, mais avec les Xamarin.Forms !

Stay Tuned !

blog comments powered by Disqus