Introduction
This article describes, how we can set Corner Radius for Control or View or Layout. Sometimes, we may get the requirement to set corner radius for StackLayout or Grid or ListView, in such cases I used to try to put View or Layout inside the Frame to make the corner radius, but, it was difficult to set all corner properties. So in this article, we can learn to achieve this functionality using CustomRenderer.
Requirements
- This article's source code is prepared by using Visual Studio. And it is better to install the latest Visual Studio updates from here.
- This article is prepared on a MAC machine.
- This sample project is Xamarin.Forms PCL project.
- This sample app is targeted for Android, iOS. And tested for Android & iOS.
Description
The creation of Xamarin.Forms project is very simple in Visual Studio for Mac. It will create three projects:
- Shared Code
- Xamarin.Android
- Xamarin.iOS
Because it's a Mac system with Visual Studio for Mac it doesn't support Windows projects (UWP, Windows, Windows Phone).
The following steps will show you how to create Xamarin.Forms project in Mac systems with Visual Studio.
First, open Visual Studio for Mac. And Click on New Project
After that, we need to select whether you're doing Xamarin.Forms or Xamarin.Android or Xamarin.iOS project. If we want to create Xamarin.Forms project just follow the below screenshot.
Then, we have to give the App Name i.e RoundedCornerViewDemo.
Note: In the above screen under Shared Code, select Portable class Library or Use Shared Library.
Then, click on Next Button and the following screenshot will be displayed. In that screen, we have to browse the file path where we want to save that application on our PC.
After clicking on the create button it will create the RoundedCornerViewDemo Xamarin.Forms project like this.
And the project structure will be:
- RoundedCornerViewDemo: It is for Shared Code
- RoundedCornerViewDemo.Droid: It is for Android.
- RoundedCornerViewDemo.iOS: It is for iOS
We need to follow the below few steps to make corner radius for the view.
Portable Class Library (PCL)
Step 1:
In PCL, create a class name as RoundedCornerView which should inherit any layout and this article inherits Grid Layout and adds BindableProperties like BorderColor, RoundedCornerRadius, BorderWidth, MakeCircle, FillColor.
RoundedCornerView.cs
- using System;
- using Xamarin.Forms;
- namespace RoundedCornerViewDemo.ControlsToolkit.Custom {
- public class RoundedCornerView: Grid {
- public static readonly BindableProperty FillColorProperty = BindableProperty.Create<RoundedCornerView, Color> (w => w.FillColor, Color.White);
- public Color FillColor {
- get {
- return (Color) GetValue(FillColorProperty);
- }
- set {
- SetValue(FillColorProperty, value);
- }
- }
- public static readonly BindableProperty RoundedCornerRadiusProperty = BindableProperty.Create<RoundedCornerView, double> (w => w.RoundedCornerRadius, 3);
- public double RoundedCornerRadius {
- get {
- return (double) GetValue(RoundedCornerRadiusProperty);
- }
- set {
- SetValue(RoundedCornerRadiusProperty, value);
- }
- }
- public static readonly BindableProperty MakeCircleProperty = BindableProperty.Create<RoundedCornerView, Boolean> (w => w.MakeCircle, false);
- public Boolean MakeCircle {
- get {
- return (Boolean) GetValue(MakeCircleProperty);
- }
- set {
- SetValue(MakeCircleProperty, value);
- }
- }
- public static readonly BindableProperty BorderColorProperty = BindableProperty.Create<RoundedCornerView, Color> (w => w.BorderColor, Color.Transparent);
- public Color BorderColor {
- get {
- return (Color) GetValue(BorderColorProperty);
- }
- set {
- SetValue(BorderColorProperty, value);
- }
- }
- public static readonly BindableProperty BorderWidthProperty = BindableProperty.Create<RoundedCornerView, int> (w => w.BorderWidth, 1);
- public int BorderWidth {
- get {
- return (int) GetValue(BorderWidthProperty);
- }
- set {
- SetValue(BorderWidthProperty, value);
- }
- }
- }
- }
Step 2:
Create your own Xaml page named RoundedCornerViewPage.xaml, and make sure to refer to "RoundedCornerView" class in Xaml by declaring a namespace for its location and using the namespace prefix on the control element. The following code example shows how the "RoundedCornerView" renderer class can be consumed by a Xaml page:
And here, we are trying to set rounded corner radius for ListView, so place Listview inside our custom renderer control, let's see how it makes corner radius.
RoundedCornerViewPage.xaml
- <?xml version="1.0" encoding="utf-8"?>
- <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
- xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
- xmlns:custom="clr-namespace:RoundedCornerViewDemo.ControlsToolkit.Custom;assembly=RoundedCornerViewDemo"
- x:Class="RoundedCornerViewDemo.RoundedCornerViewPage">
- <StackLayout Spacing="20" Padding="20,40,20,20">
- <Label Text="RoundedCornerView" HorizontalOptions="CenterAndExpand" FontSize="30" TextColor="Blue" />
- <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2" BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">
- <ListView x:Name="EmployeeView">
- <ListView.ItemTemplate>
- <DataTemplate>
- <TextCell Text="{Binding DisplayName}" /> </DataTemplate>
- </ListView.ItemTemplate>
- </ListView>
- </custom:RoundedCornerView>
- </StackLayout>
- </ContentPage>
Note:
The "custom" namespace prefix can be named anything. However, the clr-namespace and assembly values must match the details of the custom renderer class. Once the namespace is declared the prefix is used to reference the custom control/layout.
Step 3:
Add some simple data to bind ObservableCollection to the ListView in code behind. Also, here I'm not following MVVM design pattern.
RoundedCornerViewPage.xaml.cs
- using System.Collections.ObjectModel;
- using Xamarin.Forms;
- namespace RoundedCornerViewDemo {
- public partial class RoundedCornerViewPage: ContentPage {
- ObservableCollection<Employee> employees = new ObservableCollection<Employee>();
- public RoundedCornerViewPage() {
- InitializeComponent();
- employees.Add(new Employee {
- DisplayName = "Rob Finnerty"
- });
- employees.Add(new Employee {
- DisplayName = "Bill Wrestler"
- });
- employees.Add(new Employee {
- DisplayName = "Dr. Geri-Beth Hooper"
- });
- employees.Add(new Employee {
- DisplayName = "Dr. Keith Joyce-Purdy"
- });
- employees.Add(new Employee {
- DisplayName = "Sheri Spruce"
- });
- employees.Add(new Employee {
- DisplayName = "Burt Indybrick"
- });
- EmployeeView.ItemsSource = employees;
- }
- }
- public class Employee {
- public string DisplayName {
- get;
- set;
- }
- }
- }
Examples:
In the above code, we made corner radius for ListView. But we can also set RoundedCornerRadius for any Control/View and Layout.
StackLayout
- <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2" BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">
- <StackLayout orientation="Horizontal">
- <Label Text="Hi Welcome" />
- </StackLayout>
- </custom:RoundedCornerView>
Grid
- <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2" BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">
- <Grid>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto" />
- </Grid.ColumnDefinitions>
- <Label Text="Hi Welcome" />
- </Grid>
- </custom:RoundedCornerView>
ViewCell
- <ListView x:Name="EmployeeView">
- <ListView.ItemTemplate>
- <DataTemplate>
- <ViewCell>
- <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2" BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">
- <TextCell Text="{Binding DisplayName}" />
- </custom:RoundedCornerView>
- </ViewCell>
- </DataTemplate>
- </ListView.ItemTemplate>
- </ListView>
Xamarin.Android
In Android project, create a class name as RoundedCornerViewRenderer and make sure to add renderer registration for our RoundedCornerView class above the namespace,
RoundedCornerViewRenderer.cs
- using System;
- using Android.Graphics;
- using RoundedCornerViewDemo;
- using RoundedCornerViewDemo.ControlsToolkit.Custom;
- using RoundedCornerViewDemo.Droid.Renderers;
- using Xamarin.Forms;
- using Xamarin.Forms.Platform.Android;
- [assembly: ExportRenderer(typeof(RoundedCornerView), typeof(RoundedCornerViewRenderer))]
- namespace RoundedCornerViewDemo.Droid.Renderers {
- public class RoundedCornerViewRenderer: ViewRenderer {
- protected override void OnElementChanged(ElementChangedEventArgs<View> e) {
- base.OnElementChanged(e);
- }
- protected override bool DrawChild(Canvas canvas, global::Android.Views.View child, long drawingTime) {
- if (Element == null) return false;
- RoundedCornerView rcv = (RoundedCornerView) Element;
- this.SetClipChildren(true);
- rcv.Padding = new Thickness(0, 0, 0, 0);
-
- int radius = (int)(rcv.RoundedCornerRadius);
-
-
-
- if (rcv.MakeCircle) {
- radius = Math.Min(Width, Height) / 2;
- }
-
-
- radius *= 2;
- try {
-
- var path = new Path();
- path.AddRoundRect(new RectF(0, 0, Width, Height), new float[] {
- radius,
- radius,
- radius,
- radius,
- radius,
- radius,
- radius,
- radius
- }, Path.Direction.Ccw);
- canvas.Save();
- canvas.ClipPath(path);
-
- var result = base.DrawChild(canvas, child, drawingTime);
- canvas.Restore();
-
-
-
-
- if (rcv.BorderWidth > 0) {
-
- var paint = new Paint();
- paint.AntiAlias = true;
- paint.StrokeWidth = rcv.BorderWidth;
- paint.SetStyle(Paint.Style.Stroke);
- paint.Color = rcv.BorderColor.ToAndroid();
- canvas.DrawPath(path, paint);
- paint.Dispose();
- }
-
- path.Dispose();
- return result;
- } catch (Exception ex) {
- System.Console.Write(ex.Message);
- }
- return base.DrawChild(canvas, child, drawingTime);
- }
- }
- }
Here, OnElementChanged method instantiates an Android UI Layout. And also make sure to override DrawChild which is responsible for getting the canvas in the right state that includes BorderColor, BorderWidth, BorderRadius etc.
Xamarin.iOS
In iOS project, create a class name is RoundedCornerViewRenderer and make sure to add renderer registration for our RoundedCornerView class in above of the namespace:
RoundedCornerViewRenderer.cs
- using System;
- using System.Diagnostics;
- using RoundedCornerViewDemo;
- using RoundedCornerViewDemo.ControlsToolkit.Custom;
- using RoundedCornerViewDemo.iOS;
- using Xamarin.Forms;
- using Xamarin.Forms.Platform.iOS;
- [assembly: ExportRenderer(typeof(RoundedCornerView), typeof(RoundedCornerViewRenderer))]
- namespace RoundedCornerViewDemo.iOS {
- public class RoundedCornerViewRenderer: ViewRenderer {
- protected override void OnElementChanged(ElementChangedEventArgs<View> e) {
- base.OnElementChanged(e);
- if (this.Element == null) return;
- this.Element.PropertyChanged += (sender, e1) => {
- try {
- if (NativeView != null) {
- NativeView.SetNeedsDisplay();
- NativeView.SetNeedsLayout();
- }
- } catch (Exception exp) {
- Debug.WriteLine("Handled Exception in RoundedCornerViewDemoRenderer. Just warngin : " + exp.Message);
- }
- };
- }
- public override void Draw(CoreGraphics.CGRect rect) {
- base.Draw(rect);
- this.LayoutIfNeeded();
- RoundedCornerView rcv = (RoundedCornerView) Element;
-
- rcv.Padding = new Thickness(0, 0, 0, 0);
-
- this.ClipsToBounds = true;
- this.Layer.BackgroundColor = rcv.FillColor.ToCGColor();
- this.Layer.MasksToBounds = true;
- this.Layer.CornerRadius = (nfloat) rcv.RoundedCornerRadius;
- if (rcv.MakeCircle) {
- this.Layer.CornerRadius = (int)(Math.Min(Element.Width, Element.Height) / 2);
- }
- this.Layer.BorderWidth = 0;
- if (rcv.BorderWidth > 0 && rcv.BorderColor.A > 0.0) {
- this.Layer.BorderWidth = rcv.BorderWidth;
- this.Layer.BorderColor = newUIColor(
- (nfloat)rcv.BorderColor.R,
- (nfloat) rcv.BorderColor.G,
- (nfloat) rcv.BorderColor.B,
- (nfloat) rcv.BorderColor.A).CGColor;
- }
- }
- }
- }
Here, OnElementChanged method instantiates an iOS UI, with a reference to the layout being assigned to the renderer's Element property. In DrawChild method reference to the Layout is assigned to the renderer's Element property.
Output
Please download the source code from here.