52 Weeks of Xamarin: Week 12 – Advanced Customized Controls

2015-10-28 14:01:58来源:作者:Jesse Liberty人点击

Last week we looked at a simple example of customizing a control and creating a custom renderer. This week we go a bit deeper. Our goal is to give a BoxView a border, and to adjust that border dynamically.

[ This content is taken from my forthcoming Pluralsight course ]

As noted last week, we start with an Element and a Renderer. In our case, the element will be a SpecialBoxView. Unlike last week, however, this custom class will have contents; specifically the properties we want to be able to bind to.

Bindable Properties

public class SpecialBoxView : BoxView { public static readonly BindableProperty BorderColorProperty = BindableProperty . Create < SpecialBoxView , Color >( p = > p . BorderColor , default ( Color )) ; public Color BorderColor { get { return ( Color ) GetValue ( BorderColorProperty ) ; } set { SetValue ( BorderColorProperty , value ) ; } } public static readonly BindableProperty BorderThicknessProperty = BindableProperty . Create < SpecialBoxView , double >( p = > p . BorderThickness , default ( double )) ; public double BorderThickness { get { return ( double ) GetValue ( BorderThicknessProperty ) ; } set { SetValue ( BorderThicknessProperty , value ) ; } } }

Notice that these come in pairs: a bindable property and the public property that we’ll be using to get and set that value. Thus, we can bind to BorderColor and/or BorderThickness.

Custom Renderer

With that done, we are ready to implement the custom renderer. To do so, this week we’ll turn to iOS; next week we’ll follow up by creating the Android renderer.

The first step is to derive from BoxRenderer,

public class SpecialBoxViewRenderer : BoxRenderer {

Within that class, we’ll override two methods: Draw and OnElementPropertyChanged. In our override of Draw, we will first get the element itself,

public override void Draw(CoreGraphics.CGRect rect) { SpecialBoxView specialBoxView = (SpecialBoxView)Element;

We will thenget a context to work with to set our fill and stroke color as well as the width of the stroke.

using (var context = UIGraphics.GetCurrentContext()) { context.SetFillColor(specialBoxView.Color.ToCGColor()); context.SetStrokeColor(specialBoxView.BorderColor.ToCGColor()); context.SetLineWidth((float)specialBoxView.BorderThickness);

We can then get the inset to draw the border.

var rectangle = this . Bounds . Inset (( int ) specialBoxView . BorderThickness , ( int ) specialBoxView . BorderThickness ) ;

var path = CGPath . FromRect ( rectangle ) ;

context . AddPath ( path ) ;

context . DrawPath ( CGPathDrawingMode . FillStroke ) ;

All that is left is to override OnElementPropertyChanged. We’ll make sure that it is the BorderThicknessProperty that has changed, and if so we’ll call SetNeedsDisplay which will cause Draw to be called and the border to be redrawn,

protected override void OnElementPropertyChanged ( object sender , System . ComponentModel . PropertyChangedEventArgs e ) { if ( e . PropertyName == SpecialBoxView . BorderThicknessProperty . PropertyName ) { SetNeedsDisplay () ; } }

Don’t forget!

If we use this class as is it will build and it will run, but there will be no border, because we haven’t established a link between the renderer and the custom control. As we did last week, we need to add an attribute outside of any namespace .

[assembly: ExportRendererAttribute(typeof(SpecialBoxView), typeof(SpecialBoxViewRenderer))]

You will need to add using statements to resolve the SpecialBoxView and the SpecialBoxViewRenderer.


In the XAML we’ll need a name space for the SpecialBoxView,


We can then instantiate a SpecialBoxView. We’ll want to adjust the width of the border with a slider, so we’ll set the slider to be the BindingContext and the BorderThickness property will bind to the slider’s Value.

<ContentPage.Content> <StackLayout Padding="20"> <Label Text="Customized Control" TextColor="Red" FontSize="24"/> <local:SpecialBoxView WidthRequest="200" HeightRequest="200" Color="Blue" BorderColor="Red" BindingContext="{x:Reference Name=ThicknessSlider}" BorderThickness="{Binding Path=Value}" /> <Label BindingContext="{x:Reference Name=ThicknessSlider}" Text="{Binding Path=Value}" /> <Slider x:Name="ThicknessSlider" Minimum="0" Maximum="100" /> </StackLayout> </ContentPage.Content>

The result is shown in the image at the top of this posting.

Also in 52 Weeks of Xamarin 52 Weeks of Xamarin: Week 1 52 Weeks of Xamarin: Week 2 – Starting the project 52 Weeks of Xamarin: Week 3- Events in Xamarin.Forms 52 Weeks of Xamarin: Week 4 – Creating the Model and Saving 52 Weeks of Xamarin: Week 5 – Diving in the deep end 52 Weeks of Xamarin: Week 6 – Starting xUnit Testing 52 Weeks of Xamarin: Week 7 – First Unit Tests 52 Weeks of Xamarin: Week 8 – Testing the View Model 52 Weeks of Xamarin: Week 9 – Databases part 1 52 Weeks of Xamarin: Week 10 – The UI for our database program 52 Weeks of Xamarin: Week 11 – Customized Controls for Xamarin.Forms 52 Weeks of Xamarin: Week 12 – Advanced Customized Controls

View the entire series.