Pages

Saturday, October 19, 2019

Create a Custom Control with Xamarin.Forms

  The process of building UI elements requires, at some point, some customizations to give the unique feel and look to the application and to extend the functionality of existing controls. Whether the customization is just overriding the default TextColor of the Entry control, or creating a brand new control with new look and behavior, building custom controls can provide the simple solution to achieve that, without the need of custom renderers.

Create a Custom ToggleBar Control

The ToggleBar control can be used to show some options that the user can choose from, for example a filtering mechanism (similar to a group of radio buttons), or a light-weight tabbed control..etc, see the screenshot below (should look the same on iOS):

The behavior of the control is as follows:
  1. Every button in the control has a selected state and an unselected state determined by the IsSelected bindable property.
  2. The states are visually distinguished through the SelectedColor and the UnselectedColor bindable properties.
  3. The selected items can be obtained through the bindable property SelectedItems.
  4. The control supports multi-selection (that’s why it’s SelectedItems not SelectedItem) by setting the IsMultiSelect bindable property to true (defaults to false).
Bindable properties are the foundation of custom controls (For more information about Xamarin.Forms bindable properties, see Xamarin.Forms Bindable Properties).
Every button inside the ToggleBar control is a custom control by itself. This article will guide you through creating the ToggleButton control and the same concepts can be leveraged in the ToggleBar control (see the complete sample).
The process for creating custom controls is as follows:
  1. Create a subclass of the view you want to extend or modify.
  2. Alter the functionality of the subclass by overriding the default values of the base class’s bindable properties and/or create new bindable properties that will interact with user actions.
  3. Initialize the custom control.
  4. Process inputs in run-time through the propertyChanged delegate of the newly added bindable properties.

Create a Subclass of the View you Want to Extend

Most of custom controls are hosted in a ContentView as it is the simplest container and doesn't expose special properties (like Orientation of the StackLayout that can interfer with the the control behavior). Create a subclass from ContentView in the .NET Standard library project, name it ToggleButton, it holds the StackLayout that has two children: Label and BoxView, the following diagram illustrates the control outline:

When the StackLayout is tapped, the selection state is mutated. The visual state is defined by the TextColor property of the Label and the Color property of the BoxView.

Alter the Functionality of the Subclass:

Create the bindable properties: IsSelectedSelectedColorUnselectedColorTextFontFamily and FontSize. This is the SelectedColor property along with the BindableProperty backing field:

public static readonly BindableProperty SelectedColorProperty = BindableProperty.Create(nameof(SelectedColor), typeof(Color), typeof(ToggleButton),
defaultValue: Color.Default);

 public Color SelectedColor
 {
     get { return (Color)GetValue(SelectedColorProperty); }
     set { SetValue(SelectedColorProperty, value); }
 }


Gemoji image for :bulb A bindable property is a special type of property, where the property's value is tracked by the Xamarin.Forms property system. The purpose of bindable properties is to provide a property system that supports data binding, styles, templates, and values set through parent-child relationships. The process of creating a bindable property is as follows:
  1. Create a BindableProperty instance with one of the BindableProperty.Create method overloads.
  2. Define property accessors for the BindableProperty instance.
For more information about Xamarin.Forms bindable properties, see Xamarin.Forms Bindable Properties
Gemoji image for :bulb Custom bindable properties in custom controls can be categorized into two types:
  1. Bindable properties that are passed down to the built-in bindable properties of child elements, like Text bindable property of the ToggleButton custom control, that is passed down to the Text bindable property of the Label control.
  2. Bindable properties that are specific to the custom control itself and not owned exclusively by any of the child elements, like the IsSelected bindable property. The more behavioral customization required to the custom control, the more of these bindable properties are needed.

Initializing the custom control

In the constructor of the ToggleButton class, initialize the inner controls and create the bindings between the new properties of the custom control and the properties of the inner controls (i.e., the StackLayout, the Label and the BoxView):
public ToggleButton()
{
    verticalStack = new StackLayout
    {
        Spacing = 0,
        HorizontalOptions = LayoutOptions.FillAndExpand
    };
    label = new Label
    {
        HorizontalTextAlignment = TextAlignment.Center,
        VerticalTextAlignment = TextAlignment.Center,
        VerticalOptions = LayoutOptions.CenterAndExpand,
        Margin = new Thickness(5)
    };
    boxView = new BoxView
    {
        VerticalOptions = LayoutOptions.End,
        HeightRequest = HeightRequest > 0 ? HeightRequest / 10d : 2
    };

    label.SetBinding(Label.TextColorProperty, new Binding(nameof(UnselectedColor), source: this));
    label.SetBinding(Label.TextProperty, new Binding(nameof(Text), source: this));
    label.SetBinding(Label.BackgroundColorProperty, new Binding(nameof(BackgroundColor), source: verticalStack));
    label.SetBinding(Label.WidthRequestProperty, new Binding(nameof(WidthRequest), source: verticalStack));
    label.SetBinding(Label.HeightRequestProperty, new Binding(nameof(HeightRequest), source: verticalStack));
    label.SetBinding(Label.FontFamilyProperty, new Binding(nameof(FontFamily), source: this));
    label.SetBinding(Label.FontSizeProperty, new Binding(nameof(FontSize), source: this));
    boxView.SetBinding(BoxView.BackgroundColorProperty, new Binding(nameof(BackgroundColor), source: verticalStack));
    verticalStack.GestureRecognizers.Add(new TapGestureRecognizer()
    {
        Command = new Command(() =>
        {
            IsSelected = !IsSelected;
            SelectionChanged?.Invoke(this, IsSelected);
        })
    });

    verticalStack.Children.Add(label);
    verticalStack.Children.Add(boxView);
    Content = verticalStack;
}

The constructor initializes the control properties, for example the TextColor property of the Label is bound to the UnselectedColor property of the custom control beacause the control is rendered in unselected state if IsSelected is not set, similarly, the BoxView's Color property is bound to the value of the BackgroundColor of the StackLayout to hide it, it only gets highlited with SelectedColor value when the control is selected. Setting the WidthRequest and HeightRequest for the Label and HeightRequest for the BoxView ensures they scale with the StackLayout size.
TapGestureRecognizer is added to the StackLayout’s GestureRecognizers collection to mutate the selection state of the ToggleButton when the StackLayout is tapped. The TapGestureRecognizer provides two approaches for handling the tap action: by the Tapped event, or by the Command property. For more information about the tap gesture recognizer, see Adding a tap gesture recognizer. When the value of IsSelected propery changes, the propertyChanged delegate handles the visual state of the control (see next section).
Create SelectionChanged event that gets invoked when the StackLayout is tapped, to notify consumers of the ToggleButton (e.g. the ToggleBar control) when selection changes:
public event EventHandler<bool> SelectionChanged;

Process Inputs in run-time Through the propertyChanged Delegate

Add the propertyChanged parameter to the BindableProperty.Create method used to create the IsSelectedProperty, and handle it to mutate the visual state of the control based on the IsSelected value:
public static readonly BindableProperty IsSelectedProperty =
    BindableProperty.Create(nameof(IsSelected), typeof(bool), typeof(ToggleButton), false,
        propertyChanged: (bindable, oldValue, newValue) =>
        {
            ((ToggleButton)bindable).MutateSelect();
        }
    );
        
void MutateSelect()
{
    if (IsSelected)
    {
        button.TextColor = SelectedColor;
        underLine.Color = SelectedColor;
    }
    else
    {
        button.TextColor = UnselectedColor;
        underLine.Color = BackgroundColor;
    }
}

Consuming the Custom Control

The ToggleButton control can be referenced in XAML in the .NET Standard library project by declaring a namespace for its location and using the namespace prefix on the control element. The following code example shows how the ToggleButton control can be consumed by a XAML page:
<ContentPage ...
    xmlns:controls="clr-namespace:CustomControlsSample.CustomControls"
    ...>
    ...
    <controls:ToggleButton x:Name="toggleButton" Text="On" BackgroundColor="Black" UnselectedColor="Gray" SelectedColor="White" SelectionChanged="ToggleButton_SelectionChanged"/>
    ...
</ContentPage>
The following code example shows how the ToggleButton control can be consumed by a C# page:
public class MainPage : ContentPage
{
  public MainPage ()
  {
    var toggleButton = new ToggleButton
    {
       Text = "On",
       BackgroundColor = Color.Black,
       UnselectedColor = Color.Gray,
       SelectedColor = Color.White,
    };
   toggleButton.SelectionChanged += ToggleButton_SelectionChanged;
   Content = toggleButton;
  }
}
Attach a handler to the SelectionChanged event to handle the selection change in the code-behind file:
private async void ToggleButton_SelectionChanged(object sender, EventArgs e)
{
    string message = toggleButton.IsSelected ? "ToggleButton is selected" : "ToggleButton is unselected";
    await DisplayAlert("ToggleButton", message, "OK");
}

Summary

This article has demonstrated how to create a custom control in Xamarin.Forms, enabling developers to create new controls with new look and behavior, in the .NET Standard library project, without creating custom renderers. With the rich set of the layouts and the Animation API in Xamarin.Forms, you can even create more sophisticated and appealing controls.

Related Links

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.

How to do code reviews correctly

Introduction Code review is a special event in the life cycle of software development, it's where ownership is transferred from the deve...