Xamarin.Forms

Utiliser des images vectorielles SVG dans Xamarin.Forms

par
publié le
Image par krzysztof-m de Pixabay

Il est possible de longue date d'utiliser des bibliothèques tierces telles que Skiasharp pour afficher des images vectorielles dans Xamarin.Forms. Mais quand on est soucieux de la légèreté et des performances de son application, il est souvent préférable de se limiter aux fonctionnalités proposées par défaut par notre plateforme de développement.

Depuis l'apparition des Path avec la version 4.8, Xamarin.Forms est en mesure d'utiliser d'afficher des images vectorielles sans l'aide d'aucun autre package !

Je vous montre comment ça se path... euh... passe ?


Convertir le contenu d'un SVG en Path

Les fonctionnalités utilisées dans cet article nécessitent Xamarin.Forms 5.0 ou au minimum Xamarin.Forms 4.8 avec le flag expérimental : Device.SetFlags(new string[] { "Shapes_Experimental" }); dans le constructeur de la classe App

Alors oui, je vous ai un tout petit peu menti. On ne peut toujours pas incorporer un fichier svg et l'utiliser comme ressource. Mais un fichier svg n'étant que du texte balisé en XML, il est très facile d'en extraire le contenu et d'en faire une forme dans Xamarin.Form.

Nous prendrons deux exemples : un cas simple où l'image est constituée d'un seul chemin et un cas plus complexe où l'image est composée de plusieurs chemins.

Nous verrons ensuite comment rendre une image vectorielle réutilisable facilement dans le code sans duplication à l'aide des Styles.

Image simple composée d'un seul chemin

Nous allons partir de ce svg très simple représentant un avion :

Une image vectorielle simple
Une image vectorielle simple

Si vous ouvrez le fichier avec un éditeur de texte, vous y trouverez ceci :

<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 64 64" version="1.1" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
    <path d="M15,6.8182L15,8.5l-6.5-1 l-0.3182,4.7727L11,14v1l-3.5-0.6818L4,15v-1l2.8182-1.7273L6.5,7.5L0,8.5V6.8182L6.5,4.5v-3c0,0,0-1.5,1-1.5s1,1.5,1,1.5v2.8182 L15,6.8182z"/>
</svg>

Ce qui nous intéresse ici, c'est la balise path et en particulier son attribut d contenant le tracé du dessin. Nous allons simplement copier le contenu de d dans la propriété Data d'un Path Xamarin.Forms. Comme ceci : 

<Path Aspect="Uniform"
      Data="M15,6.8182L15,8.5l-6.5-1 l-0.3182,4.7727L11,14v1l-3.5-0.6818L4,15v-1l2.8182-1.7273L6.5,7.5L0,8.5V6.8182L6.5,4.5v-3c0,0,0-1.5,1-1.5s1,1.5,1,1.5v2.8182 L15,6.8182z"
      Fill="Black"
      HeightRequest="64"  />

La propriété Aspect avec la valeur Uniform conserve les proportions de l'image. La propriété Fill indique la couleur de remplissage de la forme.

Sans entrer trop loin dans les détails, il est très facile de manipuler l'image, par exemple en modifier le contour, le remplissage ou lui appliquer des transformations.

La même image vectorielle agrandie x2, pivotée de 45° avec un fond vert et un contour pointillé noir.

<Path Aspect="Uniform"
      Data="M15,6.8182L15,8.5l-6.5-1 l-0.3182,4.7727L11,14v1l-3.5-0.6818L4,15v-1l2.8182-1.7273L6.5,7.5L0,8.5V6.8182L6.5,4.5v-3c0,0,0-1.5,1-1.5s1,1.5,1,1.5v2.8182 L15,6.8182z"            
      Fill="Green"
      HeightRequest="64"
      Rotation="45"
      Stroke="Black"
      Scale="2"
      StrokeDashArray="2 1"
      StrokeThickness="2" />

Le résultat sous Android :

Une image vectorielle, affichée et manipulée dans Xamarin.Forms
Une image vectorielle, affichée et manipulée dans Xamarin.Forms

Image complexe composée de plusieurs chemins

Malheureusement, les images vectorielles ne sont pas toujours aussi simples et sont parfois composées de plusieurs chemins. Par exemple celle-ci :

<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
    <path d="M104,320a24,24,0,1,0,24,24A24.028,24.028,0,0,0,104,320Zm0,32a8,8,0,1,1,8-8A8.009,8.009,0,0,1,104,352Z"/>
    <path d="M408,320a24,24,0,1,0,24,24A24.028,24.028,0,0,0,408,320Zm0,32a8,8,0,1,1,8-8A8.009,8.009,0,0,1,408,352Z"/>
    <path d="M488,304V256a48.051,48.051,0,0,0-48-48H373.54l-22-58.68A43.052,43.052,0,0,0,310.7,120H160a43.044,43.044,0,0,0-40.72,28.97L90.96,208H72a48.051,48.051,0,0,0-48,48v48A16.021,16.021,0,0,0,8,320v16a16.021,16.021,0,0,0,16,16H48.58a55.994,55.994,0,0,0,110.84,0H352.58a55.994,55.994,0,0,0,110.84,0H488a16.021,16.021,0,0,0,16-16V320A16.021,16.021,0,0,0,488,304ZM356.46,208H256V160h82.46ZM40,264H56v8H40Zm8.58,72H24V320H53.41A55.5,55.5,0,0,0,48.58,336ZM104,384a40,40,0,1,1,40-40A40.04,40.04,0,0,1,104,384Zm248.58-48H159.42a55.5,55.5,0,0,0-4.83-16H357.41A55.5,55.5,0,0,0,352.58,336ZM408,384a40,40,0,1,1,40-40A40.04,40.04,0,0,1,408,384Zm40-80a7.337,7.337,0,0,0-.81.05,55.871,55.871,0,0,0-78.37-.01A7.383,7.383,0,0,0,368,304H144a7.383,7.383,0,0,0-.82.04,55.871,55.871,0,0,0-78.37.01A7.337,7.337,0,0,0,64,304H40V288H64a8,8,0,0,0,8-8V256a8,8,0,0,0-8-8H41.01A32.058,32.058,0,0,1,72,224H96a8,8,0,0,0,7.21-4.54l30.71-64a7.5,7.5,0,0,0,.37-.93A27.079,27.079,0,0,1,160,136H310.7a27.027,27.027,0,0,1,19.22,8H160a8,8,0,0,0-8,8v64a8,8,0,0,0,8,8H440a32.058,32.058,0,0,1,30.99,24H448a8,8,0,0,0-8,8v24a8,8,0,0,0,8,8h24v16ZM240,160v48H168V160ZM472,264v8H456v-8Zm16,72H463.42a55.5,55.5,0,0,0-4.83-16H488Z"/>
    <path d="M256,248h40a8,8,0,0,0,0-16H256a8,8,0,0,0,0,16Z"/>
</svg>

Malheur ! l'image contient plusieurs chemins mais notre objet Path n'a qu'une seule propriété Data ! Comment faire ?

Heureusement, la propriété Data de notre Path peut contenir bien plus qu'une simple chaîne de caractère, notamment un groupe de géométrie. Nous allons donc créer un GeometryGroup contenant des PathGeometry dont nous renseigneront la propriété Figures.

Chaque path de notre svg alimente ainsi les propriétés Figures de nos PathGeometry :

<Path Aspect="Uniform"
      Fill="Black"
      HeightRequest="64">
    <Path.Data>
        <GeometryGroup>
            <PathGeometry Figures="M104,320a24,24,0,1,0,24,24A24.028,24.028,0,0,0,104,320Zm0,32a8,8,0,1,1,8-8A8.009,8.009,0,0,1,104,352Z" />
            <PathGeometry Figures="M408,320a24,24,0,1,0,24,24A24.028,24.028,0,0,0,408,320Zm0,32a8,8,0,1,1,8-8A8.009,8.009,0,0,1,408,352Z" />
            <PathGeometry Figures="M488,304V256a48.051,48.051,0,0,0-48-48H373.54l-22-58.68A43.052,43.052,0,0,0,310.7,120H160a43.044,43.044,0,0,0-40.72,28.97L90.96,208H72a48.051,48.051,0,0,0-48,48v48A16.021,16.021,0,0,0,8,320v16a16.021,16.021,0,0,0,16,16H48.58a55.994,55.994,0,0,0,110.84,0H352.58a55.994,55.994,0,0,0,110.84,0H488a16.021,16.021,0,0,0,16-16V320A16.021,16.021,0,0,0,488,304ZM356.46,208H256V160h82.46ZM40,264H56v8H40Zm8.58,72H24V320H53.41A55.5,55.5,0,0,0,48.58,336ZM104,384a40,40,0,1,1,40-40A40.04,40.04,0,0,1,104,384Zm248.58-48H159.42a55.5,55.5,0,0,0-4.83-16H357.41A55.5,55.5,0,0,0,352.58,336ZM408,384a40,40,0,1,1,40-40A40.04,40.04,0,0,1,408,384Zm40-80a7.337,7.337,0,0,0-.81.05,55.871,55.871,0,0,0-78.37-.01A7.383,7.383,0,0,0,368,304H144a7.383,7.383,0,0,0-.82.04,55.871,55.871,0,0,0-78.37.01A7.337,7.337,0,0,0,64,304H40V288H64a8,8,0,0,0,8-8V256a8,8,0,0,0-8-8H41.01A32.058,32.058,0,0,1,72,224H96a8,8,0,0,0,7.21-4.54l30.71-64a7.5,7.5,0,0,0,.37-.93A27.079,27.079,0,0,1,160,136H310.7a27.027,27.027,0,0,1,19.22,8H160a8,8,0,0,0-8,8v64a8,8,0,0,0,8,8H440a32.058,32.058,0,0,1,30.99,24H448a8,8,0,0,0-8,8v24a8,8,0,0,0,8,8h24v16ZM240,160v48H168V160ZM472,264v8H456v-8Zm16,72H463.42a55.5,55.5,0,0,0-4.83-16H488Z" />
            <PathGeometry Figures="M256,248h40a8,8,0,0,0,0-16H256a8,8,0,0,0,0,16Z" />
        </GeometryGroup>
    </Path.Data>
</Path>

Et hop :

Nous voilà capable d'afficher des images vectorielles simples ou complexes directement dans Xamarin.Forms. Mais pour l'instant, cela reste fastidieux : pour utiliser une même image à plusieurs endroits, il reste nécessaire de copier-coller tout le contenu du Path à chaque fois. 

Voyons comment résoudre ce problème avec Style !

Créer une image vectorielle réutilisable grâce aux styles

Nous allons créer un ResourceDictionary pour y placer des Styles définissant le tracé de chaque image vectorielle.

Toutes nos images SVG auront des propriétés communes, nous allons donc commencer par créer un style de base :

<Style x:Key="BaseSVGStyle" TargetType="Path">
    <Setter Property="Aspect" Value="Uniform" />
    <Setter Property="Fill" Value="Black" />
</Style>

Nous allons ensuite créer un style par image héritant de BaseSVGStyle à l'aide de la propriété BasedOn. Voici un exemple :

<Style x:Key="PlaneSVG"
        BasedOn="{StaticResource BaseSVGStyle}"
        TargetType="Path">
    <Setter Property="Data" Value="m430.37 48.711c29.15-29.153-11.17-67.267-39.96-38.479l-106.3 106.3-243.52-64.372-40.59 40.595 200.71 105.71-81.18 81.17-63.104-7.75-32.106 32.11 71.595 37.64 37.645 71.6 32.1-32.11-7.38-62.74 81.18-81.17 103.43 199.39 40.6-40.6-61.63-238.58 108.51-108.71z" />
    <Setter Property="HeightRequest" Value="64" />
</Style>
    
<Style x:Key="CarSVG"
        BasedOn="{StaticResource BaseSVGStyle}"
        TargetType="Path">
    <Setter Property="HeightRequest" Value="32" />
    <Setter Property="Data">
        <Setter.Value>
            <GeometryGroup>
                <PathGeometry Figures="M104,320a24,24,0,1,0,24,24A24.028,24.028,0,0,0,104,320Zm0,32a8,8,0,1,1,8-8A8.009,8.009,0,0,1,104,352Z" />
                <PathGeometry Figures="M408,320a24,24,0,1,0,24,24A24.028,24.028,0,0,0,408,320Zm0,32a8,8,0,1,1,8-8A8.009,8.009,0,0,1,408,352Z" />
                <PathGeometry Figures="M488,304V256a48.051,48.051,0,0,0-48-48H373.54l-22-58.68A43.052,43.052,0,0,0,310.7,120H160a43.044,43.044,0,0,0-40.72,28.97L90.96,208H72a48.051,48.051,0,0,0-48,48v48A16.021,16.021,0,0,0,8,320v16a16.021,16.021,0,0,0,16,16H48.58a55.994,55.994,0,0,0,110.84,0H352.58a55.994,55.994,0,0,0,110.84,0H488a16.021,16.021,0,0,0,16-16V320A16.021,16.021,0,0,0,488,304ZM356.46,208H256V160h82.46ZM40,264H56v8H40Zm8.58,72H24V320H53.41A55.5,55.5,0,0,0,48.58,336ZM104,384a40,40,0,1,1,40-40A40.04,40.04,0,0,1,104,384Zm248.58-48H159.42a55.5,55.5,0,0,0-4.83-16H357.41A55.5,55.5,0,0,0,352.58,336ZM408,384a40,40,0,1,1,40-40A40.04,40.04,0,0,1,408,384Zm40-80a7.337,7.337,0,0,0-.81.05,55.871,55.871,0,0,0-78.37-.01A7.383,7.383,0,0,0,368,304H144a7.383,7.383,0,0,0-.82.04,55.871,55.871,0,0,0-78.37.01A7.337,7.337,0,0,0,64,304H40V288H64a8,8,0,0,0,8-8V256a8,8,0,0,0-8-8H41.01A32.058,32.058,0,0,1,72,224H96a8,8,0,0,0,7.21-4.54l30.71-64a7.5,7.5,0,0,0,.37-.93A27.079,27.079,0,0,1,160,136H310.7a27.027,27.027,0,0,1,19.22,8H160a8,8,0,0,0-8,8v64a8,8,0,0,0,8,8H440a32.058,32.058,0,0,1,30.99,24H448a8,8,0,0,0-8,8v24a8,8,0,0,0,8,8h24v16ZM240,160v48H168V160ZM472,264v8H456v-8Zm16,72H463.42a55.5,55.5,0,0,0-4.83-16H488Z" />
                <PathGeometry Figures="M256,248h40a8,8,0,0,0,0-16H256a8,8,0,0,0,0,16Z" />
            </GeometryGroup>
        </Setter.Value>
    </Setter>
</Style>

Les styles consistent donc principalement à définir la valeur de la propriété Data de nos Path.

Pour les utiliser dans nos Views, nous devons d'abord déclarer le dictionnaire de ressource. Soit dans les ressources de la page, soit dans App.xaml si l'on souhaite accéder aux images partout dans le projet (PathesStyles est le nom de mon dictionnaire de ressource).

<ContentPage.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <pathes:PathesStyles />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</ContentPage.Resources>

Ensuite, dans la contenu de la page, c'est simple comme Bonjour , on applique le style souhaité à notre path :

<Path Style="{StaticResource CarSVG}" />

Il est toujours possible, bien entendu, de surcharger les valeurs proposées par le style. Pour afficher l'image en rouge par exemple : 

<Path Style="{StaticResource CarSVG}" Fill="Red" />
Un florilège d'images vectorielles dans Xamarin.Forms
Un florilège d'images vectorielles dans Xamarin.Forms

Pour la démo, les valeurs sont en dur dans le code, mais bien entendu toutes les propriétés sont Bindables pour en modifier les valeurs à l'exécution. Votre créativité fera le reste !

Projet GitHub

Retrouvez la démonstration de l'utilisation des Path Xamarin.Forms pour afficher des images vectorielles dans mon dépôt GitHub.

Pensez bien à liker et partager pour que ce soit utile au plus grand nombre !