Draw a circle with a Xamarin Forms custom renderer

Xamarin Forms is getting better with each iteration, there's no doubt about that. We have more controls at our disposal than Win32 devs started with. If it doesn't fit our needs you can use effects. Changing a BoxView into a circle seems to be possible, but felt a bit wrong. With Xamarin Forms we can still use native implementations with custom renderers.

Creating a custom control

First you create a custom control so you can use it in your PCL where your XAML lives. As I don't use the BoxView to start with I inherited from a View. Next add the bindable properties you want to set via your XAML. So I added 2 color properties and 1 property that enables the circle to draw as active (it uses the fillcolor for that) or inactive (just the stroke).

public class CircleDot: View  
{
    public static readonly BindableProperty FillColorProperty =
           BindableProperty.Create(nameof(FillColor), typeof(Color), typeof(CircleDot), Color.Black);

    public Color FillColor
    {
        get { return (Color)GetValue(FillColorProperty); }
        set { SetValue(FillColorProperty, value); }
    }

    public static readonly BindableProperty StrokeColorProperty =
           BindableProperty.Create(nameof(StrokeColor), typeof(Color), typeof(CircleDot), Color.Black);

    public Color StrokeColor
    {
        get { return (Color)GetValue(StrokeColorProperty); }
        set { SetValue(StrokeColorProperty, value); }
    }

    public static readonly BindableProperty ActiveProperty =
          BindableProperty.Create(nameof(Active), typeof(bool), typeof(CircleDot), false);

    public bool Active
    {
        get { return (bool)GetValue(ActiveProperty); }
        set { SetValue(ActiveProperty, value); }
    }
}

Creating the custom renderer files

In our Android and iOS project we need a custom renderer class so our platforms knows how to render the CircleDot view we created.

The class that you create in your Android or iOS project must inherit from ViewRenderer. The generic ViewRenderer expects two types the CircleDot view from our PCL and the native representation. For iOS this will be an UIView, for Android an Android.Views.View (not to confuse with a Xamarin Forms view).

// For iOS:
public class CircleDotRenderer: ViewRenderer<CircleDot, UIView>

// For Android 
public class CircleDotRenderer: ViewRenderer<CircleDot, Android.Views.View>  

We also need to register our custom renderer. Above the namespace of your custom renderer class add:

[assembly: ExportRenderer(typeof(CircleDot), typeof(CircleDotRenderer))]

Basic Android custom renderer

For Android we need to tell the system that our control will draw itself via

this.SetWillNotDraw(false);  

Now we need to override the OnElementChanged and OnElementPropertyChanged methods. In the OnElementPropertyChanged we need to tell Android that if our ActiveProperty changed it needs to redraw itself:

protected override void OnElementChanged(ElementChangedEventArgs<CircleDot> e)  
{
    base.OnElementChanged(e);
    if (Control == null)
    {
        var circleDotView = new Android.Views.View(Forms.Context);
        SetNativeControl(circleDotView);
    }
}

protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)  
{
    base.OnElementPropertyChanged(sender, e);
    if (e.PropertyName == CircleDot.ActiveProperty.PropertyName)
    {
        Invalidate();
    }
}

Basic iOS custom renderer

We need to override the OnElementChanged and OnElementPropertyChanged methods. In the OnElementPropertyChanged we need to tell iOS that if our ActiveProperty changed it needs to redraw itself:

protected override void OnElementChanged(ElementChangedEventArgs<CircleDot> e)  
{
    base.OnElementChanged(e);
    if (Control == null)
    {
        var circleDotView = new UIView();
        SetNativeControl(circleDotView);
    }
}

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)  
{
    base.OnElementPropertyChanged(sender, e);
    if (e.PropertyName == CircleDot.ActiveProperty.PropertyName)
    {
        this.SetNeedsDisplay();
    }
}            

Creating canvas code

We still need to tell the custom renderers how the need to render/draw themselves.

I must admit that I'm not an expert in drawing native things on a canvas. So, where did I start? I used a program named PaintCode to help me out.

PaintCode

When you've worked with Sketch, you'll feel quickly at home with PaintCode. If you take a moment and watch their tutorials (https://www.paintcodeapp.com/tutorials) you'll be up and running in no time.

So I created a canvas (aha, sounds familiar for what we need for the custom renderers), draw a circle, defined two colors (ColorFill and ColorStroke) and linked them to the drawn circle. The last thing I did was creating a variable CircleDotActive and linked it to the visibility of the CircleDotFill object.

Link variable in PaintCode

Don't forget to embed your artefacts in a frame. It allows the circle to adapt itself to the available space of the custom control we're creating.

Now PaintCode will provide us the code we need to draw the CircleDot in iOS:

iOS generated code for our CircleDot

The only thing we need to change (for iOS and for Android) is replacing the circleDotActive check with the bindable active property from our custom control:

//Replace
if(circleDotActive)

//with
if (Element.Active)  

On iOS override the Draw method with the generated code.

On Android things are a bit more difficult. PaintCode doesn't generate Xamarin Android code but java code. Lucky for us the code is very similar!

On Android override the OnDraw method with the generated java code and change some method names.

Download

Download the code from my GitHub