Xamarin.Forms gradient background for Button using

2020-07-24 06:24发布

问题:

I am using Xamarin.Forms and I want to globally make the buttons look a little nicer.

I have achieved this just fine for the Android version using a custom renderer, but I am having trouble doing the same with iOS.

When defining buttons in my XAML pages, I reference "CustomButton" instead of "Button", and then I have the following CustomButtonRenderer in my iOS app.

Most of the style changes work just fine (border radius, etc), but I cannot seem to make it render a background gradient for the button.

Here is my code so far, but the background just displays as white. How can I make it display a gradient with the text on top?

class CustomButtonRenderer : ButtonRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
    {
        base.OnElementChanged(e);

        if (Control != null)
        {
            var gradient = new CAGradientLayer();
            gradient.Frame = Control.Layer.Bounds;
            gradient.Colors = new CGColor[]
            {
                UIColor.FromRGB(51, 102, 204).CGColor,
                UIColor.FromRGB(51, 102, 204).CGColor
            };

            Control.Layer.AddSublayer(gradient);
            Control.Layer.CornerRadius = 10;
            Control.Layer.BorderColor = UIColor.FromRGB(51, 102, 204).CGColor;
            Control.Layer.BorderWidth = 1;

            Control.VerticalAlignment = UIControlContentVerticalAlignment.Center;
        }
    }
}

回答1:

1st) Do not use AddSublayer, use InsertSublayerBelow so that the Z-order will be correct and your Title text will be on top.

2nd) Override LayoutSubviews and update your CAGradientLayer frame to match your UIButton.

3rd) Enjoy your gradient:

Complete Example:

[assembly: ExportRenderer(typeof(CustomButton), typeof(CustomButtonRenderer))]
namespace AppCompatRender.iOS
{
    public class CustomButtonRenderer : Xamarin.Forms.Platform.iOS.ButtonRenderer
    {
        public override void LayoutSubviews()
        {
            foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
                layer.Frame = Control.Bounds;
            base.LayoutSubviews();
        }

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

            if (e.OldElement == null)
            {
                var gradient = new CAGradientLayer();
                gradient.CornerRadius = Control.Layer.CornerRadius = 10;
                gradient.Colors = new CGColor[]
                {
                    UIColor.FromRGB(51, 102, 104).CGColor,
                    UIColor.FromRGB(51, 202, 204).CGColor
                };
                var layer = Control?.Layer.Sublayers.LastOrDefault();
                Control?.Layer.InsertSublayerBelow(gradient, layer);
            }
        }

    }
}

Update:

If you are using iOS 10+ with newer version of Xamarin.Forms, the Control.Bounds during calls to LayoutSubViews will all be zeros. You will need to set the gradient layer Frame size during sets to the control's Frame property, i.e.:

public class CustomButtonRenderer : Xamarin.Forms.Platform.iOS.ButtonRenderer
{
    public override CGRect Frame
    {
        get
        {
            return base.Frame;
        }
        set
        {
            if (value.Width > 0 && value.Height > 0)
            {
                foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
                    layer.Frame = new CGRect(0, 0, value.Width, value.Height);
            }
            base.Frame = value;
        }
    }

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

        if (e.OldElement == null)
        {
            var gradient = new CAGradientLayer();
            gradient.CornerRadius = Control.Layer.CornerRadius = 20;
            gradient.Colors = new CGColor[]
            {
                UIColor.FromRGB(51, 102, 104).CGColor,
                UIColor.FromRGB(51, 202, 204).CGColor
            };
            var layer = Control?.Layer.Sublayers.LastOrDefault();
            Control?.Layer.InsertSublayerBelow(gradient, layer);
        }
    }
}


回答2:

In the moment that OnElementChanged is called, Control.Layer.Bounds is completely zero'd out. In your rendered you will need to add methods to update the Gradient's Frame to match the Control.Layer's frame.



回答3:

Responding to the comment by Robert Cafazzo, I can help to slightly adjust this render so that it works correctly:

public class GdyBtnRendererIos : ButtonRenderer
    {
        #region Colors
        static Color rosecolor = (Color)App.Current.Resources["ClrGeneralrose"];
        static Color orangecolor = (Color)App.Current.Resources["ClrRoseOrange"];
        CGColor roseCGcolor = rosecolor.ToCGColor();
        CGColor orangeCGcolor = orangecolor.ToCGColor();
        #endregion

        CAGradientLayer gradient;

        public override CGRect Frame
        {
            get => base.Frame;
            set
            {
                if (value.Width > 0 && value.Height > 0)
                {
                    if (Control?.Layer.Sublayers != null)
                        foreach (var layer in Control?.Layer.Sublayers.Where(layer => layer is CAGradientLayer))
                            layer.Frame = new CGRect(0, 0, value.Width, value.Height);
                }
                base.Frame = value;
            }
        }

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

            if (e.PropertyName == "Renderer")
            {
                gradient.Frame = new CGRect(0, 0, Frame.Width, Frame.Height);
            }
        }

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

            if (e.OldElement != null) return;

            gradient = new CAGradientLayer
            {
                CornerRadius = Control.Layer.CornerRadius,
                Colors = new CGColor[] { roseCGcolor, orangeCGcolor },
                StartPoint = new CGPoint(0.1, 0.5),
                EndPoint = new CGPoint(1.1, 0.5)
            };

            var layer = Control?.Layer.Sublayers.LastOrDefault();
            Control?.Layer.InsertSublayerBelow(gradient, layer);

            base.Draw(Frame);
        }