Who cares about the view anyway?

While working on an issue, I discovered a cool way in which to draw using SkiaSharp – without caring about view sizes at all.

In many cases, drawing code does not really care about the actual view sizes or canvas sizes. For example, if you are drawing a button, the drawing code is going to fill the entire view. If you are working on some image, you typically want the image to fill the view.

This is because in most cases, the UI framework will do all the view sizing based on your constraints, available space and any parameters. Then, you want to draw the best image on the size that the UI framework gave you.

Let’s take a practical example: we want to make a badge with a number on it.


If we assumed that the badge will always be 15×15, then we would think this would be easy. All our sizes would be around that number. But since we are drawing a circle from the center, we actually have a center of 7.5 and a radius of 7.5. This is not a problem in any way for SkiaSharp, but it is a bit for us as we now have more things to do.

If we were to draw this without checking screen density or actual view size, we might get something like this:

Tiny badge

This is because we have used numbers like 15 and 7.5. These numbers are actually tiny when compared to the thousands of pixels on modern devices.

If we used bigger numbers, this help, but not fix all the issues. For example, this might happen:

Too big Too small

This is a result of the way SkiaSharp and UI frameworks work. The way SkiaSharp does pixels is a bit different to typical UI frameworks. When designing a UI, the framework knows about screen densities so this typically means that UI frameworks will scale UI elements automatically.

For example, if there is a view with a size of 15×15, in most cases you would expect this view to be the same size across all screens and devices. So, if you were on an iPhone, this might translate to a 30×30 view. On some Android devices, they use a density of 3.5, so this translates to a 52.5. As a result of the screen density, placing the devices side by side results in very similar view sizes to our eyes.

However, SkiaSharp does not care about screen densities (maybe incorrectly, but that is for an open issue). Regardless of the device or platform, it asks the OS for the exact bounds of the view – in raw pixels. So, if we take our previous examples of iOS and Android, we will end up with different canvas sizes: 30×30 and a 52×52 (because there is no such thing as a .5 raster pixel).

Drawing Correctly

If we know that SkiaSharp does not scale automatically, we can use simple arithmetic to work out that we can just scale before we draw.

In all our drawing code examples, we are going to draw a 15×15 circle in the center of the view so that it touches the edges:

  // assume a perfect 15x15 canvas and view
  canvas.DrawCircle(7.5f, 7.5f, 7.5f, paint);

Screen Desnsity

To handle screen density, we could use something like Xamarin.Essentials. There is a nice DeviceDisplay API that will give us the main screen’s density:

// get the density
var density = (float)DeviceDisplay.MainDisplayInfo.Density;

// scale to the density

// draw
canvas.DrawCircle(7.5f, 7.5f, 7.5f, paint);

This would work.

In that one case.

If we need to support devices where density may change or has multiple displays, then we can use a trick of just dividing the canvas size by the view size:

  var density = e.Info.Width / (float)this.Width;

View Size

What happens if you decide that a 15×15 badge is too small and want to use 16×16 or even a big 20×20? Or, in a case where 15×15 is too big? You would think to just make the view bigger and all would be well.

Unfortunately, it won’t.

Because you may have used sizes like radius = 7.5f or a textSize = 8f.

Using exact sizes is often just fine, if you are very sure that the view size will never change. But, as developers, we can not assume that.

A smart developer might think to use the view size instead of hard coordinates. Then using arithmetic, scale things relative to that:

// get the density
var density = e.Info.Width / (float)this.Width;

// scale to the density

// draw
var halfWidth = (float)this.Width / 2f;
canvas.DrawCircle(halfWidth, halfWidth, halfWidth, paint);

That would work.

In many cases.

If you have a lot of drawing to do, there may be many, many division and multiplication operations. Modern CPUs are very fast, but why even do that? Also, with every draw, you might end up with many other operations to calculate the size and position.

An example would be if padding was needed. If you were going for a 2px padding, then this would also have to be calculated:

var halfWidth = (float)this.Width / 2f;
var padding = 2f;
canvas.DrawCircle(halfWidth, halfWidth, halfWidth - (padding * 2), paint);

And this gets worse if you want the padding to be based on the circle size, maybe with a 10% padding:

var halfWidth = (float)this.Width / 2f;
var padding = (float)this.Width / 10f;
canvas.DrawCircle(halfWidth, halfWidth, halfWidth - (padding * 2), paint);

This involves lots of arithmetic – who really wants to deal with that when trying to draw a basic circle?

Not me.

Arbitrary Size

What if we could draw things using any base size, and then let the graphics engine do all the work? Like it was designed to do.

Now this is probably not something that I invented or discovered. I may have thought of it now, but I am not always the brightest person so that basically means everyone already knows. But, let’s assume that there was one other person that didn’t know.

So, what if we could take our 15×15 circle and convert that thought into a 100×100 circle and let the engine do the work to make our numbers appear as if it was 15×15?

We can do that with SkiaSharp.

It is called scaling. Pretty obvious, I know, but it only took a few years.

Instead of doing “advanced” arithmetic to calculate sizes, offsets and percentages, we can use any base size that makes things easier. In our example, we have a full-view circle with a number in the center.

So why not use 100 as the base size and have the circle have a radius of 50? Well, we can.

// pick a cool number for us
const float baseSize = 100f;

var canvas = e.Surface.Canvas;

// scale to our base unit
canvas.Scale(e.Info.Width / baseSize);

var circleFillPaint = new SKPaint {
    IsAntialias = true,
    Style = SKPaintStyle.Fill,
    Color = 0xFFFF5722,

// center of 50x50 and a radius of 50
canvas.DrawCircle(50f, 50f, 50f, circleFillPaint);

// we can even use nice numbers for font sizes
var textPaint = new SKPaint {
    IsAntialias = true,
    TextSize = 70f,
    Color = SKColors.White,

// measure the text width
var textBounds = SKRect.Empty;
var advance = textPaint.MeasureText(BadgeValue, ref textBounds);

// draw the text at the center (x = 50 - half the width; y = 100 - half text height)
canvas.DrawText(BadgeValue, 50f - (advance / 2f), 100f - (textBounds.Height / 2f), textPaint);

Now, we can use this exact code to render a perfect badge, regardless of the view size, the screen density or any other view-related property. The reason for this is that the first thing we do is convert from drawing units into our perfect unit:

canvas.Scale(e.Info.Width / 100f);

As long as our numbers are based on 100, then we can never go wrong.

Turning Events into Commands

Have you ever used some control in Xamarin.Forms that appears to have an event instead of a command? You are working the MVVM love and then you come across that annoying control. Almost all the Xamarin.Forms controls have both events and commands, enabling choice, but you do get those non-conforming ones…

I was lurking on the Xamarin forums and I came across a question regarding the event-only design of the SkiaSharp SKCanvasView. The SKCanvasView has a PaintSurface event that allows you to draw on the view with SkiaSharp commands.

There is nothing wrong with the event as it stands, but things get messy when all the data is in a view model. In simple cases, this is easy to resolve. Take a small example where there is only one view model, which is the binding context for an entire Page:

public partial class MainPage : ContentPage
    public MainPage()

        BindingContext = new MainViewModel();

And the view model looks like this:

public class MainViewModel
    public MainViewModel()
        PaintCommand = new Command<SKPaintSurfaceEventArgs>(OnPainting);

    public ICommand PaintCommand { get; private set; }

    private void OnPainting(SKPaintSurfaceEventArgs e)
        // ... draw ...

Where, or rather how, does one get an event somewhere on the page to the view model? Well, one way would be to attach a handler to the view and then pass the event arguments to the view model:

canvasView.PaintSurface += (sender, e) =>
    // we can do this because of our simple example
    var viewModel = (MainViewModel)BindingContext;

    // fire the command
    if (viewModel.PaintCommand.CanExecute(e))

This will work as everything is pretty much straight-forward. However, what happens if the canvas is in a ListView or if there are several canvases on the page? We would end up with a messy code-behind and probably a few memory leaks.

A much better way to solve this problem would be to use Xamarin.Forms’ behaviors:

Behaviors lets you add functionality to user interface controls without having to subclass them. Behaviors are written in code and added to controls in XAML or code.

Behaviors are easy to create and easy to use. The first thing we need to do is create our specific behavior:

public class PaintSurfaceCommandBehavior : Behavior<SKCanvasView>
    // we need a bindable property for the command
    public static readonly BindableProperty CommandProperty =

    // the command property
    public ICommand Command
        get { return (ICommand)GetValue(CommandProperty); }
        set { SetValue(CommandProperty, value); }

    // invoked immediately after the behavior is attached to a control
    protected override void OnAttachedTo(SKCanvasView bindable)

        // we want to be notified when the view's context changes
        bindable.BindingContextChanged += OnBindingContextChanged;
        // we are interested in the paint event
        bindable.PaintSurface += OnPaintSurface;

    // invoked when the behavior is removed from the control
    protected override void OnDetachingFrom(SKCanvasView bindable)

        // unsubscribe from all events
        bindable.BindingContextChanged -= OnBindingContextChanged;
        bindable.PaintSurface -= OnPaintSurface;

    // the view's context changed
    private void OnBindingContextChanged(object sender, EventArgs e)
        // update the behavior's context to match the view
        BindingContext = ((BindableObject)sender).BindingContext;

    // the canvas needs to be painted
    private void OnPaintSurface(object sender, SKPaintSurfaceEventArgs e)
        // first check if the command can/should be fired
        if (Command?.CanExecute(e) == true)
            // fire the command

Now that we have the behavior, we can simply add it to the canvas view:

        <local:PaintSurfaceCommandBehavior Command="{Binding PaintCommand}" />

That’s it! Our canvas is now command-based and can be drawn on from the view model.

This is just a very limited behavior, for one specific event for one specific view. But, behaviors are very powerful and can be used to do many more things. One such use is to make a more generic behavior that can map any event to any command:

Besides those examples, be sure to read more about behaviors, and what they can do, in the Xamarin documentation.