• laurent@lioncoding.com

Créer un Color Picker dans Xamarin.Forms


Il sera question dans cet article, de créer un composant visuel proposant plusieurs couleurs et qui donne la possibilité de choisir une.

Il est à noter que Xamarin dans sa version actuelle ne propose pas encore un composant de type Color Picker. Néanmoins, quelques Plugins existent. Un de ces plugins est Amporis.Xamarin.Forms.ColorPicker. Ce plugin propose des champs de saisie pour définir les valeurs hexadécimales des couleurs, ce qui n’est pas pratique.

C’est pourquoi je vous propose cet article qui vous permettra de créer un Color Picker offrant des couleurs pré-définies: ceci nous permet de nous débarrasser des codes hexadécimaux.

Création du projet Cross-platform Xamarin

N.B: J’utilise Visual Studio 2019 pour cet article. Pour ceux qui utilisent VS2017, le procédé de création d’un projet Xamarin est le même que celui présenté dans mes articles précédents.

Sélectionnez le type de projet Application mobile(Xamarin.Forms) puis sur Suivant

Entrez le nom du projet: ColorPicker, sélectionnez l’emplacement du projet et cliquez sur Créer

Puis sélectionnez le modèle Vide.

Ajout des packages nuget

Nous utiliseront le plugin Rg.Plugins.Popup pour créer le Popup page sur lequel seront disposées les couleurs et le plugin DLToolkit.Forms.Controls.FlowListView qui est une sorte de ListView avec un Grid intégré.

Initialisations et Configurations

Le plugin Rg.Plugins.Popup doit être initialisé dans le projet Android et IOS avant utilisation.

Projet Android

  • Initialisation dans le fichier MainActivity.cs
protected override void OnCreate(Bundle savedInstanceState)
{    
    // ...
    // Init Rg plugin
    Rg.Plugins.Popup.Popup.Init();
    // ...
    LoadApplication(new App());
}

Projet IOS

  • Initialisation dans le fichier AppDelegate.cs
 public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    // Init Rg plugin
    Rg.Plugins.Popup.Popup.Init();

    global::Xamarin.Forms.Forms.Init();
    LoadApplication(new App());
    return base.FinishedLaunching(app, options);
}

Commençons par mettre en place des convertisseurs

  • Un convertisseur de code hexadécimal en couleur donc un String to Color
  • Un convertisseur de booléen en Color(Bool to Color): Ceci parait étrange. Ne vous inquiétez pas, les couleurs seront pré-définies si Vrai ou Faux. Ce convertisseur aura pour rôle de renvoyer une couleur par défaut si l’utilisateur choisie une, l’objectif est de pouvoir mettre en évidence la couleur sélectionnée.

StringToColorConverter

using System;
using System.Globalization;
using Xamarin.Forms;

namespace ColorPicker.Converters
{
    public class StringToColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var defaultColor = Color.FromRgba(0, 0, 0, 0);
            if (value == null) return defaultColor;
            var colorAsString = (string)value;
            try
            {
                return Color.FromHex(colorAsString);
            }
            catch
            {
                return defaultColor;
            }

        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

BoolToColorConverter

using System;
using System.Globalization;
using Xamarin.Forms;

namespace ColorPicker.Converters
{
    public class BoolToColorConverter: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var x = (bool)value;
            if (x) return Color.Tomato;
            else return Color.FromHex("#eee");
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

Le Popup Page qui nous fournit les couleurs

  • La vue: ColorSelectionPopupView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<pages:PopupPage
    x:Class="ColorPicker.PopupPages.ColorSelectionPopupView"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:animations="clr-namespace:Rg.Plugins.Popup.Animations;assembly=Rg.Plugins.Popup"
    xmlns:converters="clr-namespace:ColorPicker.Converters"
    xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"
    xmlns:pages="clr-namespace:Rg.Plugins.Popup.Pages;assembly=Rg.Plugins.Popup">

    <pages:PopupPage.Resources>
        <converters:BoolToColorConverter x:Key="BoolToColorConverter" />
        <converters:StringToColorConverter x:Key="StringToColorConverter" />
    </pages:PopupPage.Resources>

    <pages:PopupPage.Animation>
        <animations:ScaleAnimation
            DurationIn="400"
            DurationOut="300"
            EasingIn="SinOut"
            EasingOut="SinIn"
            HasBackgroundAnimation="True"
            PositionIn="Center"
            PositionOut="Center"
            ScaleIn="1.2"
            ScaleOut="0.8" />
    </pages:PopupPage.Animation>


    <ContentPage.Content>
        <Grid
            BackgroundColor="#fff"
            HorizontalOptions="Center"
            VerticalOptions="Center">
            <ScrollView Margin="8,8,8,8" VerticalScrollBarVisibility="Never">
                <StackLayout
                    HeightRequest="350"
                    Orientation="Vertical"
                    WidthRequest="300">
                    <flv:FlowListView
                        Margin="5,0,5,0"
                        FlowColumnCount="5"
                        FlowItemTappedCommand="{Binding ColorTappedCommand}"
                        FlowItemsSource="{Binding CategoriesColors}"
                        FlowLastTappedItem="{Binding SelectedColor}"
                        HasUnevenRows="True"
                        SeparatorVisibility="None">
                        <flv:FlowListView.FlowColumnTemplate>
                            <DataTemplate>
                                <Frame
                                    Margin="0"
                                    BackgroundColor="{Binding HexValue, Converter={StaticResource StringToColorConverter}}"
                                    BorderColor="{Binding Selected, Converter={StaticResource Key=BoolToColorConverter}}"
                                    CornerRadius="360"
                                    HeightRequest="20">
                                    <Grid
                                        Margin="0"
                                        HorizontalOptions="FillAndExpand"
                                        VerticalOptions="FillAndExpand">
                                        <ContentView BackgroundColor="{Binding HexValue, Converter={StaticResource StringToColorConverter}}" HeightRequest="40" />
                                    </Grid>
                                </Frame>
                            </DataTemplate>
                        </flv:FlowListView.FlowColumnTemplate>
                    </flv:FlowListView>

                    <Button
                        HorizontalOptions="FillAndExpand"
                        Pressed="Button_Pressed"
                        Text="Select" />
                </StackLayout>
            </ScrollView>
        </Grid>
    </ContentPage.Content>
</pages:PopupPage>

Le Frame d’une couleur a une forme ronde. Pour faire cela, vous devez jouer sur son CornerRadius.

  • Son code Behind: ColorSelectionPopupView.cs
using Rg.Plugins.Popup.Pages;
using Rg.Plugins.Popup.Services;
using System;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace ColorPicker.PopupPages
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ColorSelectionPopupView : PopupPage
    {
        ColorSelectionPopupViewModel ViewModel;

        public ColorSelectionPopupView()
        {
            InitializeComponent();
            BindingContext = ViewModel = new ColorSelectionPopupViewModel();
        }

        private async void Button_Pressed(object sender, EventArgs e)
        {
            if (ViewModel.SelectedColor == null)
            {
                await DisplayAlert("No color selected", "You must choose one color !", "OK");
                return;
            }
            //Send a message to notify the selected Color
            MessagingCenter.Send(ViewModel, "_categoryColor");
            //close this
            await PopupNavigation.PopAsync(true);
        }
    }
}
  • Le ViewModel du Popup Page : ColorSelectionPopupViewModel.cs
using ColorPicker.ViewModels;
using System.Collections.ObjectModel;
using System.Windows.Input;
using Xamarin.Forms;
using Xamarin.Forms.Internals;

namespace ColorPicker.PopupPages
{
    public class ColorSelectionPopupViewModel: BaseViewModel
    {
        private ObservableCollection<ColorExtended> _categoriesColors;
        public ObservableCollection<ColorExtended> CategoriesColors
        {
            get { return _categoriesColors; }
            set { SetProperty(ref _categoriesColors, value); }
        }

        private ColorExtended _selectedColor;
        public ColorExtended SelectedColor
        {
            get { return _selectedColor; }
            set { SetProperty(ref _selectedColor, value); }
        }

        public ICommand ColorTappedCommand { get; set; }


        public ColorSelectionPopupViewModel()
        {
            InitColor();
            ColorTappedCommand = new Command(() => ColorTapped());
            SelectedColor = null;
        }


        private void ColorTapped()
        {
            if (SelectedColor == null) return;
            CategoriesColors.ForEach(i => i.Selected = (i.HexValue == SelectedColor.HexValue));
        }


        public void InitColor()
        {

            CategoriesColors = new ObservableCollection<ColorExtended>
            {
                new ColorExtended { HexValue = "#FFB900" },
                new ColorExtended { HexValue = "#F7630C" },
                new ColorExtended { HexValue = "#DA3B01" },
                new ColorExtended { HexValue = "#E3008C" },
                new ColorExtended { HexValue = "#9A0089" },
                new ColorExtended { HexValue = "#881798" },
                new ColorExtended { HexValue = "#0063B1" },
                new ColorExtended { HexValue = "#0078D7" },
                new ColorExtended { HexValue = "#498205" },

                new ColorExtended { HexValue = "#107C10" },
                new ColorExtended { HexValue = "#4C4A48" },
                new ColorExtended { HexValue = "#4A5459" },
                new ColorExtended { HexValue = "#525E54" },
                new ColorExtended { HexValue = "#847545" },
                new ColorExtended { HexValue = "#724F2F" },

                new ColorExtended { HexValue = "#A21025" },
                new ColorExtended { HexValue = "#193E91" },
                new ColorExtended { HexValue = "#54A81B" },

                new ColorExtended { HexValue = "#EA005E" },
                new ColorExtended { HexValue = "#00B7C3" },
                new ColorExtended { HexValue = "#E74856" },

                new ColorExtended { HexValue = "#525ABB" },
                new ColorExtended { HexValue = "#00CCFF" },
                new ColorExtended { HexValue = "#333366" },
                new ColorExtended { HexValue = "#6666FF" },
                new ColorExtended { HexValue = "#7298A5" },
                new ColorExtended { HexValue = "#1C2641" },
                new ColorExtended { HexValue = "#66FFCC" },
                new ColorExtended { HexValue = "#00CC99" },
                new ColorExtended { HexValue = "#0099CC" }
            };

        }
    }


    public class ColorExtended : BaseViewModel
    {
        private string _hexValue;
        public string HexValue
        {
            get { return _hexValue; }
            set { SetProperty(ref _hexValue, value); }
        }


        private bool _selected;
        public bool Selected
        {
            get { return _selected; }
            set { SetProperty(ref _selected, value); }
        }
    }
}

La liste CategoriesColors est l’objet qui contient les couleurs pré-définies.

Le MainPage

  • L’interface: ColorPickerView.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="ColorPicker.Views.ColorPickerView"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:converters="clr-namespace:ColorPicker.Converters"
    Title="{Binding Title}">

    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:StringToColorConverter x:Key="StringToColorConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <ContentPage.Content>
        <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
            <!--  SELECTED COLOR  -->
            <Frame
                BackgroundColor="{Binding CategoryBackgroundColor, Converter={StaticResource StringToColorConverter}}"
                CornerRadius="25"
                HeightRequest="10"
                WidthRequest="10" />

            <!--  BUTTON PICKER  -->
            <ImageButton
                Margin="0,10,0,0"
                BackgroundColor="Transparent"
                Pressed="ShowColorsPopup"
                Source="pick.png">
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal">
                            <VisualState.Setters>
                                <Setter Property="Scale" Value="1" />
                            </VisualState.Setters>
                        </VisualState>
                        <VisualState x:Name="Pressed">
                            <VisualState.Setters>
                                <Setter Property="Scale" Value="0.7" />
                            </VisualState.Setters>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
            </ImageButton>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>
  • Son code Behind : ColorPickerView.cs
using ColorPicker.PopupPages;
using ColorPicker.ViewModels;
using Rg.Plugins.Popup.Extensions;
using System;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace ColorPicker.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ColorPickerView : ContentPage
    {
        private ColorPickerViewModel viewModel;

        public ColorPickerView()
        {
            InitializeComponent();
            BindingContext = viewModel = new ColorPickerViewModel();
        }

        private async void ShowColorsPopup(object sender, EventArgs e)
        {
            await Navigation.PushPopupAsync(new ColorSelectionPopupView());
        }
    }
}
  • Le ViewModel correspondant: ColorPickerViewModel.cs

using ColorPicker.PopupPages;
using Xamarin.Forms;

namespace ColorPicker.ViewModels
{
    public class ColorPickerViewModel : BaseViewModel
    {
        public ColorPickerViewModel()
        {
            Title = "Custom ColorPicker";
            RegisterMessages();
        }

        // Get the selected color from PopupPage using MessagingCenter
        private void RegisterMessages()
        {
            MessagingCenter.Subscribe<ColorSelectionPopupViewModel>(this, "_categoryColor", (s) =>
            {
                if (s != null)
                {
                    CategoryBackgroundColor = s.SelectedColor?.HexValue;
                }
            });
        }


        private string _categoryBackgroundColor = "#2196F3";
        public string CategoryBackgroundColor
        {
            get { return _categoryBackgroundColor; }
            set { SetProperty(ref _categoryBackgroundColor, value); }
        }
    }
}

Ressources

Voir le Code source complet sur Github.

N’hésitez pas à me contacter via le formulaire de contact ou par ou par mail.


Commentaires