Reputation: 24562
My application uses switches which look like this:
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
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):
Upvotes: 5
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
iOS output
Upvotes: 0