c# - 如何在 Xamarin Forms 中复制 iOS 开关的功能,使其在 iOS 和 Android 中看起来相同?

标签 c# xamarin xamarin.forms

我的应用程序使用如下所示的开关:

enter image description here

我想在 Xamarin Forms 中复制此功能,以便它在 iOS 和 Android 中显示和看起来完全像这个 iOS 版本。

我该怎么做呢?如果可能的话,我想要一个不使用任何 iOS 和 Android 渲染器的解决方案。取而代之的是仅使用 Forms 的 TapGestureRecognizer。

最佳答案

这是一个使用 SkiaSharp 的解决方案。通过 nuget 将 SkiaSharp 和 SkiaSharp.Views.Forms 包含到您的项目中。

现在创建一个新的内容 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();
    }
}

同时创建一个名为 SkiaTools.cs 的新类,其中包含一些必要的静态方法:

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);
    }
}

现在终于可以在您的 View 或页面中使用以下 xaml 来使用开关了:

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

结果将如下所示(未切换/切换):

enter image description here

关于c# - 如何在 Xamarin Forms 中复制 iOS 开关的功能,使其在 iOS 和 Android 中看起来相同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53076616/

相关文章:

c# - 是否可以禁用 C# 中的函数

c# - 这两个代码片段中的第一个在完成更多工作时如何比第二个快 3 倍?

linq - Xamarin Forms - 如何从 MergedDictionaries 中选择 C# 中的资源

xamarin - 如何使 ACR 用户对话框 ShowLoading 方法作为异步任务运行?

c# - Xamarin.forms Acr userdialog 加载指示器未显示

c# - 如何从 xamarin.forms 中的 Google 电子表格中读取数据

c# - 获取用户在浏览器中输入的确切网址

c# - ASP.NET Core 2.1 Swagger(swashbuckle) Url模板可选参数

android - 如何在 Android 中为通知区域和通知抽屉使用不同的通知图标

c# - 图像编码为 ccitt 的 iOS pdf