0

Does anyone have a complete example of a Xamarin Forms android button custom renderer? Or is that even possible?

I am basically looking to make round corners button.

I tried starting from this example: https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/entry/

Using http://taoffi.isosoft.org/post/2016/03/26/Xamarin-forms-so-you-lost-your-rounded-buttons

But I get loads of errors at compilation:

using System;
using System.Collections.Generic;
using System.Linq;
//using UXDivers.Artina.Shared;
using Xamarin.Forms;
using Android.Graphics.Drawables;
using System.ComponentModel;
using Xamarin.Forms.Platform.Android;
using Android.Graphics;
using Android.Views;
using System.Runtime.Remoting.Contexts;
using CustomRenderer.Android;

[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonCompatRenderer))]

namespace CustomRenderer.Android
{
    public class CustomButtonCompatRenderer : ButtonRenderer
    {
        public CustomButtonCompatRenderer(Context context) : base(context)
        {
            SetWillNotDraw(false);
        }


        private GradientDrawable _normal,
                                        _pressed;


        // resolves: button text alignment lost after click or IsEnabled change
        //public override void ChildDrawableStateChanged(Android.Views.View child)
        //{
        //  base.ChildDrawableStateChanged(child);
        //  Control.Text = Control.Text; 
        //}

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

            if (Control != null)
            {
                SetAlignment();

                var density = Math.Max(1, Resources.DisplayMetrics.Density);
                var button = e.NewElement;
                var mode = MeasureSpec.GetMode((int)button.BorderRadius);
                var borderRadius = button.BorderRadius * density;
                var borderWidth = button.BorderWidth * density;

                // Create a drawable for the button's normal state
                _normal = new Android.Graphics.Drawables.GradientDrawable();

                if (button.BackgroundColor.R == -1.0 && button.BackgroundColor.G == -1.0 && button.BackgroundColor.B == -1.0)
                    _normal.SetColor(Android.Graphics.Color.ParseColor("#ff2c2e2f"));
                else
                    _normal.SetColor(button.BackgroundColor.ToAndroid());

                _normal.SetStroke((int)borderWidth, button.BorderColor.ToAndroid());
                _normal.SetCornerRadius(borderRadius);

                // Create a drawable for the button's pressed state
                _pressed = new Android.Graphics.Drawables.GradientDrawable();
                var highlight = Context.ObtainStyledAttributes(new int[]
                                    {
                                        Android.Resource.Attribute.ColorAccent  //  .ColorActivatedHighlight
                                    }).GetColor(0, Android.Graphics.Color.Gray);

                _pressed.SetColor(highlight);
                _pressed.SetStroke((int)borderWidth, button.BorderColor.ToAndroid());
                _pressed.SetCornerRadius(borderRadius);

                // Add the drawables to a state list and assign the state list to the button
                var sld = new StateListDrawable();
                sld.AddState(new int[] { Android.Resource.Attribute.StatePressed }, _pressed);
                sld.AddState(new int[] { }, _normal);
                Control.SetBackground(sld);     //.SetBackgroundDrawable(sld); // deprecated
            }
        }

        private void SetAlignment()
        {
            var element = this.Element as Button;

            if (element == null || this.Control == null)
            {
                return;
            }

            this.Control.Gravity = GravityFlags.CenterHorizontal | GravityFlags.CenterVertical;
            //element.VerticalAlignment.ToDroidVerticalGravity() |  
            //element.HorizontalAlignment.ToDroidHorizontalGravity();  
        }

        void DrawCustom(Button targetButton)
        {
            if (Control == null || targetButton == null)
                return;

        }
    }
}

Errors:

CustomButtonCompatRenderer.cs(20,62,20,66): error CS1729: 'ButtonRenderer' does not contain a constructor that takes 1 arguments
CustomButtonCompatRenderer.cs(52,39,52,47): error CS0234: The type or namespace name 'Graphics' does not exist in the namespace 'CustomRenderer.Android' (are you missing an assembly reference?)
CustomButtonCompatRenderer.cs(55,38,55,54): error CS0234: The type or namespace name 'Graphics' does not exist in the namespace 'CustomRenderer.Android' (are you missing an assembly reference?)
CustomButtonCompatRenderer.cs(63,40,63,48): error CS0234: The type or namespace name 'Graphics' does not exist in the namespace 'CustomRenderer.Android' (are you missing an assembly reference?)
CustomButtonCompatRenderer.cs(66,68,66,79): error CS0117: 'Resource.Attribute' does not contain a definition for 'ColorAccent'
CustomButtonCompatRenderer.cs(67,52,67,68): error CS0234: The type or namespace name 'Graphics' does not exist in the namespace 'CustomRenderer.Android' (are you missing an assembly reference?)
CustomButtonCompatRenderer.cs(75,69,75,81): error CS0117: 'Resource.Attribute' does not contain a definition for 'StatePressed'

My environment:

  • Visual studio for mac: 7.3.3 (build 12)

  • Android SDK tools 26.1.1

  • Android SDK build-tools 26.0.3

  • Android SDK build-tools 25.0.3

  • Android Target version: Android 7.1 (API 25)

  • Minimum Android version: Android 4.0.3 (API 15)

PS: I am new to Xamarin and C#

1 Answer 1

2

Here it is. A ripple-enabled button with CornerRadius and Padding.

You'll have to add the usings, the namespace and the ExportRenderer attribute.

MainActivity.Activity is a property of type Context that I set when the app initializes. Just add this line: Activity = this; in the OnCreate method, before Forms.Init.

public class ButtonRenderer : Xamarin.Forms.Platform.Android.ButtonRenderer
{
    // Your Button class
    Button button;

    float radius = 0;
    //--------------------------------------------------------------------------------//
    public ButtonRenderer() : base(MainActivity.Activity) { }

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

        button = Element as Button;

        if (e.OldElement == null)
        {
            if (IsPostLollipopAndroid) Control.Elevation = 2;

            radius = (float)button.CornerRadius;

            if (button.Padding != null)
            {
                Control.SetPadding(ToPx((int)button.Padding.Left), ToPx((int)button.Padding.Top), ToPx((int)button.Padding.Right), ToPx((int)button.Padding.Bottom));
            }

            SetColors();

            Control.SetMinHeight(0);
            Control.SetMinimumHeight(0);
            Control.SetMinWidth(0);
            Control.SetMinimumWidth(0);
        }
    }
    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);

        switch (e.PropertyName)
        {
            case nameof(VisualElement.BackgroundColor):
            case nameof(Button.PressedColor):
                {
                    SetColors();

                    break;
                }
            case nameof(Button.Padding):
                {
                    if (button.Padding != null)
                    {
                        Control.SetPadding((int)button.Padding.Left, (int)button.Padding.Top, (int)button.Padding.Right, (int)button.Padding.Bottom);
                    }

                    break;
                }
        }
    }

    public override bool DispatchTouchEvent(MotionEvent e)
    {
        if (!Element.InputTransparent) return base.DispatchTouchEvent(e);
        else return true;
    }

    void SetColors()
    {
        radius = (float)button.CornerRadius;

        StateListDrawable list = new StateListDrawable();

        if (IsPreLollipopAndroid)
        {
            GradientDrawable pressedDrawable;

            if (Element.BackgroundColor != Color.Transparent)
            {
                Android.Graphics.Color pressedColor = ChangeColorBrightness(Element.BackgroundColor, (button.PressedColor == ButtonPressedColor.Dark) ? 1f - (float).15 : 1f + (float).15).ToAndroid();
                pressedDrawable = new GradientDrawable(GradientDrawable.Orientation.LeftRight, new int[] { pressedColor, pressedColor });

                pressedDrawable.SetCornerRadius(ToPxFloat(radius));
            }
            else
            {
                Android.Graphics.Color pressedColor = (button.PressedColor == ButtonPressedColor.Dark) ? Color.Black.MultiplyAlpha(.15).ToAndroid() : Color.White.MultiplyAlpha(.15).ToAndroid();
                pressedDrawable = new GradientDrawable(GradientDrawable.Orientation.LeftRight, new int[] { pressedColor, pressedColor });

                pressedDrawable.SetCornerRadius(ToPxFloat(radius));
            }

            list.AddState(new int[] { Android.Resource.Attribute.StatePressed }, pressedDrawable);

            //--------------------------------------------------------------------------------//

            Android.Graphics.Color color = Element.BackgroundColor.ToAndroid();
            var normalDrawable = new GradientDrawable(GradientDrawable.Orientation.LeftRight, new int[] { color, color });

            normalDrawable.SetCornerRadius(ToPxFloat(radius));

            list.AddState(new int[] { -Android.Resource.Attribute.StatePressed }, normalDrawable);

            Control.Background = list;
        }
        else
        {
            GradientDrawable normalDrawable = new GradientDrawable();
            normalDrawable.SetCornerRadius(ToPxFloat(radius));
            normalDrawable.SetColor(Element.BackgroundColor.ToAndroid());

            float[] outerRadii = Enumerable.Repeat(ToPxFloat(radius), 8).ToArray();

            RoundRectShape r = new RoundRectShape(outerRadii, null, null);

            ShapeDrawable shapeDrawable = new ShapeDrawable(r);
            shapeDrawable.Paint.Color = Color.White.ToAndroid();

            var pressedColor = (button.PressedColor == ButtonPressedColor.Dark) ? Color.Black.MultiplyAlpha(.15).ToAndroid() : Color.White.MultiplyAlpha(.15).ToAndroid();
            var ripple = new RippleDrawable(ColorStateList.ValueOf(pressedColor), normalDrawable, shapeDrawable);

            Control.Background = ripple;
        }
    }

    public static int ToPx(double dp)
    {
        return (int)Android.Util.TypedValue.ApplyDimension(Android.Util.ComplexUnitType.Dip, (float)dp, Droid.MainActivity.Activity.Resources.DisplayMetrics);
    }

    public static float ToPxFloat(double dp)
    {
        return Android.Util.TypedValue.ApplyDimension(Android.Util.ComplexUnitType.Dip, (float)dp, Droid.MainActivity.Activity.Resources.DisplayMetrics);
    }

    public static bool IsPreLollipopAndroid => Android.OS.Build.VERSION.SdkInt < Android.OS.BuildVersionCodes.Lollipop;
    public static bool IsPostLollipopAndroid => Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Lollipop;

    public static Color ChangeColorBrightness(Color color, float factor)
    {
        return Color.FromRgba(color.R * factor, color.G * factor, color.B * factor, color.A);
    }
}

The Button class has Padding, CornerRadius and PressedColor (an enum with Dark and Light fields).

Hope it helps!

Sign up to request clarification or add additional context in comments.

2 Comments

What is "mainactivity.activity". Could you show your activity class?
Oh, sorry, I forgot about that... I'll edit the answer

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.