Loupe

Xamarin Forms : Créer une vue avec un arrière-plan dégradé à l'aide de custom renderers

Difficile d'échapper aux dégradés dans les designs d'applications mobiles de nos jours, mais ce n'est pas la chose la plus évidente à faire en Xamarin.Forms ! En effet, aucun moyen de le faire sans passer par des renderers.

Dans cet article, nous allons donc voir comment créer une vue qui aura un arrière-plan dégradé comme ceci :

ios_horizontal - Copie.PNG

Projet commun

Tout d'abord, dans le projet commun, commençons par créer notre vue qui aura un dégradé comme arrière-plan. Comme je veux que ma vue puisse contenir n'importe quel type de contenu, je la fais étendre ContentView, mais libre à vous de choisir quel contrôle étendre.

Cette vue contient 3 propriétés "bindable" :

  • StartColor : la couleur de départ du dégradé
  • EndColor : la couleur de fin du dégradé
  • Orientation : le sens du dégradé (horizontal, ou vertical)

Ci-dessous, le code complet du control GradientView :

public class GradientView : ContentView
{
    public static readonly BindableProperty StartColorProperty = BindableProperty.Create(
            nameof(StartColor),
            typeof(Color),
            typeof(GradientView),
            defaultBindingMode: BindingMode.OneWay,
            defaultValue: default(Color));

    public Color StartColor
    {
        get { return (Color)GetValue(StartColorProperty); }
        set { SetValue(StartColorProperty, value); }
    }

    public static readonly BindableProperty EndColorProperty = BindableProperty.Create(
            nameof(EndColor),
            typeof(Color),
            typeof(GradientView),
            defaultBindingMode: BindingMode.OneWay,
            defaultValue: default(Color));

    public Color EndColor
    {
        get { return (Color)GetValue(EndColorProperty); }
        set { SetValue(EndColorProperty, value); }
    }

    public static readonly BindableProperty OrientationProperty = BindableProperty.Create(
            nameof(Orientation),
            typeof(StackOrientation),
            typeof(GradientView),
            defaultBindingMode: BindingMode.OneWay,
            defaultValue: StackOrientation.Horizontal);

    public StackOrientation Orientation
    {
        get { return (StackOrientation)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, value); }
    }
}

Malheureusement, ceci n'est pas suffisant pour dessiner notre dégradé. Passons donc au code spécifique aux plateformes Android et iOS.

Projet Android

Dans le projet Android, créons une nouvelle classe qui contiendra notre renderer. Si vous n'êtes pas familier avec la notion de renderer, ils permettent de personnaliser l'apparence et le comportements des différents contrôles. Pour plus de détails, vous réferer à la documentation officielle. Dans notre cas, le renderer permettra de personnaliser l'apparence de la ContentView afin d'y appliquer notre dégradé.

Voici à quoi doit ressembler notre renderer au départ et notez la présence des méthodes OnElementChanged et OnElementPropertyChanged que nous implémenterons tout à l'heure. La première nous servira à dessiner l'arrière plan lorsque le contrôle change, notamment lorsque qu'il est construit, et la deuxième lorqu'une propriété change, par exemple lorsqu'une couleur sera changée à l'exécution.

using Android.Content;
using GradientViewSample.Controls;
using GradientViewSample.Droid.Renderers;
using System;
using System.ComponentModel;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: Xamarin.Forms.ExportRenderer(typeof(GradientView), typeof(DroidGradientViewRenderer))]
namespace GradientViewSample.Droid.Renderers
{
    public class DroidGradientViewRenderer : VisualElementRenderer<ContentView>
    {
        public DroidGradientViewRenderer(Context context) 
        : base(context) 
        { } 
        
        protected override void OnElementChanged(ElementChangedEventArgs<ContentView> e)
        { 
            base.OnElementChanged(e); 
        }
        
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e); 
        }
    }
}

Implémentons donc notre renderer. Tout d'abord, commençons par ajouter les propriétés de notre contrôle ainsi que la méthode qui dessinera l'arrière plan :

private Color StartColor { get; set; }
private Color EndColor { get; set; }
private StackOrientation Orientation { get; set; }
private void SetGradientBackground()
{
    int[] colors = { StartColor.ToAndroid(), EndColor.ToAndroid() };
    GradientDrawable gradient = new GradientDrawable(
        Orientation == StackOrientation.Horizontal 
        ? GradientDrawable.Orientation.LeftRight 
        : GradientDrawable.Orientation.TopBottom, colors); 
        
    Android.Support.V4.View.ViewCompat.SetBackground(this, gradient);
}

Puis implémentons les méthodes OnElementChanged et OnElementPropertyChanged :

protected override void OnElementChanged(ElementChangedEventArgs<ContentView> e)
{
    base.OnElementChanged(e);

    if (e.OldElement != null || Element == null)
    {
        return;
    }

    try
    {
        var stack = (GradientView)e.NewElement;
        StartColor = stack.StartColor;
        EndColor = stack.EndColor;
        Orientation = stack.Orientation;

        SetGradientBackground();
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(@"ERROR:", ex.Message);
    }
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged(sender, e);
    if (e.PropertyName == nameof(GradientView.StartColor)
       || e.PropertyName == nameof(GradientView.EndColor))
    {
        var stack = (GradientView)sender;
        if (stack == null)
        {
            return;
        }

        StartColor = stack.StartColor;
        EndColor = stack.EndColor;
        Orientation = stack.Orientation;

        SetGradientBackground();
    }
}

Voilà, notre renderer Android est terminé. Si vous ne souhaitez ou ne pouvez pas implémenter la partie concernant iOS, vous pouvez sauter la partie suivante pour passer directement à l'utilisation de notre nouveau contrôle.

Projet iOS

Maintenant, dans le projet iOS, de la même manière que pour le projet Android, créons une classe qui contiendra notre renderer. A la différence d'Android, ici, nous n'utiliserons que la méthode OnElementPropertyChanged couplée à la méthode Draw pour dessiner notre dégradé.

using System.ComponentModel;
using GradientViewSample.iOS.Renderers;
using GradientViewSample.Controls;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(GradientView), typeof(IOSGradientViewRenderer))]
namespace GradientViewSample.iOS.Renderers
{
    public class IOSGradientViewRenderer : VisualElementRenderer<ContentView>
    {
        public override void Draw(CGRect rect)
        {
            base.Draw(rect);
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
        }
    }
}

Implémentons maintenant notre renderer. La méthode OnElementPropertyChanged, qui est appelée lorsqu'une propriété du contrôle change, ici appelera la méthode SetNeedsDisplay pour demander à redessiner le contrôle. C'est la méthode Draw qui s'occupe de dessiner l'interface. Elle récupère donc les couleurs définies dans le contrôle et crée un CAGradientLayer en fonction de l'orientation choisie.

Capture.PNG

C'en est fini pour le renderer iOS.

Utilisation

Passons maintenant à l'utilisation de nouveau contrôle. Pour cela, rien de plus simple, il suffit d'ajouter le contrôle sur une page.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="GradientViewSample.MainPage"
    xmlns:ctrls="clr-namespace:GradientViewSample.Controls">

    <ctrls:GradientView
        StartColor="#4297ef"
        EndColor="#002f90"
        Orientation="Vertical">
        
        <StackLayout>
            <Label Text="Gradient view sample" 
               TextColor="White"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand"/>
        </StackLayout>
        
    </ctrls:GradientView>
</ContentPage>

Si tout s'est bien passé, en démarrant l'application vous devriez avoir des rendus similaires à ceux-ci : 

Android_horizontal.jpg  ios_horizontal.PNG

Android_vertical.jpg   ios_vertical.PNG

TADA ! En voilà de beaux dégradés :)
Vous pourrez retrouver tout le code source ici.

D'autres exemples d'utilisation des custom renderers avec Thomas ici et ici 

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus