Saturday 9 December 2017

How to set Corner Radius for View, Layout, Cell (Grid, Stack, ListView) in Xamarin.Forms

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 that cases earlier I tried to put View or Layout inside the Frame to make the corner radius, but it is difficult to set all corner properties. So in this article, we can learn how to achieve this functionality using CustomRenderer.

Requirements:
  • This article source code is prepared by using Visual Studio. And it is better to install 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 creates will three projects 
1) Shared Code
2) Xamarin.Android
3) Xamarin.iOS
Because 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 system with  Visual Studio,
First, open the 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 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 Click on Create, button it will create the RoundedCornerViewDemo Xamarin.Forms project like this
And 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 below few steps to make corner radius for the view.

Portable Class Library (PCL):

Step 1: 
In PCL, create a class name is RoundedCornerView which should inherit any layout and this article inherit Grid Layout and adding BindableProperties like BorderColor, RoundedCornerRadius, BorderWidth, MakeCircle, FillColor.

RoundedCornerView.cs

  1. using System;      
  2. using Xamarin.Forms;      
  3.       
  4. namespace RoundedCornerViewDemo.ControlsToolkit.Custom      
  5. {      
  6.     public class RoundedCornerView : Grid      
  7.     {      
  8.         public static readonly BindableProperty FillColorProperty =      
  9.             BindableProperty.Create<RoundedCornerView, Color>(w => w.FillColor, Color.White);      
  10.         public Color FillColor      
  11.         {      
  12.             get { return (Color)GetValue(FillColorProperty); }      
  13.             set { SetValue(FillColorProperty, value); }      
  14.         }      
  15.       
  16.         public static readonly BindableProperty RoundedCornerRadiusProperty =      
  17.             BindableProperty.Create<RoundedCornerView, double>(w => w.RoundedCornerRadius, 3);      
  18.         public double RoundedCornerRadius      
  19.         {      
  20.             get { return (double)GetValue(RoundedCornerRadiusProperty); }      
  21.             set { SetValue(RoundedCornerRadiusProperty, value); }      
  22.         }      
  23.       
  24.         public static readonly BindableProperty MakeCircleProperty =      
  25.             BindableProperty.Create<RoundedCornerView, Boolean>(w => w.MakeCircle, false);      
  26.         public Boolean MakeCircle      
  27.         {      
  28.             get { return (Boolean)GetValue(MakeCircleProperty); }      
  29.             set { SetValue(MakeCircleProperty, value); }      
  30.         }      
  31.       
  32.         public static readonly BindableProperty BorderColorProperty =      
  33.             BindableProperty.Create<RoundedCornerView, Color>(w => w.BorderColor, Color.Transparent);      
  34.         public Color BorderColor      
  35.         {      
  36.             get { return (Color)GetValue(BorderColorProperty); }      
  37.             set { SetValue(BorderColorProperty, value); }      
  38.         }      
  39.       
  40.         public static readonly BindableProperty BorderWidthProperty =      
  41.             BindableProperty.Create<RoundedCornerView, int>(w => w.BorderWidth, 1);      
  42.         public int BorderWidth      
  43.         {      
  44.             get { return (int)GetValue(BorderWidthProperty); }      
  45.             set { SetValue(BorderWidthProperty, value); }      
  46.         }      
  47.     }      
  48.     

Step 2:
Create your own Xaml page name is RoundedCornerViewPage.xaml, and make sure refer "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 make corner radius.

RoundedCornerViewPage.xaml

  1. <?xml version="1.0" encoding="utf-8"?>    
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"    
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"    
  4.     xmlns:custom="clr-namespace:RoundedCornerViewDemo.ControlsToolkit.Custom;assembly=RoundedCornerViewDemo"    
  5.     x:Class="RoundedCornerViewDemo.RoundedCornerViewPage">    
  6.     <StackLayout Spacing="20" Padding="20,40,20,20">    
  7.         <Label Text="RoundedCornerView" HorizontalOptions="CenterAndExpand" FontSize="30" TextColor="Blue"/>    
  8.         <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2"  BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">    
  9.          <ListView x:Name="EmployeeView">    
  10.             <ListView.ItemTemplate>    
  11.                <DataTemplate>    
  12.                 <TextCell Text="{Binding DisplayName}"/>    
  13.                </DataTemplate>    
  14.                </ListView.ItemTemplate>    
  15.           </ListView>    
  16.          </custom:RoundedCornerView>    
  17.         </StackLayout>    
  18. </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 list data to bind ObservableCollection to the ListView in code behind. Also here I'm not following MVVM design pattern.

RoundedCornerViewPage.xaml.cs
  1. using System.Collections.ObjectModel;    
  2. using Xamarin.Forms;    
  3.     
  4. namespace RoundedCornerViewDemo    
  5. {    
  6.     public partial class RoundedCornerViewPage : ContentPage    
  7.     {    
  8.         ObservableCollection<Employee> employees = new ObservableCollection<Employee>();    
  9.     
  10.         public RoundedCornerViewPage()    
  11.         {    
  12.             InitializeComponent();    
  13.     
  14.             employees.Add(new Employee { DisplayName = "Rob Finnerty" });    
  15.             employees.Add(new Employee { DisplayName = "Bill Wrestler" });    
  16.             employees.Add(new Employee { DisplayName = "Dr. Geri-Beth Hooper" });    
  17.             employees.Add(new Employee { DisplayName = "Dr. Keith Joyce-Purdy" });    
  18.             employees.Add(new Employee { DisplayName = "Sheri Spruce" });    
  19.             employees.Add(new Employee { DisplayName = "Burt Indybrick" });    
  20.     
  21.             EmployeeView.ItemsSource = employees;    
  22.         }    
  23.     }    
  24.     
  25.     public class Employee    
  26.     {    
  27.         public string DisplayName { getset; }    
  28.     }    
  29. }    

Examples:
In above code, we made corner radius for ListView. But we can also set RoundedCornerRadius for any Control/View and Layout.

StackLayout:
  1. <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2"  BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">    
  2.          <StackLayout orientation="Horizontal">   
  3.             <Label Text="Hi Welcome"/>     
  4.           </StackLayout>    
  5. </custom:RoundedCornerView>    
Grid:
  1. <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2"  BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">     
  2.            <Grid>    
  3.                <Grid.RowDefinitions>    
  4.                    <RowDefinition Height="Auto"/>    
  5.                </Grid.RowDefinitions>    
  6.                <Grid.ColumnDefinitions>    
  7.                    <ColumnDefinition Width="Auto"/>    
  8.                </Grid.ColumnDefinitions>    
  9.                <Label Text="Hi Welcome"/>   
  10.            </Grid>    
  11. </custom:RoundedCornerView>   
  
ViewCell:

  1. <ListView x:Name="EmployeeView">     
  2.                 <ListView.ItemTemplate>     
  3.                     <DataTemplate>      
  4.                         <ViewCell>    
  5.                             <custom:RoundedCornerView BorderColor="Gray" BorderWidth="2"  BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RoundedCornerRadius="8">      
  6.                                 <TextCell Text="{Binding DisplayName}"/>    
  7.                             </custom:RoundedCornerView>      
  8.                         </ViewCell>    
  9.                     </DataTemplate>      
  10.                 </ListView.ItemTemplate>      
  11. </ListView>      

Xamarin.Android:

In Android 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
  1. using System;        
  2. using Android.Graphics;        
  3. using RoundedCornerViewDemo;        
  4. using RoundedCornerViewDemo.ControlsToolkit.Custom;        
  5. using RoundedCornerViewDemo.Droid.Renderers;        
  6. using Xamarin.Forms;        
  7. using Xamarin.Forms.Platform.Android;        
  8.         
  9. [assembly: ExportRenderer(typeof(RoundedCornerView), typeof(RoundedCornerViewRenderer))]        
  10. namespace RoundedCornerViewDemo.Droid.Renderers        
  11. {        
  12.     public class RoundedCornerViewRenderer : ViewRenderer        
  13.     {        
  14.         
  15.         protected override void OnElementChanged(ElementChangedEventArgs<View> e)        
  16.         {        
  17.             base.OnElementChanged(e);        
  18.         }        
  19.         
  20.         protected override bool DrawChild(Canvas canvas, global::Android.Views.View child, long drawingTime)        
  21.         {        
  22.             if (Element == nullreturn false;        
  23.         
  24.             RoundedCornerView rcv = (RoundedCornerView)Element;        
  25.             this.SetClipChildren(true);        
  26.         
  27.             rcv.Padding = new Thickness(0, 0, 0, 0);        
  28.             //rcv.HasShadow = false;        
  29.         
  30.             int radius = (int)(rcv.RoundedCornerRadius);        
  31.             // Check if make circle is set to true. If so, then we just use the width and        
  32.             // height of the control to calculate the radius. RoundedCornerRadius will be ignored        
  33.             // in this case.        
  34.             if (rcv.MakeCircle)        
  35.             {        
  36.                 radius = Math.Min(Width, Height) / 2;        
  37.             }        
  38.         
  39.             // When we create a round rect, we will have to double the radius since it is not        
  40.             // the same as creating a circle.        
  41.             radius *= 2;        
  42.         
  43.             try        
  44.             {        
  45.                 //Create path to clip the child         
  46.                 var path = new Path();        
  47.                 path.AddRoundRect(new RectF(0, 0, Width, Height),        
  48.                               new float[] { radius, radius, radius, radius, radius, radius, radius, radius },        
  49.                               Path.Direction.Ccw);        
  50.         
  51.                 canvas.Save();        
  52.                 canvas.ClipPath(path);        
  53.         
  54.                 // Draw the child first so that the border shows up above it.        
  55.                 var result = base.DrawChild(canvas, child, drawingTime);        
  56.         
  57.                 canvas.Restore();        
  58.         
  59.                 /*     
  60.                  * If a border is specified, we use the same path created above to stroke     
  61.                  * with the border color.     
  62.                  * */        
  63.                 if (rcv.BorderWidth > 0)        
  64.                 {        
  65.                     // Draw a filled circle.        
  66.                     var paint = new Paint();        
  67.                     paint.AntiAlias = true;        
  68.                     paint.StrokeWidth = rcv.BorderWidth;        
  69.                     paint.SetStyle(Paint.Style.Stroke);        
  70.                     paint.Color = rcv.BorderColor.ToAndroid();        
  71.         
  72.                     canvas.DrawPath(path, paint);        
  73.         
  74.                     paint.Dispose();        
  75.                 }        
  76.         
  77.                 //Properly dispose        
  78.                 path.Dispose();        
  79.                 return result;        
  80.             }        
  81.             catch (Exception ex)        
  82.             {        
  83.                 System.Console.Write(ex.Message);        
  84.             }        
  85.         
  86.             return base.DrawChild(canvas, child, drawingTime);        
  87.         }        
  88.     }        
  89.  
    

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
  1. using System;    
  2. using System.Diagnostics;    
  3. using RoundedCornerViewDemo;    
  4. using RoundedCornerViewDemo.ControlsToolkit.Custom;    
  5. using RoundedCornerViewDemo.iOS;    
  6. using Xamarin.Forms;    
  7. using Xamarin.Forms.Platform.iOS;    
  8.     
  9. [assembly: ExportRenderer(typeof(RoundedCornerView), typeof(RoundedCornerViewRenderer))]    
  10. namespace RoundedCornerViewDemo.iOS    
  11. {    
  12.     public class RoundedCornerViewRenderer : ViewRenderer    
  13.     {    
  14.         protected override void OnElementChanged(ElementChangedEventArgs<View> e)    
  15.         {    
  16.             base.OnElementChanged(e);    
  17.     
  18.             if (this.Element == nullreturn;    
  19.     
  20.             this.Element.PropertyChanged += (sender, e1) =>    
  21.             {    
  22.                 try    
  23.                 {    
  24.                     if (NativeView != null)    
  25.                     {    
  26.                         NativeView.SetNeedsDisplay();    
  27.                         NativeView.SetNeedsLayout();    
  28.                     }    
  29.                 }    
  30.                 catch (Exception exp)    
  31.                 {    
  32.                     Debug.WriteLine("Handled Exception in RoundedCornerViewDemoRenderer. Just warngin : " + exp.Message);    
  33.                 }    
  34.             };    
  35.         }    
  36.     
  37.         public override void Draw(CoreGraphics.CGRect rect)    
  38.         {    
  39.             base.Draw(rect);    
  40.     
  41.             this.LayoutIfNeeded();    
  42.     
  43.             RoundedCornerView rcv = (RoundedCornerView)Element;    
  44.             //rcv.HasShadow = false;    
  45.             rcv.Padding = new Thickness(0, 0, 0, 0);    
  46.     
  47.             //this.BackgroundColor = rcv.FillColor.ToUIColor();    
  48.             this.ClipsToBounds = true;    
  49.             this.Layer.BackgroundColor = rcv.FillColor.ToCGColor();    
  50.             this.Layer.MasksToBounds = true;    
  51.             this.Layer.CornerRadius = (nfloat)rcv.RoundedCornerRadius;    
  52.             if (rcv.MakeCircle)    
  53.             {    
  54.                 this.Layer.CornerRadius = (int)(Math.Min(Element.Width, Element.Height) / 2);    
  55.             }    
  56.             this.Layer.BorderWidth = 0;    
  57.     
  58.             if (rcv.BorderWidth > 0 && rcv.BorderColor.A > 0.0)    
  59.             {    
  60.                 this.Layer.BorderWidth = rcv.BorderWidth;    
  61.                 this.Layer.BorderColor =    
  62.                     new UIKit.UIColor(    
  63.                     (nfloat)rcv.BorderColor.R,    
  64.                     (nfloat)rcv.BorderColor.G,    
  65.                     (nfloat)rcv.BorderColor.B,    
  66.                         (nfloat)rcv.BorderColor.A).CGColor;    
  67.             }    
  68.         }    
  69.     }    
  70. }    

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 being assigned to the renderer's Element property. 

Output:
                  
Please download the source code from below.

5 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Do you have code for UWP as well?

    ReplyDelete
  3. Muchas gracias, funciona muy bien. Tengo StackLayouts simuladores de botones(les pongo imagen a mi gusto y demas caracteristicas que no me permite el boton basico de Xamarin xd), y necesitaba pornerles radio, con esto me funciono perfectamente. Saludos.

    ReplyDelete
  4. Nice blog and absolutely outstanding. You can do something much better but i still say this perfect.Keep trying for the best. Hire xamarin developer india, xamarin development company

    ReplyDelete