Dot.Blog

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

Xamarin.Forms : Conteneur Pinch & Zoom

Il y a des petites choses qui améliorent grandement l’UX. Le support du Pinch & Zoom en fait partie. Cela se code rapidement mais si on en faisant un conteneur toujours prêt à servir sans se casser la tête ? …

Pincer pour zoomer

Si souffler n’est pas jouer, pincer ça compte… Alors c’est un pincement à double sens, pincement ou étirement, le tout avec deux doigts. Une “gesture” bien connue sur les unités mobiles.

Bien connue mais pas implémenter par défaut dans un conteneur.

Alors pourquoi ne pas réaliser le nôtre ?

A quoi cela va ressembler ?

Une petite capture vaut mille mots… Le pinch est exécuté sous l’émulateur vous ne verrez pas des gros doigts laissant des traces sur la vitre d’un smartphone :-)



C’est du plus bel effet isn’t it ?

Le code

Il n’est pas si terrifiant que ça.

Créer le contrôle comme suit, faites un copier/coller ça sera encore plus simple, et placer le tout dans un fichier C# dans notre code Xamarin.Forms (de préférence dans un répertoire dédié aux nouveaux contrôles par exemple) :


using System;
using Xamarin.Forms; using Xamarin.Forms.Internals;

namespace PinchAndZoom.Controls
{
     public class ZoomContainer : ContentView
     {
         private double currentScale = 1;
         private double startScale = 1;
         private double xOffset = 0;
         private double yOffset = 0;

        public ZoomContainer()
         {
             var pinchGesture = new PinchGestureRecognizer();
             pinchGesture.PinchUpdated += OnPinchUpdated;
             GestureRecognizers.Add(pinchGesture);
         }

        private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
         {
             switch (e.Status)
             {
                 case GestureStatus.Started:
                     startScale = Content.Scale;
                     Content.AnchorX = 0;
                     Content.AnchorY = 0;
                     break;
                 case GestureStatus.Running:
                 {
                     currentScale += (e.Scale - 1) * startScale;
                     currentScale = Math.Max(1, currentScale);

                    var renderedX = Content.X + xOffset;
                     var deltaX = renderedX / Width;
                     var deltaWidth = Width / (Content.Width * startScale);
                     var originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;

                    var renderedY = Content.Y + yOffset;
                     var deltaY = renderedY / Height;
                     var deltaHeight = Height / (Content.Height * startScale);
                     var originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;

                    var targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
                     var targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);

                    Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
                     Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);

                    Content.Scale = currentScale;
                     break;
                 }
                 case GestureStatus.Completed:
                     xOffset = Content.TranslationX;
                     yOffset = Content.TranslationY;
                     break;
                 case GestureStatus.Canceled:
                     xOffset = yOffset = 0;
                     Content.Scale = 1;
                     break;
                 default:
                     throw new ArgumentOutOfRangeException();
             }
         }
     }
}

Tout tourne autour d’une ContentView à laquelle on adjoint une PinchGestureRecognizer qui va générer des événements auxquels nous répondons par un changement de la Scale du contenu (quel qu’il soit, ici c’est une image mais c’est n’importe quel contenu du contrôle qui sera zoomable bien entendu). On en profitera aussi pour maintenir le centrage en jouant sur les translations X et Y du conteneur.


Voilà…

Ah oui.. comment s’en sert-on ?

Utilisation

En s’en sert comme de tout contrôle personnalisé c’est à dire en définissant un espace de nom XAML pointant sur la classe. Puis à l’aide de xmlns on utilise le contrôle comme n’importe quel autre. Ici dans la MainPage.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:d="http://xamarin.com/schemas/2014/forms/design"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:controls="clr-namespace:PinchAndZoom.Controls;assembly=PinchAndZoom"
              mc:Ignorable="d"
              x:Class="PinchAndZoom.MainPage">

    <Grid Padding="20" >

        <controls:ZoomContainer>
                 <Image Source="darkvador.jpg" />
         </controls:ZoomContainer>

    </Grid>

</ContentPage>


La grille et son padding ne sont que purement cosmétiques. Le contrôle contient une image mais il pourrait contenir tout ce qu’on veut, comme n’importe quelle ContentView.

Le Content étant la propriété  par défaut d’une ContentView inutile d’ouvrir une balise “Content” pour y placer l’image (ou autre chose). Le code est plus court et plus lisible.

Conclusion

Ce n’est peut-être pas le contrôle du siècle, mais qu’il est agréable de pouvoir zoomer sur le contenu d’une zone juste avec deux doigts ! Les smartphones se sont imposés grâce à ces petites choses là… Faites-en profiter vos utilisateurs vous aussi…


Et Stay Tuned !


Faites des heureux, partagez l'article !
blog comments powered by Disqus