• laurent@lioncoding.com

WhatsApp UI dans Xamarin.Forms (Partie 1)


👉 https://github.com/egbakou/WhatsAppUI le lien vers le projet complet sur Github.

Dans cette première partie d’une série d’articles dont l’objectif est de mettre en place les UI de WhatsApp en utilisant la technologie Xamarin.Forms, il sera question de:

  • [x] Créer un projet Xamarin avec Visual Studio;
  • [x] Installer les NuGet packages nécessaires à la mise en place de nos UI;
  • [x] Configurer chaque plateforme du projet pour l’utilisation des NuGet packages;
  • [x] Créer des composants personnalisés pour chaque plateforme.

Création du projet Cross-platform Xamarin

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

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

Puis sélectionnez le modèle Vide.

Ajout des packages nuget

La liste des plugins dont nous aurons besoin:

Initialisations et Configurations

Projet Android

  • Initialisation et accord de permission dans le fichier MainActivity.cs
protected override void OnCreate(Bundle bundle)
{
    // ...    
    // Init Media Plugin
    CrossCurrentActivity.Current.Init(this, bundle);
    // Init Acr.UserDialogs plugin
    UserDialogs.Init(this);
    // Init FFImageLoading plugin
    FFImageLoading.Forms.Platform.CachedImageRenderer.Init(enableFastRenderer: true);
    //Init ImageCircle plugin
    ImageCircleRenderer.Init();
    // Init Rg plugin
    Popup.Init(this, bundle);    
    // ...
}

public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
     PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
  • Activation de la permission d’allumage de la caméra dans le fichier AssemblyInfo.cs
[assembly: UsesFeature("android.hardware.camera", Required = true)]
[assembly: UsesFeature("android.hardware.camera.autofocus", Required = true)]
  • Fichier AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.WhatsAppUI">
    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="27" />
    <application android:label="WhatsAppUI.Android">
      <provider android:name="android.support.v4.content.FileProvider"
                android:authorities="${applicationId}.fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
        <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
          android:resource="@xml/file_paths"></meta-data>
      </provider>
    </application>
</manifest>
  • Dans le dossier Resources, créez un sous-dossier xml et dans ce dernier, un fichier xml file_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
  <external-files-path name="my_images" path="Pictures" />
  <external-files-path name="my_movies" path="Movies" />
</paths>

NB: Les deux dernières configurations sont exigées par le plugin Xam.Plugin.Media pour un bon fonctionnement (Plus de détails).

  • Définition de couleurs personnalisées pour la barre de navigation et de statut: fichier colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="launcher_background">#075e54</color>
    <color name="colorPrimary">#075e54</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>
</resources>
  • Utilisation de styles personnalisés: fichier styles.xml
<?xml version="1.0" encoding="utf-8" ?>
<resources>

  <style name="MainTheme" parent="MainTheme.Base">
  </style>
  <!-- Base theme applied no matter what API -->
  <style name="MainTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar">
    <!--If you are using revision 22.1 please use just windowNoTitle. Without android:-->
    <item name="windowNoTitle">true</item>
    <!--We will be using the toolbar so no need to show ActionBar-->
    <item name="windowActionBar">false</item>
    <!-- Set theme colors from http://www.google.com/design/spec/style/color.html#color-color-palette -->
    <!-- colorPrimary is used for the default action bar background -->
    <item name="colorPrimary">#075e54</item>
    <!-- colorPrimaryDark is used for the status bar -->
    <item name="colorPrimaryDark">#075e54</item>
    <!-- colorAccent is used as the default value for colorControlActivated
         which is used to tint widgets -->
    <item name="colorAccent">#FF4081</item>
    <!-- You can also set colorControlNormal, colorControlActivated
         colorControlHighlight and colorSwitchThumbNormal. -->
    <item name="windowActionModeOverlay">true</item>

    <item name="android:datePickerDialogTheme">@style/AppCompatDialogStyle</item>
  </style>

  <style name="AppCompatDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">#FF4081</item>
  </style>
</resources>

Projet IOS

  • Initialisation des plugins dans le fichier AppDelegate.cs
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
    // ...
    Rg.Plugins.Popup.Popup.Init();
    FFImageLoading.Forms.Platform.CachedImageRenderer.Init();
    // ...
}

Création de quelques rendus personnalisés

  • Cacher ou Afficher la barre de statut du téléphone:

L’objectif ici est de pouvoir afficher le statut d’un contact comme le fait WhasApp. Vous constaterez que la barre de statut disparaîtra au moment où vous visionnez le statut.

Pour faire cela, nous devons mettre en place une interface dans le Projet partagé et l’implémenter sur chaque plateforme du projet comme je l’ai fait dans un de mes articles précédents.

  • Float Action Button

C’est le bouton qui reste au dessus des interfaces de discussions, de statuts et d’appels.

👉 Création de la vue FloatingActionButton.xaml dans le projet partagé

<?xml version="1.0" encoding="utf-8" ?>
<Button
    x:Class="WhatsApp.Controls.FloatingActionButton"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" />

👉 Le code Behind Correspondant FloatingActionButton.xaml.cs

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

namespace WhatsApp.Controls
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class FloatingActionButton : Button
    {
        public static BindableProperty ButtonColorProperty = BindableProperty.Create(nameof(ButtonColor), typeof(Color), typeof(FloatingActionButton), Color.Accent);
        public Color ButtonColor
        {
            get
            {
                return (Color)GetValue(ButtonColorProperty);
            }
            set
            {
                SetValue(ButtonColorProperty, value);
            }
        }
        public FloatingActionButton()
        {
            InitializeComponent();
        }
    }
}

👉 Implémentation sur Android

using System;
using System.ComponentModel;
using Android.Content;
using Android.Content.Res;
using WhatsApp.Droid.Renders;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using FAB = Android.Support.Design.Widget.FloatingActionButton;


[assembly: ExportRenderer(typeof(WhatsApp.Controls.FloatingActionButton), typeof(FloatingActionButtonRenderer))]
namespace WhatsApp.Droid.Renders
{
    public class FloatingActionButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ViewRenderer<Controls.FloatingActionButton, FAB>
    {
        public FloatingActionButtonRenderer(Context context) : base(context)
        {

        }

        public FloatingActionButtonRenderer() : base(null)
        {
            throw new Exception("This constructor should not actually ever be used");
        }

        public static void Initialize() { /* used only for helping ensure the renderer is not linked out */ }
        protected override void OnElementChanged(ElementChangedEventArgs<WhatsApp.Controls.FloatingActionButton> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement == null)
                return;

            var fab = new FAB(Context);
            // set the bg
            Android.Support.V4.View.ViewCompat.SetBackgroundTintList(fab, ColorStateList.ValueOf(Element.ButtonColor.ToAndroid()));
            fab.UseCompatPadding = true;

            // set the icon
            var elementImage = Element.Image;
            var imageFile = elementImage?.File;

            if (imageFile != null)
            {
                fab.SetImageDrawable(ResourceManager.GetDrawable(Context, imageFile));
            }
            fab.Click += Fab_Click;
            SetNativeControl(fab);

        }
        protected override void OnLayout(bool changed, int l, int t, int r, int b)
        {
            base.OnLayout(changed, l, t, r, b);
            Control.BringToFront();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var fab = Control;
            if (e.PropertyName == nameof(Element.ButtonColor))
            {
                Android.Support.V4.View.ViewCompat.SetBackgroundTintList(fab, ColorStateList.ValueOf(Element.ButtonColor.ToAndroid()));
            }
            if (e.PropertyName == nameof(Element.Image))
            {
                var elementImage = Element.Image;
                var imageFile = elementImage?.File;

                if (imageFile != null)
                {
                    fab.SetImageDrawable(ResourceManager.GetDrawable(Context, imageFile));
                }
            }
            base.OnElementPropertyChanged(sender, e);

        }

        private void Fab_Click(object sender, EventArgs e)
        {
            // proxy the click to the element
            ((IButtonController)Element).SendClicked();
        }
    }
}

👉 Implémentation sur iOS

using System.ComponentModel;
using CoreGraphics;
using Foundation;
using UIKit;
using WhatsApp.Controls;
using WhatsApp.iOS.Renders;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(FloatingActionButton), typeof(FloatingActionButtonRenderer))]
namespace WhatsApp.iOS.Renders
{
    [Preserve]
    public class FloatingActionButtonRenderer : ButtonRenderer
    {
        public static void InitRenderer()
        {
        }
        public FloatingActionButtonRenderer()
        {
        }
        protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement == null)
                return;

            // remove text from button and set the width/height/radius
            Element.HeightRequest = Element.WidthRequest;
            //Element.CornerRadius = (int)Element.WidthRequest / 2;
            Element.BorderWidth = 0;
            Element.Text = null;

            // set background
            Control.BackgroundColor = ((FloatingActionButton)Element).ButtonColor.ToUIColor();
        }
        public override void Draw(CGRect rect)
        {
            base.Draw(rect);
            // add shadow
            Layer.ShadowRadius = 2.0f;
            Layer.ShadowColor = UIColor.Black.CGColor;
            Layer.ShadowOffset = new CGSize(1, 1);
            Layer.ShadowOpacity = 0.80f;
            Layer.ShadowPath = UIBezierPath.FromOval(Layer.Bounds).CGPath;
            Layer.MasksToBounds = false;

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

            if (e.PropertyName == "ButtonColor")
            {
                Control.BackgroundColor = ((FloatingActionButton)Element).ButtonColor.ToUIColor();
            }
        }
    }
}

Dans la partie suivante, nous mettrons en place l’interface de Chat. 😊

Ressources

👉 https://github.com/egbakou/WhatsAppUI le lien vers le projet complet sur Github.


Commentaires