Utiliser des images vectorielles SVG dans Xamarin.Forms
par Sylvain publié leIl 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 :
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 :
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" />
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 !