Xamarin.Tip – Build Your Own CheckBox in Xamarin.Forms

So how many times have you looked this up? Maybe tried an open source control that didn’t quite work the way you wanted or was super restricted? Just used two different images and switch their IsVisible flag depending if it was tapped or not?
Screw that.

Here’s how you can build your own nice looking checkboxes for iOS and Android with Xamarin.Forms.

Getting Everything Together

So Android has a built in Checkbox view – so the decision on how to handle that is easy. However, iOS doesn’t like checkboxes… note sure why, but they don’t and don’t have a good built in UIControl for it. You could build your own, but I would suggest using BEMCheckbox: https://github.com/Boris-Em/BEMCheckBox

This thing looks good, works well, is super flexible, and well supported. Even better, our super awesome community has wrapped it in a good binding library and stuck it on NuGet!

GitHub: https://github.com/saturdaymp/XPlugins.iOS.BEMCheckBox
NuGet: https://www.nuget.org/packages/SaturdayMP.XPlugins.iOS.BEMCheckBox/

Let’s focus on using this for the iOS renderer, and get started building this damn thing once and for all.

The Xamarin.Forms Side

We’re going to need a bunch of properties for colors for different states, as well as a Command and Event for handling when the check changes. Here’s the class to encapsulate all that:

Checkbox.cs

using System;
using System.Windows.Input;
using Xamarin.Forms;
...

/// <summary>
/// A xamarin.forms custom checkbox control that will use native renderers under the hood
/// </summary>
public class Checkbox : View
{
    public event EventHandler OnCheckChanged;

    public static BindableProperty OutlineColorProperty = BindableProperty.Create(nameof(OutlineColor), typeof(Color), typeof(Checkbox), Color.Black);
    public static BindableProperty InnerColorProperty = BindableProperty.Create(nameof(InnerColor), typeof(Color), typeof(Checkbox), Color.White);
    public static BindableProperty CheckColorProperty = BindableProperty.Create(nameof(CheckColor), typeof(Color), typeof(Checkbox), Color.Black);
    public static BindableProperty CheckedOutlineColorProperty = BindableProperty.Create(nameof(CheckedOutlineColor), typeof(Color), typeof(Checkbox), Color.Black);
    public static BindableProperty CheckedInnerColorProperty = BindableProperty.Create(nameof(CheckedInnerColor), typeof(Color), typeof(Checkbox), Color.White);
    public static BindableProperty IsCheckedProperty = BindableProperty.Create(nameof(IsChecked), typeof(bool), typeof(Checkbox), false, BindingMode.TwoWay);
    public static BindableProperty CheckedCommandProperty = BindableProperty.Create(nameof(CheckedCommand), typeof(ICommand), typeof(Checkbox), null);
    public static BindableProperty CheckedCommandParameterProperty = BindableProperty.Create(nameof(CheckedCommandParameter), typeof(object), typeof(Checkbox), null);

    public object CheckedCommandParameter
    {
        get
        {
            return GetValue(CheckedCommandParameterProperty);
        }
        set
        {
            SetValue(CheckedCommandParameterProperty, value);
        }
    }
    public ICommand CheckedCommand
    {
        get
        {
            return (ICommand)GetValue(CheckedCommandProperty);
        }
        set
        {
            SetValue(CheckedCommandProperty, value);
        }
    }
    public bool IsChecked
    {
        get
        {
            return (bool)GetValue(IsCheckedProperty);
        }
        set
        {
            SetValue(IsCheckedProperty, value);
        }
    }
    public Color CheckColor
    {
        get
        {
            return (Color)GetValue(CheckColorProperty);
        }
        set
        {
            SetValue(CheckColorProperty, value);
        }
    }
    public Color InnerColor
    {
        get
        {
            return (Color)GetValue(InnerColorProperty);
        }
        set
        {
            SetValue(InnerColorProperty, value);
        }
    }
    public Color OutlineColor
    {
        get
        {
            return (Color)GetValue(OutlineColorProperty);
        }
        set
        {
            SetValue(OutlineColorProperty, value);
        }
    }
    public Color CheckedInnerColor
    {
        get
        {
            return (Color)GetValue(CheckedInnerColorProperty);
        }
        set
        {
            SetValue(CheckedInnerColorProperty, value);
        }
    }
    public Color CheckedOutlineColor
    {
        get
        {
            return (Color)GetValue(CheckedOutlineColorProperty);
        }
        set
        {
            SetValue(CheckedOutlineColorProperty, value);
        }
    }

    public void FireCheckChange()
    {
        OnCheckChanged?.Invoke(this, new CheckChangedArgs
        {
            IsChecked = IsChecked
        });
    }

    public class CheckChangedArgs : EventArgs
    {
        public bool IsChecked { get; set; }
    }
}

Now that we have a component set up to manage all the different colors and handle the checked state and events, we need to create native renderers to use and update these!

Let’s start with Android.

Android Checkbox Renderer

Android has a built in CheckBox control, so we can use that directly in our renderer. Here’s what that might look like:

AndroidCheckboxRenderer.cs

using System;
using Android.Content;
using Android.Content.Res;
using Android.Support.V7.Widget;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
...

/// <summary>
/// Xamarin.Forms custom renderer for the Checkbox control
/// </summary>
public class CheckboxRenderer : ViewRenderer<Checkbox, AppCompatCheckBox>, CompoundButton.IOnCheckedChangeListener
{
    private const int DEFAULT_SIZE = 28;

    public CheckboxRenderer(Context context) : base(context)
    {
    }

    /// <summary>
    /// Used for registration with dependency service to ensure it isn't linked out
    /// </summary>
    public static void Init()
    {
        // intentionally empty
    }

    /// <summary>
    /// Update element bindable property from event
    /// </summary>
    /// <param name="buttonView">Button view.</param>
    /// <param name="isChecked">If set to <c>true</c> is checked.</param>
    public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
    {
        ((IViewController)Element).SetValueFromRenderer(Checkbox.IsCheckedProperty, isChecked);
        Element.CheckedCommand?.Execute(Element.CheckedCommandParameter);
    }

    public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
    {
        var sizeConstraint = base.GetDesiredSize(widthConstraint, heightConstraint);

        if (sizeConstraint.Request.Width == 0)
        {
            var width = widthConstraint;
            if (widthConstraint <= 0)
            {
                System.Diagnostics.Debug.WriteLine("Default values");
                width = DEFAULT_SIZE;
            }
            else if (widthConstraint <= 0)
            {
                width = DEFAULT_SIZE;
            }

            sizeConstraint = new SizeRequest(new Size(width, sizeConstraint.Request.Height),
                new Size(width, sizeConstraint.Minimum.Height));
        }

        return sizeConstraint;
    }


    /// <summary>
    /// Called when the control is created or changed
    /// </summary>
    /// <param name="e">E.</param>
    protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            if (Control == null)
            {
                var checkBox = new AppCompatCheckBox(Context);

                if (Element.OutlineColor != default(Color))
                {
                    var backgroundColor = GetBackgroundColorStateList(Element.OutlineColor);
                    checkBox.SupportButtonTintList = backgroundColor;
                    checkBox.BackgroundTintList = GetBackgroundColorStateList(Element.InnerColor);
                    checkBox.ForegroundTintList = GetBackgroundColorStateList(Element.OutlineColor);

                }
                checkBox.SetOnCheckedChangeListener(this);
                SetNativeControl(checkBox);
            }

            Control.Checked = e.NewElement.IsChecked;
        }
    }


    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        if (e.PropertyName == nameof(Element.IsChecked))
        {
            Control.Checked = Element.IsChecked;
        }
        else
        {
            var backgroundColor = GetBackgroundColorStateList(Element.CheckColor);
            Control.SupportButtonTintList = backgroundColor;
            Control.BackgroundTintList = GetBackgroundColorStateList(Element.InnerColor);
            Control.ForegroundTintList = GetBackgroundColorStateList(Element.OutlineColor);
        }
    }

    /// <summary>
    /// Sync from native control
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    private void CheckBoxCheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
    {
        Element.IsChecked = e.IsChecked;
    }


    private ColorStateList GetBackgroundColorStateList(Color color)
    {
        return new ColorStateList(
            new[]
            {
                new[] {-global::Android.Resource.Attribute.StateEnabled}, // checked
                new[] {-global::Android.Resource.Attribute.StateChecked}, // unchecked
                new[] {global::Android.Resource.Attribute.StateChecked} // checked
            },
            new int[]
            {
                color.WithSaturation(0.1).ToAndroid(),
                color.ToAndroid(),
                color.ToAndroid()
            });
    }

}

This is basically just setting up the ColorStateList for the control based on the colors from the controls and then wiring up the state to and events to the Xamarin.Forms control we created and the native Android Checkbox.

Now let’s look at iOS.

iOS Checkbox Renderer

Unlike Android, iOS does NOT have any form of a Checkbox control. The most common workaround for this is to use a simple image of a check and a square and interchange them and manage the state. But we want more flexibility, animations, and all the fun stuff that comes with a fully built control.

Here’s how we solved this issue for this particular set of Controls – using the BEMCheckBox.

BEMCheckBox is a third party control from Boris-Em and our lovely Xamarin.Community has already created Bindings for it AND a NuGet package. Here are the links:

Be sure to install this NuGet package in your iOS project, and then we can get to work on the renderer:

iOSCheckboxRenderer.cs

using System;
using SaturdayMP.XPlugins.iOS;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
...

/// <summary>
/// Xamarin.Forms custom renderer for the Checkbox control
/// </summary>
public class CheckboxRenderer : ViewRenderer<Checkbox, BEMCheckBox>
{
    private const int DEFAULT_SIZE = 28;

    /// <summary>
    /// Used for registration with dependency service to ensure it isn't linked out
    /// </summary>
    public static void Initialize()
    {
        // intentionally empty
    }


    public override SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint)
    {
        var sizeConstraint = base.GetDesiredSize(widthConstraint, heightConstraint);

        if (sizeConstraint.Request.Width == 0)
        {
            var width = widthConstraint;
            if (widthConstraint <= 0)
            {
                System.Diagnostics.Debug.WriteLine("Default values");
                width = DEFAULT_SIZE;
            }
            else if (widthConstraint <= 0)
            {
                width = DEFAULT_SIZE;
            }

            sizeConstraint = new SizeRequest(new Size(width, sizeConstraint.Request.Height),
                new Size(width, sizeConstraint.Minimum.Height));
        }

        return sizeConstraint;
    }


    /// <summary>
    /// Called when the control is created or changed
    /// </summary>
    /// <param name="e">E.</param>
    protected override void OnElementChanged(ElementChangedEventArgs<Checkbox> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            if (Control == null)
            {
                var checkBox = new BEMCheckBox();

                checkBox.BoxType = BEMBoxType.Square;
                checkBox.OnAnimationType = BEMAnimationType.Fill;
                checkBox.OffAnimationType = BEMAnimationType.Fill;

                // set default colors
                UpdateColors(checkBox);

                SetNativeControl(checkBox);
            }

            Control.On = e.NewElement.IsChecked;
            Control.ValueChanged += Control_ValueChanged;
        }
    }

    protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        if(e.PropertyName == nameof(Element.IsChecked))
        {
            Control.On = Element.IsChecked; 
        }
        else
        {
            UpdateColors(Control);
        }
    }

    void Control_ValueChanged(object sender, EventArgs e)
    {
        Element.IsChecked = Control.On;
        Element.CheckedCommand?.Execute(Element.CheckedCommandParameter);
    }

    private void UpdateColors(BEMCheckBox nativeCheckBox)
    {
        nativeCheckBox.TintColor = Element.OutlineColor.ToUIColor();
        nativeCheckBox.OffFillColor = Element.InnerColor.ToUIColor();
        nativeCheckBox.OnFillColor = Element.CheckedInnerColor.ToUIColor();
        nativeCheckBox.OnTintColor = Element.CheckedOutlineColor.ToUIColor();
        nativeCheckBox.OnCheckColor = Element.CheckColor.ToUIColor();
    }
}

We follow the same sort of pattern as we did with the Android renderer, but this time we use the BEMCheckBox and wire up the colors, animation styles (which you can change to whatever you want), and setup the events and state.

Using the CheckBox

Now we can use this in our XAML with bindings and styles and everything. We can use it in lists for multi select, or anywhere else!

MainPage.xaml

...
 <StackLayout Orientation="Horizontal" Spacing="16">
    <components:Checkbox CheckedCommandParameter="{Binding .}" IsChecked="{Binding UseExistingAddress}" VerticalOptions="Center" OutlineColor="{DynamicResource PrimaryTextColor}" CheckedOutlineColor="{DynamicResource PrimaryTextColor}" CheckColor="{DynamicResource PrimaryColor}"  WidthRequest="{StaticResource CheckboxSize}" HeightRequest="{StaticResource CheckboxSize}"/>
    <Label Text="Use existing address" VerticalOptions="Center" Style="{DynamicResource BodySecondary}"/>
</StackLayout>
...

See how good it looks?

iOS with empty fill:
Screen Shot 2018-04-09 at 12.05.31 PM
Screen Shot 2018-04-09 at 12.03.19 PM

Android with fill:
Screen Shot 2018-04-09 at 12.05.24 PM
Screen Shot 2018-04-09 at 12.03.40 PM


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Advertisement

Xamarin.Tip – Updating a Xamarin.Forms Button Style When Disabled

Ever wanted to change the way the Xamarin.Forms disabled button looks? For example, changing the background color and text color, or the shape?

Here’s a quick tip with no custom renderers to build a Xamarin.Forms Button that can have its style updated when the IsEnabled is set to false. The concept is basically to create a class that inherits from Button, and keep track of the original Style property, then add a BindableProperty for the style to apply when disabled, and flip between the two when IsEnabled changes.

Here is what that class looks like:

DisablingButton.cs

/// <summary>
/// Xamarin.Forms Button that allows for applying a different style when enabled.
/// </summary>
public class DisablingButton : Button
{
    private Style _normalStyle;

    public static readonly BindableProperty DisabledStyleProperty =
        BindableProperty.Create(nameof(DisabledStyle), typeof(Style), typeof(DisablingButton), null, BindingMode.TwoWay, null, (obj, oldValue, newValue) => { });

    public Style DisabledStyle
    {
        get { return (Style)GetValue(DisabledStyleProperty); }
        set { SetValue(DisabledStyleProperty, value); }
    }

    public DisablingButton()
    {
        _normalStyle = Style;

        PropertyChanged += ExtendedButton_PropertyChanged;

    }

    private void ExtendedButton_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(IsEnabled) && DisabledStyle != null)
        {
            if (IsEnabled)
                Style = _normalStyle;
            else
                Style = DisabledStyle;
        }
    }
}

So now we can use this with two different styles, and bind it all in our XAML. For example –

Styles.xaml

...
<Style x:Key="StickyBlueButton" TargetType="Button">
    <Setter Property="HeightRequest" Value="50"/>
    <Setter Property="BackgroundColor" Value="{DynamicResource BlueAccentColor}"/>
    <Setter Property="TextColor" Value="White"/>
    <Setter Property="BorderRadius" Value="0"/>
    <Setter Property="FontSize" Value="16"/>
    <Setter Property="FontAttributes" Value="Bold"/>
    <Setter Property="Margin" Value="0"/>
    <Setter Property="HorizontalOptions" Value="FillAndExpand"/>
    <Setter Property="FontFamily" Value="{StaticResource ProximaNovaBold}"/>
    <Setter Property="VerticalOptions" Value="End"/>
</Style>
<Style x:Key="StickyBlueButtonDisabled" TargetType="Button">
    <Setter Property="HeightRequest" Value="50"/>
    <Setter Property="BackgroundColor" Value="{DynamicResource LightGray}"/>
    <Setter Property="TextColor" Value="White"/>
    <Setter Property="BorderRadius" Value="0"/>
    <Setter Property="FontSize" Value="16"/>
    <Setter Property="FontAttributes" Value="Bold"/>
    <Setter Property="Margin" Value="0"/>
    <Setter Property="HorizontalOptions" Value="FillAndExpand"/>
    <Setter Property="FontFamily" Value="{StaticResource ProximaNovaBold}"/>
    <Setter Property="VerticalOptions" Value="End"/>
</Style>
...

and then use them in the page…

MainPage.xaml

...
<components:DisablingButton 
    IsEnabled="{Binding IsEnabled}"
    Command="{Binding CreateNewItemCommand}" 
    Style="{DynamicResource StickyBlueButton}" 
    DisabledStyle="{DynamicResource StickyBlueButtonDisabled}" 
    Text="Add item" />
...

And here’s what it looks like!

IsEnabled = true
Screen Shot 2018-04-02 at 4.38.07 PM

IsEnabled = false
Screen Shot 2018-04-02 at 4.41.59 PM


If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Tip – Build Your Own AccordionView in Xamarin.Forms

If you’re like me, you probably tried to find some decent “Accordion” style view for your Xamarin.Forms app. You probably found some on NuGet that aren’t supported anymore or some old GitHub repositories. Here’s my advice – give up the search and just give in and build your own. But you don’t have to do it alone! Here is a guide to build a simple but flexible and reusable AccordionView. If enough people like it, maybe we’ll put it up on NuGet for consumption.

Here’s the approach we are taking – We need a list of “sections”. Each Section has a header and the body content. For the sake of the simple example, we’ll do it with just a label body, but this could easily be done with a DataTemplate or more complicated view (I use the HtmlLabel for this irl https://github.com/matteobortolazzo/HtmlLabelPlugin).

Let’s create a view called AccordionSectionView:

AccordionSectionView.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="YourNamespace.AccordionSectionView">
    <ContentView.Content>
        <StackLayout Orientation="Vertical" Spacing="0">
            <Grid x:Name="HeaderView">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="48"/>
                </Grid.ColumnDefinitions>
                <Label x:Name="HeaderLabel"  VerticalOptions="Center" Margin="16" LineBreakMode="WordWrap" />
                <Label x:Name="IndicatorLabel" Style="{DynamicResource BodySecondaryBold}"  FontSize="32" VerticalOptions="Center" HorizontalOptions="Center" HorizontalTextAlignment="Center" VerticalTextAlignment="Center" Margin="0,16,16,16" Grid.Column="1"/>
                <Grid.GestureRecognizers>
                    <TapGestureRecognizer Tapped="Handle_Tapped"/>
                </Grid.GestureRecognizers>
            </Grid>
            <Grid x:Name="BodyView">
                <Label x:Name="BodyLabel" Style="{DynamicResource Body}" FontSize="16" Margin="16"/>
            </Grid>
        </StackLayout>
    </ContentView.Content>
</ContentView>

So we have a StackLayout with a container Grid for the header and our header has a TapGestureRecognizer.

Now let’s look at how the code behind wires it all up.

AccordionSectionView.xaml.cs

/// <summary>
/// Accordion section view.
/// </summary>
public partial class AccordionSectionView : ContentView
{
    #region Bindable Properties
    public static BindableProperty HeaderBackgroundColorProperty = 
        BindableProperty.Create(nameof(HeaderBackgroundColor),                                                                                                      
            typeof(Color),                                                                                                  
            typeof(AccordionSectionView),                                                                                              
            defaultValue: Color.Transparent,                                                                                               
            propertyChanged: (bindable, oldVal, newVal) =>                                                                                                       
            {                                                                                                    
                ((AccordionSectionView)bindable).UpdateHeaderBackgroundColor();                                                                                                
            });

        public static BindableProperty HeaderOpenedBackgroundColorProperty = 
            BindableProperty.Create(nameof(HeaderOpenedBackgroundColor),                                                                                                  
                typeof(Color),                                                                                                       
                typeof(AccordionSectionView),
                defaultValue: Color.Transparent,                                                                                                   
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                               
                {                                                                                                  
                    ((AccordionSectionView)bindable).UpdateHeaderBackgroundColor();                                                                                              
                });

        public static BindableProperty HeaderTextColorProperty =
            BindableProperty.Create(nameof(HeaderTextColor),                                                                                        
                typeof(Color),                                                                                                 
                typeof(AccordionSectionView),                                                                                        
                defaultValue: Color.Black,                                                                                          
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                         
                {                                                                                             
                    ((AccordionSectionView)bindable).UpdateHeaderTextColor((Color)newVal);                                                                                         
                });

        public static BindableProperty HeaderTextProperty =
            BindableProperty.Create(nameof(HeaderTextProperty),                                                                                    
                typeof(string),                                                                                    
                typeof(AccordionSectionView),                                                                                   
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                   
                {                                                                                      
                    ((AccordionSectionView)bindable).UpdateHeaderText((string)newVal);                                                                                   
                });

        public static BindableProperty BodyTextColorProperty =
            BindableProperty.Create(nameof(BodyTextColor),                                                                                      
                typeof(Color),                                                                                      
                typeof(AccordionSectionView),                                                                                       
                defaultValue: Color.Black,                                                                                       
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                       
                {                                                                                         
                    ((AccordionSectionView)bindable).UpdateBodyTextColor((Color)newVal)                                                                                     
                });

        public static BindableProperty BodyTextProperty = 
            BindableProperty.Create(nameof(BodyText),                                                                                 
                typeof(string),                                                                                  
                typeof(AccordionSectionView),                                                                                  
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                  
                {                                                                                      
                    ((AccordionSectionView)bindable).UpdateBodyText((string)newVal);                                                                                 
                });


        public static BindableProperty IsBodyVisibleProperty = 
            BindableProperty.Create(nameof(IsBodyVisible),                                                                                             
                typeof(bool),                                                                                       
                typeof(AccordionSectionView),                                                                                      
                defaultValue: true,
                propertyChanged: (bindable, oldVal, newVal) =>                                                                                       
                {                                                                                           
                    ((AccordionSectionView)bindable).UpdateBodyVisibility((bool)newVal);                                                                                          
                });
    #endregion

    #region Public Properties
    public Color HeaderBackgroundColor
    {
        get
        {
            return (Color)GetValue(HeaderBackgroundColorProperty);
        }
        set
        {
            SetValue(HeaderBackgroundColorProperty, value);
        }
    }
    public Color HeaderOpenedBackgroundColor
    {
        get
        {
            return (Color)GetValue(HeaderOpenedBackgroundColorProperty);
        }
        set
        {
                SetValue(HeaderOpenedBackgroundColorProperty, value);
        }
    }
    public Color HeaderTextColor
    {
        get
        {
            return (Color)GetValue(HeaderTextColorProperty);
        }
        set
        {
            SetValue(HeaderTextColorProperty, value);
        }
    }
    public string HeaderText
    {
        get
        {
            return (string)GetValue(HeaderTextProperty);
        }
        set
        {
            SetValue(HeaderTextProperty, value);
        }
    }
    public Color BodyTextColor
    {
        get
        {
            return (Color)GetValue(BodyTextColorProperty);
        }
        set
        {
            SetValue(BodyTextColorProperty, value);
        }
    }
    public string BodyText
    {
        get
        {
            return (string)GetValue(BodyTextProperty);
        }
        set
        {
            SetValue(BodyTextProperty, value);
        }
    }

    public bool IsBodyVisible
    {
        get
        {
            return (bool)GetValue(IsBodyVisibleProperty);
        }
        set
        {
            SetValue(IsBodyVisibleProperty, value);
        }
    }

    #endregion


    public AccordionSectionView()
    {
        InitializeComponent();
        IsBodyVisible = false; 
        if (Resources != null)
        {
            Resources.MergedWith = typeof(PrimaryTheme);
        }
        else
        {
            Resources = new ResourceDictionary
            {
                MergedWith = typeof(PrimaryTheme)
            };
        }
    }

    /// <summary>
    /// Updates the color of the header background.
    /// </summary>
    /// <param name="color">Color.</param>
    public void UpdateHeaderBackgroundColor(Color color)
    {
        HeaderView.BackgroundColor = color;
    }

    /// <summary>
    /// Updates the color of the header background.
    /// </summary>
    public void UpdateHeaderBackgroundColor()
    {
        if (IsBodyVisible)
        {
            HeaderView.BackgroundColor = HeaderOpenedBackgroundColor;
            BodyView.BackgroundColor = HeaderOpenedBackgroundColor;
        }
        else
        {
            HeaderView.BackgroundColor = HeaderBackgroundColor;
        }
    }

    /// <summary>
    /// Updates the color of the header text.
    /// </summary>
    /// <param name="color">Color.</param>
    public void UpdateHeaderTextColor(Color color)
    {
        HeaderLabel.TextColor = color;
    }

    /// <summary>
    /// Updates the color of the body text.
    /// </summary>
    /// <param name="color">Color.</param>
    public void UpdateBodyTextColor(Color color)
    {
        BodyLabel.TextColor = color;
    }

    /// <summary>
    /// Updates the header text.
    /// </summary>
    /// <param name="text">Text.</param>
    public void UpdateHeaderText(string text)
    {
        HeaderLabel.Text = text;
    }

    /// <summary>
    /// Updates the body text.
    /// </summary>
    /// <param name="text">Text.</param>
    public void UpdateBodyText(string text)
    {
        BodyLabel.Text = text;
    }

    public void UpdateBodyVisibility(bool isVisible)
    {
        BodyView.IsVisible = isVisible;
        IndicatorLabel.Text = "+";
        if(isVisible)
        {
            IndicatorLabel.RotateTo(45, 100);
        }
        else
        {
            IndicatorLabel.RotateTo(0, 100);
        }
    }

    private void Handle_Tapped(object sender, System.EventArgs e)
    {
        IsBodyVisible = !IsBodyVisible;
        UpdateHeaderBackgroundColor();
    }
}

We start by wiring up some bindable properties for the look and feel of the header and body sections, and then handle animations of the IndicatorLabel when the body is opened and closed. These even lets us update the content when the body is opened or closed.

Now we can use this in our pages. Bonus points when used with me DynamicStackLayout control to create dynamic bindable accordions!

MainPage.xaml

...
 <suave:DynamicStackLayout x:Name="Accordion" Orientation="Vertical" ItemsSource="{Binding Items}" Spacing="1" BackgroundColor="{DynamicResource AlternateBackground}">
    <suave:DynamicStackLayout.ItemTemplate>
        <DataTemplate>
                <components:AccordionSectionView HeaderText="{Binding Title}" IsBodyVisible="False" HeaderBackgroundColor="White" HeaderOpenedBackgroundColor="{DynamicResource AlternateBackground}" HeaderTextColor="Black" BodyTextColor="Black" BodyText="{Binding HtmlContent}" />
        </DataTemplate>
    </suave:DynamicStackLayout.ItemTemplate>
</suave:DynamicStackLayout>
...

Now all together we have something pretty cool!
Screen Shot 2018-03-26 at 4.40.34 PM


Screen Shot 2018-03-26 at 4.40.48 PM

And that’s it! Let me know what you’ve done to build your own Accordions and collapsible views!



If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Forms MarkdownTextView NuGet Announcement!

A while back I talked about building your own Markdown Label / TextView to your Xamarin applications including Xamarin Native and Xamarin Forms. I decided to put the control I’ve been working off of over on NuGet so anyone can use its basic form.

Note that this control isn’t final, I am working on some refactoring and enhancements, but if you want something that just works, then give it a shot, or contribute to the repository!

Previous Posts

Down below you can find the documentation as it’s found on the GitHub repo!

MarkdownTextView

alt tag

A Xamarin.Forms component to display markdown text in a TextView

Installation

Now on NuGet!

Install-Package MarkdownTextView.Forms

https://www.nuget.org/packages/MarkdownTextView.Forms

Usage

  • Call Init before calling Xamarin.Forms.Init()
  • iOS: SPControls.MarkdownTextView.iOS.MarkdownTextView.Init();
  • Android: SPControls.MarkdownTextView.Droid.MarkdownTextView.Init();
  • Use the control
  • In Xaml:
<ContentPage ...
             xmlns:spcontrols="clr-namespace:SPControls.Forms;assembly=SPControls.MarkdownTextView"
             ...>
  <spcontrols:MarkdownTextView Markdown="{Binding MarkdownString}" />
</ContentPage>
  • or in C#:
var mdTextView = new MarkdownTextView();
mdTextView.Markdown = "# this is my *header* tag";

TODO

  • Add other properties for updating markdown settings
  • Add text color settings
  • Add UWP Support

Xamarin.Controls – Material Form Control Updates

A while back I put together a GitHub repository for Material Design Controls for form elements (Entry, Picker, etc.) for Xamarin.Forms (iOS, Android, and UWP).
You can find some of the original posts about that here:

And you can find the GitHub repo and NuGet package here:

In this post, I wanted to focus on some of the newer features I’ve added to the latest 2018.1.28-pre1 release.

The gist is:

  • Invalid vs. Valid State
  • Font updates
  • Color flexibility

With these, you can now specify updated default colors, invalid state colors, and the original focused accent colors.
Here’s an example of a Style resource that uses these:

    <!-- Material Entry Styles -->
    <Style x:Key="PrimaryMaterialEntry" TargetType="material:MaterialEntry">
        <Setter Property="AccentColor" Value="{DynamicResource PrimaryColor}"/>
        <Setter Property="DefaultColor" Value="Gray"/>
        <Setter Property="InvalidColor" Value="Red"/>
    </Style>

What does validation state mean? You can attach behaviors to easily add validation to your material form controls. Here is an example behavior on the MaterialEntry that requires a length, and sets the state:

   /// <summary>
    /// Material entry length behavior. Allows for the limitation of the text with a min and max length
    /// </summary>
    public class MaterialEntryLengthValidationBehavior : Behavior<MaterialEntry>
    {
        public int MaxLength { get; set; }
        public int MinLength { get; set; } = 0;

        /// <summary>
        /// Attach events on attachment to view
        /// </summary>
        /// <param name="bindable">Bindable.</param>
        protected override void OnAttachedTo(MaterialEntry bindable)
        {
            base.OnAttachedTo(bindable);
            bindable.TextChanged += OnEntryTextChanged;
            bindable.EntryUnfocused += Bindable_EntryUnfocused;
        }

        /// <summary>
        /// Detach events on detaching from view
        /// </summary>
        /// <param name="bindable">Bindable.</param>
        protected override void OnDetachingFrom(MaterialEntry bindable)
        {
            base.OnDetachingFrom(bindable);
            bindable.TextChanged -= OnEntryTextChanged;
            bindable.EntryUnfocused -= Bindable_EntryUnfocused;
        }

        /// <summary>
        /// Stop text input once max is hit
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        void OnEntryTextChanged(object sender, TextChangedEventArgs e)
        {
            var entry = (Entry)sender;

            if (entry.Text == null)
                return;

            // if Entry text is longer than valid length
            if (entry.Text.Length > this.MaxLength)
            {
                string entryText = entry.Text;

                entryText = entryText.Remove(entryText.Length - 1); // remove last char

                entry.Text = entryText;
            }


        }

        /// <summary>
        /// Set invalid on unfocus if the min is not met
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        void Bindable_EntryUnfocused(object sender, FocusEventArgs e)
        {
            var entry = (MaterialEntry)sender;
            if (MinLength > 0)
            {
                if (entry.Text == null || entry.Text.Length < this.MinLength)
                {
                    entry.IsValid = false;
                }
                else
                {
                    entry.IsValid = true;
                }
            }
        }
    }

Then you can attach it to a MaterialEntry

<material:MaterialEntry Placeholder="CVV" Keyboard="Numeric" Text="{Binding CVV}" Style="{DynamicResource PrimaryMaterialEntry}">
    <material:MaterialEntry.Behaviors>
        <behaviors:MaterialEntryLengthValidationBehavior MaxLength="3" MinLength="3"/>
    </material:MaterialEntry.Behaviors>
</material:MaterialEntry>

And then you get:
Screen Shot 2018-02-22 at 4.19.52 PMScreen Shot 2018-02-22 at 4.19.57 PMScreen Shot 2018-02-22 at 4.20.03 PMScreen Shot 2018-02-22 at 4.20.11 PM

These validation states, and updated color properties are available on all the Material Forms controls, so install the preview nuget package and get started!

Install-Package MaterialFormControls -Version 2018.1.28-pre1

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.NuGet – Native Behaviors

I’ve been doing a lot of mix development between Xamarin.Forms and Native Xamarin iOS/Android. Because of this, we abstract away concepts that can be shared regardless of using Xamarin.Forms or not. One thing I personally like, and use all the time, is Xamarin.Forms Behaviors. I wanted a way to use the same pattern with my native components. I went ahead and created NativeBehaviors.

Get it

Concepts

Like mentioned above, we wanted to follow the same style as Xamarin.Forms Behaviors without having a dependency on Xamarin.Forms. You can create a NativeBehavior for any native control, and then handle attaching and detaching the behavior within the lifecycle of your ViewController, Activity, Fragment, etc.

This helps us keep our event handling cleaned up in memory while still being useful, easy to follow, re-usable, and flexible.

Documentation

Read below for the documentation on how to build your own NativeBehaviors and start making your controls more flexible!

NativeBehaviors

A behaviors implementation for Xamarin Native controls meant to reflect the usefulness of Xamarin.Forms.Behaviors

Installation

This project is up on NuGet now:

Install-Package NativeBehaviors

Usage

These behaviors are meant to reflect the same use as Xamarin.Forms Behaviors, but require a little more work to manage the lifecycle of events since native views don’t have a collection of Behaviors as a property.

  1. Create a custom Behavior on your native platform. This example will use an iOS UITextField

TextFieldPhoneMaskBehavior.cs

/// <summary>
/// Text field phone mask behavior.
/// Format of (###) ###-####
/// </summary>
public class TextFieldPhoneMaskBehavior : NativeBehavior<UITextField>
{
    public override string BehaviorName => nameof(TextFieldPhoneMaskBehavior);

    private string _previousText = string.Empty;
    /// <summary>
    /// Attach text changed event
    /// </summary>
    /// <param name="bindable">Bindable.</param>
    protected override void OnAttachedTo(UITextField bindable)
    {
        bindable.EditingChanged += Bindable_TextChanged;
    }

    /// <summary>
    /// Detach event
    /// </summary>
    /// <param name="bindable">Bindable.</param>
    protected override void OnDetachingFrom(UITextField bindable)
    {
        bindable.EditingChanged -= Bindable_TextChanged;
    }

    /// <summary>
    /// Apply mask
    /// </summary>
    /// <param name="sender">Sender.</param>
    /// <param name="e">E.</param>
    void Bindable_TextChanged(object sender, EventArgs e)
    {
        var textField = sender as UITextField;

        // only apply change if typing forward
        if (textField != null && textField.Text.Length > _previousText.Length)
        {
            if (textField.Text.Length == 1)
            {
                // we have our first number, add the ( behind it
                textField.Text = $"({textField.Text}";
            }
            if (textField.Text.Length == 4)
            {
                // finish the area code
                textField.Text += ") ";
            }
            if (textField.Text.Length == 9)
            {
                // add dash
                textField.Text += "-";
            }
        }
        _previousText = textField.Text;
    }
}
  1. Attach Behavior in lifecycle

MyViewController.cs

using NativeBehaviors; // needed for extension method
...
private TextFieldPhoneMaskBehavior _phoneMaskBehavior;

protected override void ViewWillAppear(bool animated)
{
    ...
    _phoneMaskBehavior = new TextFieldPhoneMaskBehavior();
    myTextField.AttachBehavior(_phoneMaskBehavior);
    ...
}
...
  1. Detach Behavior in lifecycle

MyViewController.cs

using NativeBehaviors; // needed for extension method
...
protected override void ViewDidDisappear()
{
    ...
    myTextField.DetachBehavior(_phoneMaskBehavior);
    ...
}
...

Use the OnCreate and OnDestroy events in your Android Activities.

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Tip – Xamarin.Forms Long Press Effect

Here’s a quick and helpful tool to use in your Xamarin.Forms applications! How many times have you wanted to add a long press handler? Seems like something that should be a simple Gesture built into the platform, but we have to fend for ourselves. Luckily the solution is pretty simple using Xamarin.Forms Effects!

Let’s first create our shared Effect in our shared code:

LongPressedEffect.cs

    /// <summary>
    /// Long pressed effect. Used for invoking commands on long press detection cross platform
    /// </summary>
    public class LongPressedEffect : RoutingEffect
    {
        public LongPressedEffect() : base("MyApp.LongPressedEffect")
        {
        }

        public static readonly BindableProperty CommandProperty = BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(LongPressedEffect), (object)null);
        public static ICommand GetCommand(BindableObject view)
        {
            return (ICommand)view.GetValue(CommandProperty);
        }

        public static void SetCommand(BindableObject view, ICommand value)
        {
            view.SetValue(CommandProperty, value);
        }


        public static readonly BindableProperty CommandParameterProperty = BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(LongPressedEffect), (object)null);
        public static object GetCommandParameter(BindableObject view)
        {
            return view.GetValue(CommandParameterProperty);
        }

        public static void SetCommandParameter(BindableObject view, object value)
        {
            view.SetValue(CommandParameterProperty, value);
        }
    }

Now we have 2 bindable properties – the Command that we want to bind when the long press is detected and the CommandParameter to pass into the Command.

We can use these in our native Effect implementations to invoke when the press is detected. Let’s create our Android implementation.

AndroidLongPressedEffect.cs

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(AndroidLongPressedEffect), "LongPressedEffect")]
namespace AndroidAppNamespace.Effects
{
    /// <summary>
    /// Android long pressed effect.
    /// </summary>
    public class AndroidLongPressedEffect : PlatformEffect
    {
        private bool _attached;

        /// <summary>
        /// Initializer to avoid linking out
        /// </summary>
        public static void Initialize() { }

        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="T:Yukon.Application.AndroidComponents.Effects.AndroidLongPressedEffect"/> class.
        /// Empty constructor required for the odd Xamarin.Forms reflection constructor search
        /// </summary>
        public AndroidLongPressedEffect()
        {
        }

        /// <summary>
        /// Apply the handler
        /// </summary>
        protected override void OnAttached()
        {
            //because an effect can be detached immediately after attached (happens in listview), only attach the handler one time.
            if (!_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick += Control_LongClick;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick += Control_LongClick;
                }
                _attached = true;
            }
        }

        /// <summary>
        /// Invoke the command if there is one
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">E.</param>
        private void Control_LongClick(object sender, Android.Views.View.LongClickEventArgs e)
        {
            Console.WriteLine("Invoking long click command");
            var command = LongPressedEffect.GetCommand(Element);
            command?.Execute(LongPressedEffect.GetCommandParameter(Element));
        }

        /// <summary>
        /// Clean the event handler on detach
        /// </summary>
        protected override void OnDetached()
        {
            if (_attached)
            {
                if (Control != null)
                {
                    Control.LongClickable = true;
                    Control.LongClick -= Control_LongClick;
                }
                else
                {
                    Container.LongClickable = true;
                    Container.LongClick -= Control_LongClick;
                }
                _attached = false;
            }
        }
    }

And now for iOS:

iOSLongPressedEffect.cs

[assembly: ResolutionGroupName("MyApp")]
[assembly: ExportEffect(typeof(iOSLongPressedEffect), "LongPressedEffect")]
namespace iOSNamespace.Effects
{
    /// <summary>
    /// iOS long pressed effect
    /// </summary>
    public class iOSLongPressedEffect : PlatformEffect
    {
        private bool _attached;
        private readonly UILongPressGestureRecognizer _longPressRecognizer;
        /// <summary>
        /// Initializes a new instance of the
        /// <see cref="T:Yukon.Application.iOSComponents.Effects.iOSLongPressedEffect"/> class.
        /// </summary>
        public iOSLongPressedEffect()
        {
            _longPressRecognizer = new UILongPressGestureRecognizer(HandleLongClick);
        }

        /// <summary>
        /// Apply the handler
        /// </summary>
        protected override void OnAttached()
        {
            //because an effect can be detached immediately after attached (happens in listview), only attach the handler one time
            if (!_attached)
            {
                Container.AddGestureRecognizer(_longPressRecognizer);
                _attached = true;
            }
        }

        /// <summary>
        /// Invoke the command if there is one
        /// </summary>
        private void HandleLongClick()
        {
            var command = LongPressedEffect.GetCommand(Element);
            command?.Execute(LongPressedEffect.GetCommandParameter(Element));
        }

        /// <summary>
        /// Clean the event handler on detach
        /// </summary>
        protected override void OnDetached()
        {
            if (_attached)
            {
                Container.RemoveGestureRecognizer(_longPressRecognizer);
                _attached = false;
            }
        }

    }

Now that we have our 2 implementations, let’s use it in our XAML!

MyPage.xaml

<Label Text="Long Press Me!" effects:LongPressedEffect.Command="{Binding ShowAlertCommand}" effects:LongPressedEffect.CommandParameter="{Binding .}">
    <Label.Effects>
        <effects:LongPressedEffect />
    </Label.Effects>
</Label>

Now you can start handling long presses on any control! If you want to add it to a ListView just attach it to either the ViewCell or the internal View of the cell.

 

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.NuGet – Xamarin.Forms Dynamic Bindable StackLayout

I recently released a component I commonly use in my Xamarin.Forms applications for binding data to a wrapping layout here: Xamarin.NuGet – DynamicWrapLayout Announcement! In the spirit of this type of control, I’ve also released a new NuGet package for a bindable DynamicStackLayout. It’s a simple control that allows you to create a StackLayout and bind an ItemsSource collection and an ItemTemplate. This is useful for smaller, but dynamic collections with the use of the orientation changing of a StackLayout. This means you could have a horizontally scrolling list of cards, bind the orientation or change it to vertical, and play with positioning more easily than using a ListView. I would still highly suggest using a ListView over this control for a standard vertical stack of dynamic content since this control does NOT use any view recycling or virtualization which can cause performance issues with large collections or constantly changing collection.

Get it here

NuGet: https://www.nuget.org/packages/DynamicStackLayout

Github: https://github.com/SuavePirate/DynamicStackLayout

In the end, you get something like this!

Be sure to read the documentation below:

DynamicStackLayout

A Xamarin.Forms layout for creating dynamically wrapped views. Inspired by the WrapLayout example: https://developer.xamarin.com/samples/xamarin-forms/UserInterface/CustomLayout/WrapLayout/

Installation

It’s on NuGet! https://www.nuget.org/packages/DynamicStackLayout/

Install-Package DynamicStackLayout

Be sure to install in all projects that use it.

Usage

There are two key properties that make this control useful – the ItemsSource (like a ListView) and the ItemTemplate (although, you can also just add children to the view – it does both!)
Be sure to wrap it in a ScrollView though

XAML

Add the xmlns:

xmlns:suave=&quot;clr-namespace:SuaveControls.DynamicStackLayout;assembly=SuaveControls.DynamicStackLayout&quot;

Use it in your View:

&lt;ScrollView&gt;
    &lt;suave:DynamicStackLayout ItemsSource=&quot;{Binding Items}&quot; HorizontalOptions=&quot;Fill&quot;&gt;
        &lt;suave:DynamicStackLayout.ItemTemplate&gt;
            &lt;DataTemplate&gt;
                &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
                    &lt;Label Text=&quot;{Binding .}&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
                &lt;/StackLayout&gt;
            &lt;/DataTemplate&gt;
        &lt;/suave:DynamicStackLayout.ItemTemplate&gt;
    &lt;/suave:DynamicStackLayout&gt;
&lt;/ScrollView&gt;

Don’t like data-binding and want to just use child views? You can do that too!

&lt;ScrollView&gt;
    &lt;suave:DynamicStackLayout HorizontalOptions=&quot;Fill&quot;&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;0&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;1&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;2&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;3&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
      &lt;StackLayout BackgroundColor=&quot;Gray&quot; WidthRequest=&quot;120&quot; HeightRequest=&quot;180&quot;&gt;
          &lt;Label Text=&quot;4&quot; TextColor=&quot;White&quot; VerticalOptions=&quot;FillAndExpand&quot; HorizontalOptions=&quot;FillAndExpand&quot; VerticalTextAlignment=&quot;Center&quot; HorizontalTextAlignment=&quot;Center&quot; /&gt;
      &lt;/StackLayout&gt;
    &lt;/suave:DynamicStackLayout&gt;
&lt;/ScrollView&gt;

Features

  • Bindable child views
  • Bindable to collections
  • Handles layout changing well (try rotating the device)
  • Doesn’t require custom renderers (All Xamarin.Forms baby!)

Notes

This does not use any native view virtualization, which means performance does not scale well with extremely large data sets.

Coming soon

  • ItemSelected event and SelectedItem bindable property (for now, you can add custom gestures and commands to your DataTemplate and handle the events yourself)
  • Better Collection Updating

 

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.NuGet – DynamicWrapLayout Announcement!

It’s been a little while since I’ve posted about any new Xamarin Controls or updates, but here’s a great one!

I needed to create a layout that could bind to a collection of data in Xamarin.Forms that fit to a dynamic grid that also scaled to larger devices and orientation changes. I thought about creating a new custom renderer that uses the RecyclerView with a GridLayoutManager and a UICollectionView with a custom layout. However, I didn’t need to bind to tons of data, so I decided to create a new control that is entirely Xamarin.Forms – No custom renderers!

I’ve called it the DynamicWrapLayout since it was inspired by the WrapLayout from Charles Petzold and David Britch.

Get it on NuGet: https://www.nuget.org/packages/DynamicWrapLayout/
Get the source on GitHub: https://github.com/SuavePirate/DynamicWrapLayout

Because it is all Xamarin.Forms, it should work on all the platforms that Forms supports! So use it everywhere! I’ll create another post about how I built it if you want to work on your own implementation, but for now, you can read the documentation on the control below.

Documentation

DynamicWrapLayout

A Xamarin.Forms layout for creating dynamically wrapped views. Inspired by the WrapLayout example: https://developer.xamarin.com/samples/xamarin-forms/UserInterface/CustomLayout/WrapLayout/

Installation

It’s on NuGet! https://www.nuget.org/packages/DynamicWrapLayout/

Install-Package DynamicWrapLayout

Be sure to install in all projects that use it.

Usage

There are two key properties that make this control useful – the ItemsSource (like a ListView) and the DataTemplate (although, you can also just add children to the view – it does both!)
Be sure to wrap it in a ScrollView though

XAML

Add the xmlns:

xmlns:suave="clr-namespace:SuaveControls.DynamicWrapLayout;assembly=SuaveControls.DynamicWrapLayout"

Use it in your View:

<ScrollView>
    <suave:DynamicWrapLayout ItemsSource="{Binding Items}" HorizontalOptions="Fill">
        <suave:DynamicWrapLayout.ItemTemplate>
            <DataTemplate>
                <StackLayout BackgroundColor="Gray" WidthRequest="120" HeightRequest="180">
                    <Label Text="{Binding .}" TextColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" />
                </StackLayout>
            </DataTemplate>
        </suave:DynamicWrapLayout.ItemTemplate>
    </suave:DynamicWrapLayout>
</ScrollView>

Don’t like data-binding and want to just use child views? You can do that too!

<ScrollView>
    <suave:DynamicWrapLayout HorizontalOptions="Fill">
      <StackLayout BackgroundColor="Gray" WidthRequest="120" HeightRequest="180">
          <Label Text="0" TextColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" />
      </StackLayout>
      <StackLayout BackgroundColor="Gray" WidthRequest="120" HeightRequest="180">
          <Label Text="1" TextColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" />
      </StackLayout>
      <StackLayout BackgroundColor="Gray" WidthRequest="120" HeightRequest="180">
          <Label Text="2" TextColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" />
      </StackLayout>
      <StackLayout BackgroundColor="Gray" WidthRequest="120" HeightRequest="180">
          <Label Text="3" TextColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" />
      </StackLayout>
      <StackLayout BackgroundColor="Gray" WidthRequest="120" HeightRequest="180">
          <Label Text="4" TextColor="White" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" />
      </StackLayout>
    </suave:DynamicWrapLayout>
</ScrollView>

Features

  • Bindable child views
  • Bindable to collections
  • Handles layout changing well (try rotating the device)
  • Doesn’t require custom renderers (All Xamarin.Forms baby!)

What does this thing look like?

Android:


iOS:


Notes

This does not use any native view virtualization, which means performance does not scale well with extremely large data sets.

Coming soon

  • ItemSelected event and SelectedItem bindable property (for now, you can add custom gestures and commands to your DataTemplate and handle the events yourself)
     

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.

Xamarin.Forms Floating Action Button NuGet Announcement

As promised, here is another release of one of my GitHub libraries for Xamarin! This time we are talking about the FloatingActionButton or “FAB”!

Here are some important links:

GitHub project: https://github.com/SuavePirate/Xamarin.Forms.Controls.FloatingActionButton
NuGet package: https://www.nuget.org/packages/SuaveControls.FloatingActionButton

Don’t forget to read up on my original post on how to create your own FloatingActionButton and how to use it: Xamarin.Controls – Xamarin.Forms FloatingActionButton (including iOS!)

Documentation


Xamarin.Forms.Controls.FloatingActionButton

A custom view to create a FloatingActionButton for both Android and iOS as part of Material Design

That’s right, even on iOS!

How to use

Clone the repository and open include the src projects in your Xamarin.Forms and Platform projects.

Now Available on NuGet!

Install-Package SuaveControls.FloatingActionButton

Special note for iOS: Make sure to call FloatingActionButtonRenderer.InitRenderer(); in your AppDelegate.cs in order to avoid linking it out.

Then you can include it in your XAML or call it from C# (See the example projects for a demo):

<?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:local="clr-namespace:SuaveControls.FabExample"
             xmlns:controls="clr-namespace:SuaveControls.Views;assembly=SuaveControls.FloatingActionButton"
             x:Class="SuaveControls.FabExample.MainPage">
    <StackLayout Margin="32">
        <Label Text="This is a Floating Action Button!" 
           VerticalOptions="Center" 
           HorizontalOptions="Center"/>

        <controls:FloatingActionButton x:Name="FAB" HorizontalOptions="CenterAndExpand" WidthRequest="50" HeightRequest="50"  VerticalOptions="CenterAndExpand" Image="ic_add_white.png" ButtonColor="#03A9F4" Clicked="Button_Clicked"/>
    </StackLayout>
</ContentPage>

Android Example

Android Floating Action Button

iOS Example

iOS Floating Action Button

TODO:

  • Make it more flexible. Add Different color states, add sizing adjustments, etc.
  • Create UWP implementation
  • Create Xamarin Component

Come support the project and join the contributors list! We would love to see this TODO list dropped to nothing!

 

If you like what you see, don’t forget to follow me on twitter @Suave_Pirate, check out my GitHub, and subscribe to my blog to learn more mobile developer tips and tricks!

Interested in sponsoring developer content? Message @Suave_Pirate on twitter for details.