We occasionally want to include a specific UI design in our mobile apps that should look exactly the same on both Android and iOS, but we don’t want to construct custom controls to do so. This is when the NControl package comes into play and can help us construct custom controls without having to write platform-specific renderers!
NControl is a wrapper for NGraphics, a cross-platform library you can use to create graphically rich interactive views and UI widgets on .NET. In this example we will create an animated circular button, but you may use it to work with complex vectors, brushes, pens, and shapes, among other things. SVG and PNG files can also be imported and exported!
Now let’s get at it!
Let’s begin with the required libraries. NGraphics needs to be installed in all the projects, but NControl only needs to be in the Xamarin.Forms one.
Now, let’s create a CircularButtonControl class which will inherit from NControlView. In our class, we will define a label and a background to set the color.
public class CircularButtonControl : NControlView { private readonly NControlView _background; private readonly Label _label; public CircularButtonControl() { _label = new Label { Text = "Learn more", TextColor = Xamarin.Forms.Color.White, FontSize = 20, HorizontalTextAlignment = Xamarin.Forms.TextAlignment.Center, VerticalTextAlignment = Xamarin.Forms.TextAlignment.Center }; _background = new NControlView { DrawingFunction = (canvas, rect) => { } }; var content = new Grid { Children = { _background, _label } }; Content = content; } }
Let’s test our control by calling it in our XAML!
<controls:CircularButtonControl HeightRequest="150" WidthRequest="150" BackgroundColor="{StaticResource Primary}" HorizontalOptions="Center"/>
A control needs properties, so we will include a few bindable properties to set the Text, the Text Color, the Font Size, and a Command from the XAML.
#region Bindable Properties public static BindableProperty CommandProperty = BindableProperty.Create(nameof(Command), typeof(ICommand), typeof(CircularButtonControl), defaultBindingMode: BindingMode.TwoWay, propertyChanged: (b, o, n) => ((CircularButtonControl)b).Command = (ICommand)n); public static BindableProperty FontSizeProperty = BindableProperty.Create(nameof(FontSize), typeof(short), typeof(CircularButtonControl), propertyChanged: (b, o, n) => ((CircularButtonControl)b).FontSize = (short)n); public static BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Xamarin.Forms.Color), typeof(CircularButtonControl), propertyChanged: (b, o, n) => ((CircularButtonControl)b).TextColor = (Xamarin.Forms.Color)n); public static BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(CircularButtonControl), propertyChanged: (b, o, n) => ((CircularButtonControl)b).Text = (string)n); #endregion Bindable Properties #region Properties public ICommand Command { get => GetValue(CommandProperty) as ICommand; set { SetValue(CommandProperty, value); } } public short FontSize { get => (short)GetValue(FontSizeProperty); set { SetValue(FontSizeProperty, value); _label.FontSize = value; Invalidate(); } } public string Text { get => GetValue(TextProperty) as string; set { SetValue(TextProperty, value); _label.Text = value; Invalidate(); } } public Xamarin.Forms.Color TextColor { get => (Xamarin.Forms.Color)GetValue(TextColorProperty); set { SetValue(TextColorProperty, value); _label.TextColor = value; Invalidate(); } } #endregion Properties
You can remove the default values we placed in the constructor. It should look like this:
public CircularButtonControl() { _label = new Label { HorizontalTextAlignment = Xamarin.Forms.TextAlignment.Center, VerticalTextAlignment = Xamarin.Forms.TextAlignment.Center }; _background = new NControlView { DrawingFunction = (canvas, rect) => { } }; var content = new Grid { Children = { _background, _label } }; Content = content; }
Now, let’s test it out by setting those properties in the XAML. Don’t forget to add a Command in the ViewModel!
public class AboutViewModel : BaseViewModel { public AboutViewModel() { Title = "About"; OpenWebCommand = new Command(async () => await Browser.OpenAsync("https://trailheadtechnology.com/")); } public ICommand OpenWebCommand { get; } }
<controls:CircularButtonControl Text="Learn more" TextColor="White" FontSize="20" HeightRequest="150" WidthRequest="150" BackgroundColor="{StaticResource Primary}" HorizontalOptions="Center" Command="{Binding OpenWebCommand}"/>
Great! It looks like you can set all of our properties from the XAML. Our control doesn’t know what to do with the Command yet, so let’s take care of that now. Let’s animate our control to make it react on touch and make it execute the command at the end of that animation.
#region Methods public override bool TouchesBegan(IEnumerable<NGraphics.Point> points) { base.TouchesBegan(points); this.ScaleTo(0.98, 40, Easing.CubicInOut); return true; } public override bool TouchesCancelled(IEnumerable<NGraphics.Point> points) { base.TouchesCancelled(points); this.ScaleTo(1.0, 40, Easing.CubicInOut); return true; } public override bool TouchesEnded(IEnumerable<NGraphics.Point> points) { base.TouchesEnded(points); this.ScaleTo(1.0, 40, Easing.CubicInOut); CallCommandIfAvailable(); return true; } void CallCommandIfAvailable() { if (Command != null && Command.CanExecute(null)) Command.Execute(null); } #endregion Methods
Now, we will modify the control to have a round shape. Add a new bindable property to set the background color from the XAML. Note that this property must override the one from the NControlView by using the “new” keyword.
public static BindableProperty BackgroundColorProperty = BindableProperty.Create(nameof(BackgroundColor), typeof(Xamarin.Forms.Color), typeof(CircularButtonControl), propertyChanged: (b, o, n) => ((CircularButtonControl)b).BackgroundColor = (Xamarin.Forms.Color)n); public new Xamarin.Forms.Color BackgroundColor { get => (Xamarin.Forms.Color)GetValue(BackgroundColorProperty); set { SetValue(BackgroundColorProperty, value); Invalidate(); } }
Lastly, define the round shape in the DrawingFunction within our constructor.
public CircularButtonControl() { _label = new Label { HorizontalTextAlignment = Xamarin.Forms.TextAlignment.Center, VerticalTextAlignment = Xamarin.Forms.TextAlignment.Center }; _background = new NControlView { DrawingFunction = (canvas, rect) => { canvas.FillEllipse(rect, new NGraphics.Color(BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, BackgroundColor.A)); } }; var content = new Grid { Children = { _background, _label } }; Content = content; }
There you go! Of course, this is a simple example with the most basic components, but it is a good starting point to enrich your apps with fantastic custom controls using NControl. Happy coding!