iamsophia
iamsophia

Reputation: 796

Xamarin Forms Switch's Toggled event not being triggered when using custom renderer

I have a Xamarin.Forms app and I have created a custom Switch as below:

public class ExtSwitch : Switch
{
   public static readonly BindableProperty SwitchOnColorProperty =
          BindableProperty.Create(nameof(SwitchOnColor),
                                  typeof(Color), typeof(ExtSwitch), Color.Default);

   public Color SwitchOnColor
   {
      get { return (Color)GetValue(SwitchOnColorProperty); }
      set { SetValue(SwitchOnColorProperty, value); }
   }

   // More codes here //
}

In My XAML I used it like:

<local:ExtSwitch Grid.Column = "2"
       IsToggled="{Binding IsToggled}" 
       Toggled="Handle_Toggled"
       SwitchThumbColor="White" 
       SwitchOnColor="Red" 
       SwitchOffColor="Gray"
       HorizontalOptions="End" 
       VerticalOptions="Center" />

In My C# code I have a Handle_Toggled method that handles what happened when the Swich is toggled. But somehow the Toggled event is not being triggered when used inside my custom switch but works perfectly when used in a normal switch.

Can someone point to me what am I missing here or what am I doing wrong?

Edit:

Custom renderer in iOS:

class ExtSwitchRenderer : SwitchRenderer
{
   protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
   {
       base.OnElementChanged(e);

       if (e.OldElement != null || e.NewElement == null) return;

       ExtSwitch s = Element as ExtSwitch;

       UISwitch sw = new UISwitch();
       sw.ThumbTintColor = s.SwitchThumbColor.ToUIColor();
       sw.OnTintColor = s.SwitchOnColor.ToUIColor();

       SetNativeControl(sw);
    }
}

Using the above code:

enter image description here

Using the code suggested below:

enter image description here

Custom renderer in Android:

public class ExtSwitchRenderer : SwitchRenderer
    {
        public ExtSwitchRenderer(Context context) : base(context) { }
        ExtSwitch s;

       protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Switch> e)
      {
        base.OnElementChanged(e);
        if (e.OldElement != null || e.NewElement == null)
            return;

        s = Element as ExtSwitch;
        if (this.Control != null)
        {
            if (this.Control.Checked)
            {
                this.Control.TrackDrawable.SetColorFilter(s.SwitchOnColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
            }
            else
            {
                this.Control.TrackDrawable.SetColorFilter(s.SwitchOffColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
            }
            this.Control.CheckedChange += this.OnCheckedChange;
        }
        Control.Toggle();
    }

    void OnCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
    {
        if (this.Control.Checked)
        {
            this.Control.ThumbDrawable.SetColorFilter(s.SwitchOnColor.ToAndroid(), PorterDuff.Mode.Multiply);
            this.Control.TrackDrawable.SetColorFilter(s.SwitchOnColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
        }
        else
        {
            this.Control.ThumbDrawable.SetColorFilter(s.SwitchOffColor.ToAndroid(), PorterDuff.Mode.Multiply);
            this.Control.TrackDrawable.SetColorFilter(s.SwitchOffColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
        }
    }
}

Upvotes: 4

Views: 1780

Answers (3)

Christopher Stephan
Christopher Stephan

Reputation: 1141

SetColorFilter is deprecated in the meantime. With package Xamarin.AndroidX.Core you can use the renderer like this:

public class ColoredSwitchRenderer : SwitchRenderer
{
    private readonly Android.Graphics.Color _defaultColor = ((Color)Application.Current.Resources["Default"]).ToAndroid();
    private readonly Android.Graphics.Color _activeColor = ((Color)Application.Current.Resources["Active"]).ToAndroid();
    private readonly Android.Graphics.Color _inactiveColor = ((Color)Application.Current.Resources["Inactive"]).ToAndroid();

    public ColoredSwitchRenderer(Context context) : base(context)
    {
    }

    protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.Switch> e)
    {
        base.OnElementChanged(e);

        if (Control == null)
        {
            return;
        }

        SetThumbColor();
        UpdateTrackColors();
        Control.CheckedChange += OnCheckedChange;
    }

    private void SetThumbColor()
    {
        var filter = BlendModeColorFilterCompat.CreateBlendModeColorFilterCompat(_defaultColor, BlendModeCompat.SrcAtop);
        Control.ThumbDrawable.SetColorFilter(filter);
    }

    private void OnCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
    {
        UpdateTrackColors();
        Element.IsToggled = Control.Checked;
    }

    private void UpdateTrackColors()
    {
        if (Control.Checked)
        {
            var filter = BlendModeColorFilterCompat.CreateBlendModeColorFilterCompat(_activeColor, BlendModeCompat.Src);
            Control.TrackDrawable?.SetColorFilter(filter);
        }
        else
        {
            var filter = BlendModeColorFilterCompat.CreateBlendModeColorFilterCompat(_inactiveColor, BlendModeCompat.Src);
            Control.TrackDrawable?.SetColorFilter(filter);
        }
    }

    protected override void Dispose(bool disposing)
    {
        Control.CheckedChange -= OnCheckedChange;
        base.Dispose(disposing);
    }
}

and for the sake of completeness the iOS renderer:

public class ColoredSwitchRenderer : SwitchRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
    {
        base.OnElementChanged(e);

        if (Control != null)
        {
            Application.Current.Resources.TryGetValue("Active", out var activeColor);
            Control.OnTintColor = ((Color)activeColor).ToUIColor();

            Application.Current.Resources.TryGetValue("Default", out var defaultColor);
            Control.ThumbTintColor = ((Color)defaultColor).ToUIColor();

            Application.Current.Resources.TryGetValue("Inactive", out var inactiveColor);
            Control.Subviews[0].Subviews[0].BackgroundColor = ((Color)inactiveColor).ToUIColor();
        }
    }
}

Upvotes: 0

Geral Torres
Geral Torres

Reputation: 109

on IOs renderer

protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
    {
        base.OnElementChanged(e);



        if (e.OldElement != null || e.NewElement == null) return;

        CustomSwitch s = Element as CustomSwitch;



        //this.Control.ThumbTintColor = s.SwitchThumbColor.ToUIColor();
        this.Control.OnTintColor = s.SwitchOnColor.ToUIColor();

    }

and Android renderer in OnCheckedChange

private void OnCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
   {
        if (this.Control.Checked)
        {
            this.Control.TrackDrawable.SetColorFilter(view.SwitchOnColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
        }
        else
        {
            this.Control.TrackDrawable.SetColorFilter(view.SwitchOffColor.ToAndroid(), PorterDuff.Mode.SrcAtop);
        }

        Element.IsToggled = Control.Checked;


    }

Upvotes: 7

sme
sme

Reputation: 4153

Don't set the native control, instead just update the current Control, like this:

this.Control.ThumbTintColor = s.SwitchThumbColor.ToUIColor();
this.Control.OnTintColor = s.SwitchOnColor.ToUIColor();

And theres no need to call SetNativeControl

Upvotes: 1

Related Questions