Xamarin : prendre une capture d'écran d'un élément affiché ou d'une portion d'écran
Dans cet article, nous verrons comment générer une image à partir d'une vue affichée sur le mobile de l'utilisateur depuis Android et iOS.
Imaginons par exemple que vous souhaitez générer une image pour partager quelque chose de graphique et plus vivant au lieu de simplement proposer un partage de texte. Il est alors intéressant de générer une vue correspondante et de la transformer en image. Avec Xamarin Forms, cette vue peut être placée dans la partie commune (un seul développement \o/) et même être affichée et rendue personnalisable facilement par l'utilisateur.
Pour ma part, j'en ai besoin dans une application de gestion de Podcasts pour n'extraire que la partie dans le rectangle rouge et la proposer en partage :
Voici ce que nous allons faire dans la suite de l'article :
- Création d'une vue en XAML Xamarin Forms,
- Création et utilisation d'un service de screenshot d'une vue natif Android et iOS.
Partie commune
Je me contente de créer une vue XAML classique, que je construis avec le contenu que je souhaite. Je prends uniquement la peine de la nommer pour pouvoir y accéder depuis le code behind :
<Frame x:Name="ScreenShotableView" HasShadow="False"> </Frame>
Ensuite, côté code-behind, sur le click sur un bouton, j'appelle mon service de génération d'un screenshot pour une vue donnée. Dans cet exemple, j'utilise Xamarin.Essentials (dont Jérôme a déjà parlé ici) pour proposer l'image en partage à l'utilisateur :
var screenshot = DependencyService.Resolve<IScreenshotService>() .GetForView(ScreenShotableView); ScreenShotableView.IsVisible = false; string fileName = Guid.NewGuid().ToString("N") + ".png"; IFilesService filesService = DependencyService.Resolve<IFilesService>(); using (var file = await filesService.OpenFileAsync(fileName)) { await file.WriteAsync(screenshot, 0, screenshot.Length); } var completeFileName = filesService.GetLocalPathForFileName(fileName); var shareFileRequest = new ShareFileRequest { Title = "Un titre", File = new ShareFile(completeFileName) }; Device.BeginInvokeOnMainThread( () => Share.RequestAsync(shareFileRequest));
Le service de ScreenShot a une définition toute bête lui aussi :
public interface IScreenshotService { byte[] GetForView(View view); }
Service Android
L'implémentation côté Android est la suivante :
- Récupération du renderer Android de la vue passée en paramètre.
- Récupération de la vue native sous-jacente,
- Utilisation des APIs natives pour dessiner à l'aide de la méthode Draw sur un Canvas natif.
- On copie le tout dans un MemoryStream dont on retourne le tableau de byte.
public byte[] GetForView(View view) { var elementRenderer = Xamarin.Forms.Platform.Android .Platform.GetRenderer(view); var nativeView = elementRenderer.View; using (var screenshot = Bitmap.CreateBitmap( nativeView.Width, nativeView.Height, Bitmap.Config.Argb8888)) { using (Canvas canvas = new Canvas(screenshot)) { nativeView.Draw(canvas); using (var stream = new MemoryStream()) { screenshot.Compress( Bitmap.CompressFormat.Png, 90, stream); return stream.ToArray(); } } } }
Service iOS
Sur iOS c'est aussi simple :
- Récupération du renderer iOS de la vue passée en paramètre.
- Récupération de la vue native sous-jacente,
- Utilisation de la méthode DrawViewHierarchy pour dessiner la vue.
public byte[] GetForView(View view) { var elementRenderer = Xamarin.Forms.Platform.iOS .Platform.GetRenderer(view); var nativeView = elementRenderer.NativeView; UIGraphics.BeginImageContext(nativeView.Frame.Size); nativeView.DrawViewHierarchy(nativeView.Frame, true); var image = UIGraphics.GetImageFromCurrentImageContext(); UIGraphics.EndImageContext(); using (var imageData = image.AsPNG()) { var bytes = new byte[imageData.Length]; System.Runtime.InteropServices.Marshal.Copy( imageData.Bytes, bytes, 0, Convert.ToInt32(imageData.Length)); return bytes; } }
Happy coding !
Commentaires