The basics to create custom Xamarin.Forms controls using SkiaSharp

While Xamarin.Forms provides a wide range of native controls, many apps require special controls that are not part of the native kit. SkiaSharp is a cross platform library to directly draw on the UI canvas and makes it possible to create complete custom controls. This post will explain basic techniques you will propably need when creating your first control.

Content

  • What is SkiaSharp
  • SkiaSharp basics
  • User input
  • Bindable properties, commands and events

What is SkiaSharp?

First things first, what even is this SkiaSharp thing? Simply said, SkiaSharp is a 2D graphics library that provides a rich API to basically draw fancy things on the UI canvas. The name comes from the Skia Graphics Library which is a project by Google and serves as the graphic engine for products like Chrome, Android, Firefox or Flutter. Similiar to Xamarin.Forms, Skia has different backends where a common API is exposed that we can use. The most important backends for us are the ones powered by the CPU and GPU, namely OpenGL and recently Vulcan. SkiaSharp is a library which provides Xamarin bindings to the Skia API which is mostly written in C++. In order to provide the bindings, Mono maintains a fork of Skia which contains some Xamarin specific logic (you can find the fork here). The actual bindings can be found in the SkiaSharp repository. Additionaly, SkiaSharp provides views which abstract some of the Skia logic and takes care of all required lifecycle operations. SkiaSharp is available for all Xamarin platforms and beyond, the repository currently lists the following platforms:

  • .NET Core
  • Tizen
  • Xamarin.Android
  • Xamarin.iOS
  • Xamarin.tvOS
  • Xamarin.watchOS
  • Xamarin.Mac
  • Windows Classic Desktop (Windows.Forms / WPF)
  • Windows UWP (Desktop / Mobile / Xbox / HoloLens)

SkiaSharp basics

To use SkiaSharp in Xamarin.Forms, you need to install the SkiaSharp.Views.Forms NuGet package in the core project. If you dont use Xamarin.Forms, you just have to install the SkiaSharp.Views package in your platform project.

Drawing

To draw something you can directly add a point, line, rectangle, circle, rounded rectangle, text or image to the canvas. The position of each shape is defined by SKPoints or SKRects, more about the coordinate system in SkiaSharp later.

Sample chapes: Rounded rect, circle and square.
Drawing the Xamarin logo from SVG path data.

Paint

All operations that add a drawing to the canvas require a paint for the form that gets drawn.

  • StrokeWidth: The size of the stroke.
  • Style: You can either only draw the path (Stroke), fill the form created by the path (Fill) or fill the form and draw the path (StrokeAndFill) which will add the StrokeWidth to the form.
An arc with antialiasing enabled (red) compared to one without antialiasing (green).
Drawing the Xamarin logo using BlendModes to layer the shapes.
  1. Remove the inner X using SKBlendMode.Src (as an alternative to ClipPath)
  2. Draw the background beneath the logo using SKBlendMode.SrcOut (to be more precise, we fill everything else with the background)

The coordinate system

The coordinates of where something is drawn is mostly represented by SKPoints or SKRects. In Skia, the upper left position in the view has the coordinates x=0; y=0, the lower right position has the coordinates x=Width;y=Height. Another thing to note is that the zero angle when drawing an arc is positioned right to the center. I would advice to draw help points when creating a control to visualize where a position is.

Sample points to demonstrate the coordinate system in SkiaSharp: One circle in each corner, a centered square and an arc from 0° to 45°.

Manipulating the canvas

Instead of transforming every shape it can get very handy to tranform the canvas itself. One example was the ClipPath method which masks the drawable area. Other very usefull methods are Transform (move in x,y direction), Scale or Rotate. When applying a transformation, it will get applied to all drawings after that. To restore the original canvas transformation you can use Save and Restore or put the code in a using block of an SKAutoCanvasRestore instance.

An arc from 0° to 45° after rotating the canvas by -90° (orange) compared to the same arc without a canvas rotation (red).

User input

Controls usually recieve some user input, like a button that gets pressed. To make our control interactive, we can use the Xamarin.Forms gestures, native gestures or the SkiaSharp touch event. When working with the Xamarin.Forms touch input you have to account for the different sizes Xamarin.Forms and SkiaSharp use.

Bindable properties, commands and events

Now this is more of a Xamarin.Forms basic in general, but since it is important when creating a control I would like to shortly explain how to create bindable properties, commands and how to use events to propagate changes in the control. They can get used to bind when using MVVM or when you want to create a style because style setters require bindable properties. Adding an event is useful not only because not everyone works with bindings but also because you can use it in the code behind to adapt the UI in addition to execute some logic when the command is triggered.

  • returnType: The return type of the property.
  • declaringType: Type of the class where the property is declared.
  • defaultValue: The default value of the property. Setting this property is important if the value cant be null because you will otherwise get an exception!
  • validateValue: A validation expression for the value that gets set. If the expression fails, an exception will be thrown. In the sample, we need a color so we check that the color is not set to null.
  • propertyChanged: A delegate that is called whenever the property changes. We can use this action to invalidate the surface to represent the new value.

Further reading

If you want to dive deeper into SkiaSharp, I would encourage you to check out these links:

Software developer from germany.