Alan2
Alan2

Reputation: 24562

How can I duplicate the functionality of the iOS switch in Xamarin Forms so it will look the same in iOS and Android?

My application uses switches which look like this:

enter image description here

I would like to duplicate this functionality in Xamarin Forms so that it would appear and look exactly like this iOS version in both iOS and Android.

How could I go about doing this? If possible I would like a solution that does not use any iOS and Android renderers. Instead something that just uses the TapGestureRecognizer of Forms.

Upvotes: 4

Views: 453

Answers (2)

Markus Michel
Markus Michel

Reputation: 2299

Here is a solution using SkiaSharp. Include SkiaSharp and SkiaSharp.Views.Forms via nuget into your project.

Now create a new Content View:

CustomSwitch.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:skia="clr-    namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
             x:Class="MyNamespace.CustomSwitch" MinimumWidthRequest="120" MinimumHeightRequest="60" WidthRequest="120" HeightRequest="60">
    <ContentView.Content>
        <skia:SKCanvasView x:Name="PrimaryCanvas" PaintSurface="OnPaintSurface" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
            <skia:SKCanvasView.GestureRecognizers>
                <TapGestureRecognizer Tapped="OnElementTapped" />
            </skia:SKCanvasView.GestureRecognizers>
        </skia:SKCanvasView>
    </ContentView.Content>
</ContentView>

CustomSwitch.xaml.cs

public partial class CustomSwitch : ContentView
{
    #region Properties
    public static BindableProperty OutlineColorProperty = BindableProperty.Create("OutlineColor", typeof(Color), typeof(Color), Color.LightGray);
    public static BindableProperty SwitchBackgroundColorProperty = BindableProperty.Create("SwitchBackgroundColor", typeof(Color), typeof(Color), Color.White);
    public static BindableProperty SwitchBackgroundColorToggledProperty = BindableProperty.Create("SwitchBackgroundColorToggled", typeof(Color), typeof(Color), Color.Green);
    public static BindableProperty ButtonFillColorProperty = BindableProperty.Create("ButtonFillColor", typeof(Color), typeof(Color), Color.White);
    public static BindableProperty IsToggledProperty = BindableProperty.Create("IsToggled", typeof(bool), typeof(bool), false);


    public Color OutlineColor
    {
        get { return (Color)GetValue(OutlineColorProperty); }
        set { SetValue(OutlineColorProperty, value); }
    }

    public Color ToggledBackgroundColor
    {
        get { return (Color)GetValue(SwitchBackgroundColorToggledProperty); }
        set { SetValue(SwitchBackgroundColorToggledProperty, value); }
    }

    public Color SwitchBackgroundColor
    {
        get { return (Color)GetValue(SwitchBackgroundColorProperty); }
        set { SetValue(SwitchBackgroundColorProperty, value); }
    }

    public Color ButtonFillColor
    {
        get { return (Color)GetValue(ButtonFillColorProperty); }
        set { SetValue(ButtonFillColorProperty, value); }
    }

    public bool IsToggled
    {
        get { return (bool)GetValue(IsToggledProperty); }
        set {
            SetValue(IsToggledProperty, value);
            OnToggleChange?.Invoke(this, value);
        }
    }

    #endregion

    public CustomSwitch()
    {
        InitializeComponent();
    }

    public event EventHandler<bool> OnToggleChange;

    private SKColor? animatedBgColor = null;
    private SKPoint buttonPosition = new SKPoint(30, 30);
    private bool isAnimating = false;

    protected virtual void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
    {
        SKImageInfo info = e.Info;
        SKSurface surface = e.Surface;
        SKCanvas canvas = surface.Canvas;
        canvas.Clear();

        SKPaint primaryFill = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = animatedBgColor != null ? animatedBgColor.Value : IsToggled ? ToggledBackgroundColor.ToSKColor() : SwitchBackgroundColor.ToSKColor(),
            IsAntialias = true
        };
        SKPaint primaryOutline = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 2,
            Color = OutlineColor.ToSKColor(),
            IsAntialias = true
        };
        SKPaint circleFill = new SKPaint
        {
            Style = SKPaintStyle.Fill,
            Color = ButtonFillColor.ToSKColor(),
            IsAntialias = true
        };
        SKPaint circleOutline = new SKPaint
        {
            Style = SKPaintStyle.Stroke,
            StrokeWidth = 2,
            Color = OutlineColor.ToSKColor(),
            IsAntialias = true
        };
        SKRoundRect rect = new SKRoundRect(new SKRect(0, 0, 120, 60), 30, 30);
        SKRoundRect rectOutline = new SKRoundRect(new SKRect(1, 1, 119, 59), 28, 28);
        if (!isAnimating)
            buttonPosition.X = IsToggled ? 90f : 30f;
        canvas.DrawRoundRect(rect, primaryFill);
        canvas.DrawRoundRect(rectOutline, primaryOutline);

        canvas.DrawCircle(buttonPosition, 24, circleFill);
        canvas.DrawCircle(buttonPosition, 23, circleOutline);
    }

    private void AnimateToToggle()
    {
        isAnimating = true;
        new Animation((value) =>
        {
            double colorPartWeight = 1 - value;
            animatedBgColor = SkiaTools.CalculateWeightedColor(SwitchBackgroundColor, ToggledBackgroundColor, colorPartWeight, value);
            PrimaryCanvas.InvalidateSurface();
        }).Commit(this, "bgcolorAnimation", length: (uint)250, repeat: () => false);

        new Animation((value) =>
        {
            buttonPosition.X = 30 + (float)(value * 60.0);
        }).Commit(this, "positionAnimation", length: (uint)250, repeat: () => false, finished: (v,c) => { buttonPosition.X = 90.0f; isAnimating = false; });
    }

    private void AnimateFromToggle()
    {
        isAnimating = true;
        new Animation((value) =>
        {
            double colorPartWeight = 1 - value;
            animatedBgColor = SkiaTools.CalculateWeightedColor(ToggledBackgroundColor, SwitchBackgroundColor, colorPartWeight, value);
            PrimaryCanvas.InvalidateSurface();
        }).Commit(this, "bgcolorAnimation", length: (uint)250, repeat: () => false);

        new Animation((value) =>
        {
            buttonPosition.X = 90 - (float)(value * 60.0);
        }).Commit(this, "positionAnimation", length: (uint)250, repeat: () => false, finished: (v, c) => { buttonPosition.X = 30.0f; isAnimating = false; });
    }

    private void OnElementTapped(object sender, EventArgs e)
    {
        IsToggled = !IsToggled;
        if (IsToggled == true)
            AnimateToToggle();
        else
            AnimateFromToggle();
    }
}

Also create a new class called SkiaTools.cs which holds some necessary static methods:

SkiaTools.cs

public static class SkiaTools
{
    public static SKColor CalculateWeightedColor(Xamarin.Forms.Color from, Xamarin.Forms.Color to, double weightA, double weightB)
    {
        double r = (from.R * weightA) + (to.R * weightB);
        double g = (from.G * weightA) + (to.G * weightB);
        double b = (from.B * weightA) + (to.B * weightB);
        double a = (from.A * weightA) + (to.A * weightB);

        byte bR = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(r * 256.0)));
        byte bG = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(g * 256.0)));
        byte bB = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(b * 256.0)));
        byte bA = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(a * 256.0)));

        return new SKColor(bR, bG, bB, bA);
    }

    public static SKColor ToSkColor(this Xamarin.Forms.Color xcolor)
    {
        byte r = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(xcolor.R * 256.0)));
        byte g = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(xcolor.G * 256.0)));
        byte b = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(xcolor.B * 256.0)));
        byte a = (byte)Math.Max(0, Math.Min(255, (int)Math.Floor(xcolor.A * 256.0)));

        return new SKColor(r,g,b,a);
    }
}

Now finally in your views or pages you can use the switch by using the following xaml:

<local:CustomSwitch Margin="10,5,10,5" SwitchBackgroundColor="White" OutlineColor="LightGray" ButtonFillColor="White" ToggledBackgroundColor="Green" IsToggled="True" OnToggleChange="CustomSwitch_OnToggleChange"/>

The result will look like this (untoggled/toggled):

enter image description here

Upvotes: 5

Arvind Chourasiya
Arvind Chourasiya

Reputation: 17412

There is easy solution available without using custom renderer. Install Xamarin.Toolkit.Effects.dll Packages from NuGet package manager. Than use it like this

<StackLayout>
    <Label Text="Regular switch" />
    <Switch x:Name="ChangeColorSwitch" Toggled="ChangeColorSwitch_Toggled"  
            effects:SwitchChangeColor.TrueColor="#E32636" 
            effects:SwitchChangeColor.FalseColor="#5D8AA8"  />
</StackLayout>

Declaring effects

xmlns:effects="clr-namespace:Xamarin.Toolkit.Effects;assembly=Xamarin.Toolkit.Effects"

Capturing Switch Toggled event

private void ChangeColorSwitch_Toggled(object sender, ToggledEventArgs e)
{
    var isSwitchOn = e.Value; //e.Value is type of bool
}

Android output

enter image description here

iOS output

enter image description here

Upvotes: 0

Related Questions