Sometimes when developing Xamarin.Forms user interfaces you get to a point where you just need to handle the drawing yourself, and for that SkiaSharp is invaluable. And that’s one of the core pieces of this UI Challenge, using SkiaSharp to create controls that are unlike any other.
First, I should mention that this amazing design concept that we are reproducing was created by Ionut Zamfir over at Dribbble. The key elements that drew my attention to this layout were:
Beautiful use of colours
Light and Dark themes
Complex multi-colour gradients
Unique custom slider for the temperature
Seriously, just check it out in all it’s beauty:
Xamarin UI July
This post is part of Xamarin UI July, organised by Steve Thewissen. Today is day 13 with another exciting post coming out every day for the rest of the month.
Let’s break it down
So let’s try and reproduce this layout in Xamarin.Forms. When you look at it, the overall page layout itself is fairly straight forward consisting of a main Grid with a StackLayout to lay the elements out down the page.
Pro Tip: Grids are a great root layout element because they allow you to position elements proportionally on a page whilst still allowing overlapping elements
If you focus just on the background of the design it has two elements:
A multi-colour gradient across the entire page
The mountain image, at the bottom of the page
For the implementation the main page consists of a SKCanvasView for the background gradient. There is also an Image with a VerticalOptions of End which positions it at the bottom of the screen but above the gradient.
The order of elements in your Xaml is important because it implies the z-order. Which is to say, elements that appear later in your Xaml will appear in-front of earlier elements
The background gradient is a little more complex than it may first appear because it isn’t just a linear gradient between two colours. It’s actually a radial gradient between three colours. This complexity means we can’t use something like PancakeView for the gradient, instead we can render this with SkiaSharp. Complex gradients are surprisingly easy in SkiaSharp, but does require you implement them in code.
Code to render gradient
The following code is called whenever the BackgroundGradient canvas needs to be drawn. First thing it does is load up the colours from the application resources (because different themes have different gradient colours - but more on that later). Then it uses those colours to create a RadialGradient shader which is drawn over the entire canvas area.
The documentation and samples for SkiaSharp are excellent, don’t be scared to check them out. For example, here are the docs for Creating Radial Gradients
Okay, this is the fun bit, that temperature slider. Notice how it has a slider thumb and above it, it dynamically eats into (or clips) the slider background. There isn’t a control like this in Xamarin.Forms (or anywhere for that matter), so it’s time to write a control in SkiaSharp, named appropriately GaugeControl.
The background of the slider is really just a RoundedRect with a gradient fill. No big deal. However, the trick is to have a bit “clipped” out where the slider thumb is. Well, it turns out that with SkiaSharp you can provide a path to clip out of the drawing, which is what we do above the slider thumb.
One thing to note in the code above is that when you are dealing with SkiaSharp you are dealing with pixels, so if you are used to working with Xamarin.Forms units you need to actually work out the scaling of your device.
The key line in there is the ClipPath method call which uses the path and a SKClipOperation.Difference which basically means, only render whatever you output where it falls outside of the clip path. One thing to consider is that every drawing operation you perform after you call ClipPath will have the clipping applied. But one little trick you can do is restore you canvas to a point before the clipping by using SKAutoCanvasRestore.
Handling User Input
It’s one thing to draw the control, it’s quite another to make it interact with the user. In our case, we want to detect when the user drags on the control so that we can update the position of the thumb slider and the clipping path. To do this we use a Xamarin.Forms effect which reports touch operations.
So when we get a TouchAction we update the percent of the slider, which in turn calls InvalidateSurface causing a redraw of the SkiaSharp canvas.
The TouchEffect I lifted from the awesome SkiaSharp samples. For more about the TouchEffect check out Finger Painting in SkiaSharp in the Docs.
Another interesting bit about this design is the dark and light themes. For this UI Challenge we make it so when you click on the profile image it toggles between dark and light. In a real application you would probably tie this to user preferences, or even cooler, to the ambient light sensor of the device so it automatically switches based on lighting conditions. For us thought, it’s just tied to the profile image, like so:
The result is quite nice though, as shown in this video:
Multiple Theme Files
I have multiple themes (technically ResourceDictionary) that have different values for resources. For example:
For this challenge we just have a dark and light theme, but of course you could have as many themes as you like. Or even allow the user to create their own.
To go along with the themes we have a helper to switch between them. This essentially just loads resources from a resource dictionary into the application resources and then fires a message through the MessagingCenter so that other parts of the application can know when themes are switched.
Using Dynamic Resources
In order for Views to respond to changes in the Application Resources the key is to use DynamicResource instead of StaticResource.
As an example, if you look at the styles setup in the App.xaml, you’ll see that I have used DynamicResource for the TextColor so that it’ll update when the ThemeManager changes the Color values, no additional code required.
And to make the mountain image at the bottom change it is dynamically linked to an image name from the theme.
Sending Theme Message
One aspect that will not automatically update when the theme changes is the SkiaSharp elements, so we need to trigger them to update somehow. As mentioned previously, we use the MessagingCenter (although any notification mechanism would work).
In the MainPage we register for notifications in the OnAppearing. When it fires (from the ThemeHelper), we call UpdateTheme, which effectively just calls InvalidateSurface which asks SkiaSharp to redraw the background.
Always remember to Unsubscribe from the MessagingCenter
Finally, in the Background SkiaSharp paint code we read the new colour values from the application dictionary that are used to draw the Gradient.
The same sort of logic also applies to the background gradients in the GaugeView.
The two sections right down the bottom of the page are simply horizontal scrolling StackLayout with Grids in them and using the PancakeView to give them rounded corners.
In a real solution, you might want to try using the new CollectionView, but for the sake of our exercise, the ScrollView will do.
The Final Result
I think we could call this Xamarin.Forms UI Challenge a success. It’s great to see how well SkiaSharp integrates with Xamarin.Forms to allow us to create crazy-cool custom controls. Also multiple themes/styles is not too hard to implement as well.
I actually did this UI Challenge live over Twitch, so if you want to watch hours of me coding this up then check out these recordings over at YouTube.
If you want to catch me doing other live coding things follow me on Twitch. It’s a great platform where we can chat as we build software, ask questions, submit code). Follow me at https://www.twitch.tv/kymphillpotts and come join in the fun!
I hope you find these posts useful, feel free to leave me a comment below or reach out to me via Twitter with some feedback.