Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Xamarin.Forms : l’AbsoluteLayout trop souvent ignoré ?

Parmi les nombreuses stratégies de mise en page offertes l’AbsoluteLayout reste trop souvent  ignorée. Pourquoi ? Et, le mérite-t-elle ? C’est ce que nous allons tenter de voir dans ce billet..

AbsoluteLayout, le layout mal aimé ?

Alors autant le dire d’emblée je ne dispose pas de statistiques officielles et mondiales sur l’utilisation des contrôles de mise en page sous Xamarin.Forms… Je ne peux donc me fier qu’à mon expérience, qui même large n’offre qu’un panel qui n’a que peu de chance d’être scientifiquement représentatif avec la rigueur que cela exige. Mais bon, c’est un avis d’expert, et beaucoup de nos décisions et de nos prévisions en tout domaine se font sur ce type d’avis qui est souvent meilleur que celui de monsieur tout-le-monde au café du coin. Cela reste donc à prendre avec recul mais c’est mieux que pas d’avis du tout ou l’avis de quelqu’un qui ne connaît pas le domaine.

Cela étant posé, ma constatation au travers de mes audits, des projets pour lesquels j’interviens en Conseil ou en développement, c’est que l’AbsoluteLayout est mal aimé, donc peu utilisé.

Pourquoi ?

De deux choses l’une : soit c’est un contrôle sans intérêt et ceci explique donc cela, soit c’est un contrôle très intéressant mais mal compris, ceci expliquant aussi cela (formule qui n’a donc aucun intérêt puisqu’elle fonctionne dans les deux cas).

Soyons francs, techniquement l’AbsoluteLayout n’est pas une merveille sur laquelle on a spontanément envie d’écrire un roman fleuve… C’est vrai. Mais elle (c’est une mise en page) se montre capable de choses tout à fait utiles notamment dans le cadre de l’Adaptive Design par exemple (ou du plus snob Responsive UI).

Alors si ce contrôle est utile et peu utilisé et en nous référant à l’alternative posée plus haut c’est qu’en toute logique il est mal compris.

Cette incompréhension je la crois venir de deux origines différentes, la première est le nom AbsoluteLayout qui fait penser à une sorte de Canvas XAML (WPF) c’est à dire un objet où les coordonnées des enfants visuels s’expriment en Pixels donc de façon absolue. Ce n’est pas faux en plus, c’est l’une des utilisations de l’AbsoluteLayout. Or tout le monde le sait, on vous l’a seriné assez souvent, avec les différents form factors existants aujourd’hui il ne faut absolut(ment) pas utiliser ce type de conteneur visuel ! Il faut faire de l’Adaptive Design, du Responsive UI, du truc hype hypra cool de djeuns. Et pour une fois ce ne sont pas des âneries juste pour faire une mode. C’est fondé et justifié.

Mais l’AbsoluteLayout est bien plus malin qu’un Canvas WPF, il accepte un paramétrage plus fin permettant de rendre une, plusieurs ou toutes les coordonnées et tailles relatives et non plus “Absolute” !

Et c’est ce second aspect qui aurait pu sauver de l’anonymat l’AbsoluteLayout qui l’enfonce encore plus, car cet aspect semble confus, et ce qui n’est pas clair ne donne pas envie de chercher plus loin.

Et voici comment un bon contrôle très utile peut se trouver sous-utilisé pour des raisons qui finalement n’ont rien de techniques.

Il est temps de réhabiliter le soldat AbsoluteLayout, et s’il en fallait un assez courageux pour une telle mission au demeurant pas très réjouissante, c’est bien votre serviteur s’appuyant sur le légendaire Dot.Blog (tout de même plus de 1000 articles en 12 ans d’existence, ce n’est plus un blog, c’est une institution d’utilité publique !).

Donc je m’y colle…

Mais vous allez voir ce n’est pas non plus une punition hein ! Vous allez découvrir des choses étonnantes et bien pratiques si vous ne connaissiez pas l’AbsoluteLayout et ça c’est réjouissant !

Propriétés et Syntaxe de base

L’AbsoluteLayout est un contrôle visuelle, une “vue” au sens Android, mais je préfère “contrôle visuel” dans une terminologie plutôt XAML surtout que les Xamarin.Forms ne s’appliquent pas qu’à Android mais aussi à iOS, UWP etc.

C’est une surface, un support on dit plutôt un “conteneur visuel”.

On crée une instance en XAML de la façon la plus traditionnelle c’est à dire par une balise ouvrante. Beaucoup d’éléments XAML peut avoir une balise fermante “intégrée” et simplifiée en ajoutant “/>” en fin de ligne. S’agissant ici d’un conteneur il faudra bien entendu écrire les autres balises à l’intérieur donc on trouvera généralement deux balises distinctes, l’une d’ouverture <AbsoluteLayout> et l’autre de fermeture </AbsoluteLayout> quelque part plus bas dans le code XAML.

Les propriétés offertes sont relativement limitées (outre le “pot common” des contrôles visuels des Xamarin.Forms). Notamment on trouve une BackgroundColor dont le nom devrait suffire à expliquer son utilité.

En utilisant les propriétés communes on peut bien entendu fixer sa taille, sa position, son mode de dilatation (Expand ou pas par exemple), son centrage horizontal et toutes ces choses habituelles qui ne sont pas réservées à l’AbsoluteLayout.

Propriétés Attachées

Là où l’AbsoluteLayout se distingue d’autres contrôles c’est qu’elle propose deux propriétés attachées très importantes. C’est le cas de beaucoup de conteneurs comme la Grid qui offre les propriétés Row et Column à ses enfants visuels. L’AbsoluteLayout offre elle les propriété LayoutBounds et LayoutFlags.

Et c’est de là que vient un peu la complexité ou la confusion avec un simple Canvas à la WPF. Les fameux flags vont permettre de transformer ce qui ne serait en effet qu’un simple Canvas en quelque chose de beaucoup plus puissants et plus “moderne” donc utile.

Les flags vont modifier la façon dans la propriété LayoutBounds est interprétée. Et j’ai remarqué qu’à chaque fois qu’un contrôle avait ce type de comportement c’était confusant et que beaucoup de gens évitaient de s’en servir. Ici c’est vraiment bête de se priver des services de AbsoluteLayout à cause de cela car ce n’est pas si compliqué.

Les modes de fonctionnement

Si on ne dit rien, si on ne précise rien dans le code, par défaut l’AbsoluteLayout est en effet un Canvas WPF. De fait les LayoutBounds sont exprimées en pixels sur 4 nombres. Par exemple “0,0,1,1” signifiera Position X = 0, Position Y = 0, Largeur = 1 Pixel, Hauteur = 1 Pixel, dans cet ordre là bien précis.

Placer un carré de 10 pixels de côté à 150 pixels du haut et 50 pixels de la gauche de l’écran s’écrira donc “50,150,10,10”

Mais rappelez-vous, ce sont des propriétés attachées.. on parle donc ici des LayoutBounds exprimées dans un contrôle visuel enfant de l’AbsoluteLayout …

On aura donc un code plutôt de ce type :

image

Ici c’est une BoxView qui est placée dans l’AbsoluteLayout en position X=0, Y=0, hauteur et largeur = 100 pixels.

La prévisualisation nous montre quelque chose de ce type :

image

Un bloc de 100x100 pixels accroché en coordonnées 0,0 sur l’écran.

Il est clair que ce mode de type Canvas WPF ne sert plus à grand chose aujourd’hui. Il y tant de machines différentes à supporter avec le même code, des machines ayant des résolutions et des  tailles si différentes que travailler en pixel est tout bonnement impossible.

Mais alors pourquoi ce contrôle existe-t-il ?

Mais parce que tout cela n’est qu’un mode de base qui va pouvoir se moduler ! Si vous ne modulez pas, ça ne sert à rien on est d’accord, mais ce conteneur ne s’utilise qu’en modulant le système de coordonnées.

Et ça veut dire quoi “moduler le système de coordonnées”. Absolument rien mais ça fait bien non ? En réalité je veux dire par là qu’il va falloir utiliser d’autres propriétés de l’AbsoluteLayout qui vont modifier la façon dont les coordonnées sont interprétées. Moduler le système de coordonnées donc. Finalement ça avait un sens…

Proportions au lieu de pixels

L’astuce est simple mais plein de potentiel ! Dans les fameuses propriétés attachées que l’AbsoluteLayout on trouvait LayoutFlags. Il y a un “s” donc plusieurs flags. Et ces flags que font-ils à quoi servent-ils ? (“vous êtes de la police ?” est en train bougonner un flag à côté de moi sur l’écran… susceptibles ils sont…).

Dans LayoutBounds on rangeait les propriété X, Y, Width et Height dans cet ordre. Avec les LayoutFlags on va tout simplement pouvoir accrocher, dans le même ordre, un flag pour chaque coordonnée ce qui va indiquer à l’AbsoluteLayout comment interpréter la valeur.

Deux possibilités : En Absolu ou en Proportion.

Et vous avez le pourquoi du nom qui est crétin, AbsoluteLayout aurait du s’appeler ProportionalLayout ça aurait été plus engageant, sachant que le mode fixe (Absolu) n’est pas le plus intéressant ni le plus utilisé…

Même si toutes les combinaisons sont possibles, pour faire simple et efficace on va dire que principalement on applique le principe absolu ou proportionnel soit aux coordonnées pures (X,Y), soit au coordonnées de taille (Width et Height). On peut le faire pour toutes, si on ne le fait pour aucune… oui j’en vois qui suit… on revient au mode Canvas WPF… Intérêt nul donc (ou presque).

Dans l’exemple plus haut on voit à l’œuvre le mode “tout absolu” donc Canvas WPF. Regardons d’autres façons plus intelligentes d’utiliser le contrôle et ses variantes :

image

Dans le cas montré ci-dessus nous avons décidé que X et Y seraient proportionnelles mais que la taille le serait aussi. Proportionnel à quoi ? A la taille de la AbsoluteLayout tout simplement. Peu importe cette taille qui elle est fixée dans la balise de ce conteneur.

Dans ce mode particulier les valeurs de LayoutBounds s’expriment sous la forme d’un décimal allant de 0 à 1. On peut voir cela comme un pourcentage. 0% à 100% si vous multipliez par 100 pour avoir des pourcentages “grand public”.

Si on lit les valeurs et les flags de l’exemple ont devrait donc avoir une boîte colorée accrochée en haut à gauche de l’écran (X et Y sont à 0%) mais possédant une largeur égale à la moitié de la largeur de l’AbsoluteLayout parente (Width = 0.5) et une hauteur égale à celle du conteneur (Height = 1 donc 100%).

Comme c’est une utilisant courante il existe des raccourcis pour les flags. Si tous les flags sont proportionnels on indique juste LayoutFlags=”Full”.

De même on peut vouloir une taille d’objet fixe mais un placement proportionnel (ou l’inverse) dans ce cas on peut utiliser d’autres raccourcis comme “PositionPropertional” (à la place de XProportional,XProportional) ou “SizeProportional” (à la place de WidthProportional,HeightProportional).

Comme vous le voyez ce n’est pas bien sorcier on est soit en mode Canvas WPF soit en mode proportionnel. La seule subtilité c’est qu’on peut choisir ce mode indépendamment pour chaque propriétés de LayoutBounds ou bien le faire soit sur la partie coordonnées soit sur la partie taille.

Quelques utilisations ?

Je n’arrête pas de vous le dire donc je pense que c’est bien compris : l’adaptive design ou le reactive design, bref des UI complexes qui s’adaptent aux conditions différentes d’affichage sans réclamer aucun code C# pour le faire. Un truc pour les pros du design XAML donc. Mais c’est un métier, ça s’apprend. L’AbsoluteLayout vous met le pied à l’étrier en quelque sorte…

On peut aussi utiliser dans cet esprit un autre contrôle, le RelativeLayout, celui-là est un peu plus vicieux et difficile à maitriser il faut l’admettre mais il permet des choses encore plus sophistiquées. Déjà il a de meilleures performances, c’est bon à savoir, et il supporte aussi le positionnement en dehors de ces propres limites, autres point très intéressants à noter. Mais restons-en là pour aujourd’hui !

Dans le jargon du design je vous ai parlé de reactive design, de Complex UI, d’adaptive design, mais il y a aussi le Responsive Design ! Tout cela se vaut même si certains attachent des différences. Rappelez-vous juste que le but du jeu est de faire des UI capables de s’adapter à plein de circonstances, de résolutions, de tailles d’écran, de format paysage ou portrait, etc, le tout “automatiquement” juste en XAML sans code C#. Dans le type de cette animation Gif :


ezgif.com-crop

Le code de cette fiche utilisateur est le suivant :

<?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:local="clr-namespace:AbsoluteLayoutSample" 
             x:Class="AbsoluteLayoutSample.AbsoluteLayoutSamplePage">
    
    <AbsoluteLayout Padding="20"  VerticalOptions="CenterAndExpand">
         <Entry  AbsoluteLayout.LayoutBounds="0,40,1,40" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" Placeholder="UserName" HorizontalOptions="FillAndExpand"/>
         <Entry AbsoluteLayout.LayoutBounds="0,85,1,40" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" Placeholder="Password"  HorizontalOptions="FillAndExpand"/>
         <Entry AbsoluteLayout.LayoutBounds="0,130,0.5,40" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" Placeholder="NickName"  HorizontalOptions="FillAndExpand"/>
         <Entry  AbsoluteLayout.LayoutBounds="1,130,0.5,40" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" Placeholder="Phone"  HorizontalOptions="FillAndExpand"/>
         <Button  AbsoluteLayout.LayoutBounds="0,180,1,40" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional"  HorizontalOptions="FillAndExpand" Text="Register" TextColor="White" BackgroundColor="Black"/>
     </AbsoluteLayout>
</ContentPage>

On peut aussi utiliser l’AbsoluteLayout pour faire des choses “complexes”, le fameux “Complex UI”. A quoi cela ressemble ? Et bien par exemple faire une portée musicale avec des notes dessus si vous y avez réfléchi ce n’est pas forcément une sinécure … Alors que faire ça :

image

…Se réalise avec le code suivant :

<?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:local="clr-namespace:AbsoluteLayoutSample" 
             x:Class="AbsoluteLayoutSample.AbsoluteLayoutSamplePage">
    
    <AbsoluteLayout Padding="20"  VerticalOptions="CenterAndExpand">
         <BoxView  AbsoluteLayout.LayoutBounds="0,40,1,1" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" BackgroundColor="DarkGray"/>
         <BoxView  AbsoluteLayout.LayoutBounds="0,50,1,1" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" BackgroundColor="DarkGray"/>
         <BoxView  AbsoluteLayout.LayoutBounds="0,60,1,1" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" BackgroundColor="DarkGray"/>
         <BoxView  AbsoluteLayout.LayoutBounds="0,70,1,1" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" BackgroundColor="DarkGray"/>
         <BoxView  AbsoluteLayout.LayoutBounds="0,80,1,1" AbsoluteLayout.LayoutFlags="XProportional,WidthProportional" BackgroundColor="DarkGray"/>
         <Image Source="solClave" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="0,30,60,60" AbsoluteLayout.LayoutFlags="XProportional"/>
         <Label Text="4&#10;4" AbsoluteLayout.LayoutBounds="50,40,50,50" />
         <BoxView AbsoluteLayout.LayoutBounds="60,97,20,2" BackgroundColor="DarkGray"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="60,80,20,20" x:Name="C"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="80,70,20,20" x:Name="D"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="100,65,20,20" x:Name="E"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="120,60,20,20" x:Name="F"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="140,55,20,20" x:Name="G"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="160,50,20,20" x:Name="A"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="180,45,20,20" x:Name="B"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="200,50,20,20" x:Name="A2"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="220,55,20,20" x:Name="G2"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="240,60,20,20" x:Name="F2"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="260,65,20,20" x:Name="E2"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="280,70,20,20" x:Name="D2"/>
         <BoxView AbsoluteLayout.LayoutBounds="300,97,20,2" BackgroundColor="DarkGray"/>
         <Image Source="note" Aspect="AspectFit" AbsoluteLayout.LayoutBounds="300,80,20,20" x:Name="C2"/>
     </AbsoluteLayout>
</ContentPage>

Etonnant non ?

On peut même s’en servir pour superposer des éléments et créer des montages visuels, bref c’est riche, et ça mérite de s’y intéresser !

Conclusion

Qui aurait dit que derrière l’AbsoluteLayout se cachait tant de puissance pour si peu de complexité une fois le principe compris ? Dessiner une portée musicale est un exercice de style délicat même en XAML WPF, et pourtant là on peut s’en sortir assez facilement. C’est un bon exemple de complexité totalement gérée en XAML sans une seule goute de code C# !

Mail aimé, sous utilisé, c’est ce que j’ai remarqué, mais peut-être que cette modeste contribution pourra vous inciter à dépasser les apparences et à adopter l’AbsoluteLayout pour la création de vos designs !

Stay Tuned !

blog comments powered by Disqus