Xamarin.Controls – Xamarin.Forms PinView

In  previous post, I talked about creating a BorderlessEntry view using a Custom Renderer (or an alternative Effect). We are going to use said control in this post, so you can find it here: Xamarin.Forms Borderless Entry
On top of this control, we are also going to use a custom behavior mentioned in a blog post here: Xamarin.Tips – Restrict the Length of Your Entry Text

Now let’s talk about giving your users the ability to create a PIN to secure their account in your app while giving them a nice experience. The solution is the PinView!

We are going to build this as a custom component in Xamarin.Forms:

PinView.xaml

<ContentView xmlns="http://xamarin.com/schemas/2014/forms"               xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"               xmlns:behaviors="clr-namespace:YOUR_NAMESPACE.Behaviors;assembly=YOUR_NAMESPACE"              xmlns:views="clr-namespace:YOUR_NAMESPACE;assembly=YOUR_NAMESPACE"              x:Class="YOUR_NAMESPACE.PinView">
    <ContentView.Resources>
        <ResourceDictionary>
<Style x:Key="PinEntry" TargetType="Entry">
                <Setter Property="Keyboard" Value="Numeric"/>
                <Setter Property="IsPassword" Value="True"/>
                <Setter Property="WidthRequest" Value="50"/>
                <Setter Property="HeightRequest" Value="50"/>
                <Setter Property="Margin" Value="8,0"/>
                <Setter Property="HorizontalTextAlignment" Value="Center"/>
            </Style>
<Style x:Key="BottomBar" TargetType="BoxView">
                <Setter Property="HeightRequest" Value="2"/>
                <Setter Property="BackgroundColor" Value="White"/>
                <Setter Property="WidthRequest" Value="50"/>
                <Setter Property="VerticalOptions" Value="Start"/>
                <Setter Property="Margin" Value="0"/>
            </Style>

        </ResourceDictionary>
    </ContentView.Resources>
  <ContentView.Content>
        <StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
            <StackLayout Orientation="Vertical">
                <views:BorderlessEntry x:Name="Pin1" Style="{StaticResource PinEntry}" TextColor="White">
                    <Entry.Behaviors>
                        <behaviors:EntryLengthValidatorBehavior MaxLength="1"/>
                    </Entry.Behaviors>
                </views:BorderlessEntry>
                <BoxView Style="{StaticResource BottomBar}"/>
            </StackLayout>

            <StackLayout Orientation="Vertical">
                <views:BorderlessEntry x:Name="Pin2" Style="{StaticResource PinEntry}" TextColor="White">
                    <Entry.Behaviors>
                        <behaviors:EntryLengthValidatorBehavior MaxLength="1"/>
                    </Entry.Behaviors>
                </views:BorderlessEntry>
                <BoxView Style="{StaticResource BottomBar}"/>
            </StackLayout>
            <StackLayout Orientation="Vertical">
                <views:BorderlessEntry x:Name="Pin3" Style="{StaticResource PinEntry}" TextColor="White">
                    <Entry.Behaviors>
                        <behaviors:EntryLengthValidatorBehavior MaxLength="1"/>
                    </Entry.Behaviors>
                </views:BorderlessEntry>
                <BoxView Style="{StaticResource BottomBar}"/>
            </StackLayout>
            <StackLayout Orientation="Vertical">
                <views:BorderlessEntry x:Name="Pin4" Style="{StaticResource PinEntry}" TextColor="White">
                    <Entry.Behaviors>
                        <behaviors:EntryLengthValidatorBehavior MaxLength="1"/>
                    </Entry.Behaviors>
                </views:BorderlessEntry>
                <BoxView Style="{StaticResource BottomBar}"/>
            </StackLayout>
        </StackLayout>
  </ContentView.Content>
</ContentView>

Lastly, we add some behaviors to our code-behind:

PinView.xaml.cs

 public partial class PinView : ContentView
    {
        public static BindableProperty PinProperty = BindableProperty.Create("Pin", typeof(string), typeof(PinView), defaultBindingMode: BindingMode.OneWayToSource);
        public string Pin
        {
            get
            {
                return (string)GetValue(PinProperty);
            }
            set
            {
                SetValue(PinProperty, value);
            }
        }
        public PinView()
        {
            InitializeComponent();
            Pin = string.Empty;
            Pin1.TextChanged += Pin1_TextChanged;
            Pin2.TextChanged += Pin2_TextChanged;
            Pin3.TextChanged += Pin3_TextChanged;
            Pin4.TextChanged += Pin4_TextChanged;
        }

        private void Pin4_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (Pin4.Text.Length > 0)
                Pin4.Unfocus();
            else
                Pin3.Focus();
            Pin = Pin1.Text + Pin2.Text + Pin3.Text + Pin4.Text;
        }

        private void Pin3_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (Pin3.Text.Length > 0)
                Pin4.Focus();
            else
                Pin2.Focus();
            Pin = Pin1.Text + Pin2.Text + Pin3.Text + Pin4.Text;
        }

        private void Pin2_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (Pin2.Text.Length > 0)
                Pin3.Focus();
            else
                Pin1.Focus();
            Pin = Pin1.Text + Pin2.Text + Pin3.Text + Pin4.Text;
        }

        private void Pin1_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (Pin1.Text.Length > 0)
                Pin2.Focus();
            Pin = Pin1.Text + Pin2.Text + Pin3.Text + Pin4.Text;
        }
    }

If we dig into the behavior, we set up our TextChanged events so that when each separate Entry is updated, we move the focus to the next Entry. If we clear the text of one of the Entries, we move to the Entry before it, and when we write text into the Entry, we move to the next one.

We also use the Behavior mentioned to restrict the user from entering more than 1 character in each Entry!

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!

Xamarin.Tips – Restrict the Length of Your Entry Text

Here’s a quick one on how to restrict the number of characters a user can enter in an Entry. Basically, we are going to create a custom Behavior and then apply it to our Entry.

EntryLengthValidatorBehavior.cs

 /// <summary>
    /// Behavior that restricts the length of an entry
    /// </summary>
    public class EntryLengthValidatorBehavior : Behavior<Entry>
    {
        public int MaxLength { get; set; }

        protected override void OnAttachedTo(Entry bindable)
        {
            base.OnAttachedTo(bindable);
            bindable.TextChanged += OnEntryTextChanged;
        }

        protected override void OnDetachingFrom(Entry bindable)
        {
            base.OnDetachingFrom(bindable);
            bindable.TextChanged -= OnEntryTextChanged;
        }

        void OnEntryTextChanged(object sender, TextChangedEventArgs e)
        {
            var entry = (Entry)sender;

            if (entry.Text.Length > this.MaxLength)
            {
                string entryText = entry.Text;
                entry.TextChanged -= OnEntryTextChanged;
                entry.Text = e.OldTextValue;
                entry.TextChanged += OnEntryTextChanged;
            }
        }
    }

Now we can apply it in our Xaml:

<Entry x:Name="Pin1" TextColor="White">
    <Entry.Behaviors>
        <behaviors:EntryLengthValidatorBehavior MaxLength="4"/>
    </Entry.Behaviors>
</Entry>

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!

Xamarin.University – Upcoming Guest Lecture on Cross-Platform WebRTC

Join me on Tuesday, May 23rd and learn about bringing voice and video conferencing capabilities to your mobile apps! We will talk about the general capabilities of WebRTC, how to bring it to Native mobile development, using it in Xamarin, and extending the capabilities of it.

Leave with an app that lets you share your voice and videos with others!

Bring your devices and your questions!

Here’s a link to the guest lecture! https://university.xamarin.com/guestlectures/cross-platform-webrtc

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!

Xamarin.Tips – Create Your Own Star Wars Intro Text!

Here’s a fun one – let’s make a Xamarin.Forms page that looks like the Star Wars intro scrolling text! I also put the source up here: https://github.com/SuavePirate/StarWarsPage

Here’s the XAML for the page:

StarWarsPage.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"              xmlns:local="clr-namespace:StarWarsPage"              x:Class="StarWarsPage.MainPage">
    <Grid>
        <Image Source="starwarsintrobg.jpg" Aspect="AspectFill" HorizontalOptions="Fill" VerticalOptions="Fill"/>
        <ScrollView x:Name="TextScrollView" Orientation="Vertical" RotationX="24" Padding="16,800">
            <Label x:Name="Text" Text="{StaticResource StarWarsText}" TextColor="Yellow" FontAttributes="Bold" FontSize="30" HorizontalOptions="Fill"/>
        </ScrollView>
    </Grid>
    <ContentPage.Resources>
        <ResourceDictionary>
            <x:String x:Key="StarWarsText">
                It is a period of civil war.
Rebel spaceships, striking
from a hidden base, have won
their first victory against
the evil Galactic Empire.

During the battle, Rebel
spies managed to steal secret
plans to the Empire's
ultimate weapon, the DEATH
STAR, an armored space
station with enough power
to destroy an entire planet.

Pursued by the Empire's
sinister agents, Princess
Leia races home aboard her
starship, custodian of the
stolen plans that can save her
people and restore
freedom to the galaxy....

            </x:String>
        </ResourceDictionary>
    </ContentPage.Resources>
</ContentPage>

The important part here is the RotationX value on the ScrollView. This is going to set the backwards tilt of the scroll. To break down the other parts that make this up – We have a static String resource to use as the text for the intro. In this case I’m using the crawl text from A New Hope. We also wrap the whole thing in a Grid so that we can set up the background Image element.

Now we get a cool view that the user can scroll through at their own reading pace!

StarWars

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!

Xamarin.Tips – Changing a TableView’s Separator Color

In previous posts, I talked about the misfortunes of the Xamarin.Forms TableView. In this post, we will look at changing the separator color with a custom renderer.

For some other Xamarin.Tips on upgrading your TableView, check out these two posts on customizing the Section Titles!

Android: Xamarin.Tips – Xamarin.Forms Android Custom TableView Section Titles
iOS: Xamarin.Tips – Xamarin.Forms iOS Custom TableView Section Titles

Let’s get started with a custom view that inherits the TableView and adds a BindableProperty for our SeparatorColor:

ColoredTableView.cs

    public partial class ColoredTableView : TableView
    {
        public static BindableProperty SeparatorColorProperty = BindableProperty.Create("SeparatorColor", typeof(Color), typeof(ColoredTableView), Color.White);
        public Color SeparatorColor
        {
            get
            {
                return (Color)GetValue(SeparatorColorProperty);
            }
            set
            {
                SetValue(SeparatorColorProperty, value);
            }
        }
        public ColoredTableView()
        {
            InitializeComponent();
        }
    }

Now let’s take this view and create our renderers!

Let’s start with Android:

ColoredTableViewRenderer.cs

[assembly: ExportRenderer(typeof(ColoredTableView), typeof(ColoredTableViewRenderer))]
namespace YOUR_ANDROID_NAMESPACE
{
    public class ColoredTableViewRenderer : TableViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
        {
            base.OnElementChanged(e);
            if (Control == null)
                return;

            var listView = Control as Android.Widget.ListView;
            var coloredTableView = (ColoredTableView)Element;
            listView.Divider = new ColorDrawable(coloredTableView.SeparatorColor.ToAndroid());
            listView.DividerHeight = 3;
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if(e.PropertyName == "SeparatorColor")
            {
                var listView = Control as Android.Widget.ListView;
                var coloredTableView = (ColoredTableView)Element;
                listView.Divider = new ColorDrawable(coloredTableView.SeparatorColor.ToAndroid());
            }
        }
    }
}

Basically, we take the color and apply it as a ColorDrawable, including updating when the property changes from our Forms code.

Now let’s write up our iOS renderer:

ColoredTableViewRenderer.cs

[assembly: ExportRenderer(typeof(ColoredTableView), typeof(ColoredTableViewRenderer))]
namespace YOUR_IOS_NAMESPACE
{
    public class ColoredTableViewRenderer : TableViewRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
        {
            base.OnElementChanged(e);
            if (Control == null)
                return;

            var tableView = Control as UITableView;
            var coloredTableView = Element as ColoredTableView;
            tableView.SeparatorColor = coloredTableView.SeparatorColor.ToUIColor();
        }

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            if(e.PropertyName == "SeparatorColor")
            {
                var tableView = Control as UITableView;
                var coloredTableView = Element as ColoredTableView;

                tableView.SeparatorColor = coloredTableView.SeparatorColor.ToUIColor();
            }
        }
    }
}

Similarly to our Android implementation, we set the SeparatorColor of our Native UITableView on set up and when the value changes.

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!

Xamarin.Tips – Android Shadows on Transparent Views

I talked in a previous post about creating box-shadows on iOS UIViews that are transparent that you can find here: Xamarin.Tips – iOS Shadow on Transparent UIView

This is the Android counterpart! Let’s look at putting some shadows around a view with a fully transparent background. Android presented some completely different challenges compared to iOS. In the iOS solution, we ran into issues with Shadow layers requiring something in front of it to draw the drop shadow. With Android, using a CardView from the design support library gives us access to the Elevation and CardElevation properties which CAN indeed give us shadows around our view even though it is transparent. However, that kills any border on the view. Remember that we are looking for a transparent view, with a solid border, and shadows around it.

The general approach for iOS was to draw two new layers with shadows around the right and bottom sides of the UIView. Using layers allowed us to draw outside the actual frame of the view. However, with iOS, we aren’t so lucky.

So here’s the idea:
1. Adjust the margin and padding to allow us to expand the frame of the view
2. Draw a shadow gradient on the right side
3. Draw a shadow gradient on the bottom side
4. Draw a border around what our inner frame was

Here’s what that looks like in a custom renderer (since the origin of this was a custom Xamarin.Forms view):

TransparentFrameRenderer.cs

[assembly: ExportRenderer(typeof(TransparentFrame), typeof(TransparentFrameRenderer))]
namespace YOUR_ANDROID_NAMESPACE
{
    public class TransparentFrameRenderer : Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
        {
            base.OnElementChanged(e);
            if (e.NewElement != null)
            {
                Element.Margin = new Thickness(-5);
                Element.Padding = new Thickness(15);
                Control.CardElevation = 0;
                Control.PreventCornerOverlap = false;
            }
        }

        protected override void OnDraw(Canvas canvas)
        {
            base.OnDraw(canvas);

            // draw border
            var strokePaint = new Paint();
            strokePaint.SetARGB(255, 255, 255, 255);
            strokePaint.SetStyle(Paint.Style.Stroke);
            strokePaint.StrokeWidth = 2;
            var rect = canvas.ClipBounds;
            var outline = new Rect(15, 15, rect.Right - 15, rect.Bottom - 15);

            var bottomPaint = new Paint();
            bottomPaint.SetStyle(Paint.Style.Fill);
            var shadowShader = new LinearGradient(0, 0, 0, 30, new Android.Graphics.Color(0, 0, 0, 100), new Android.Graphics.Color(0, 0, 0, 0), Shader.TileMode.Mirror);
            bottomPaint.SetShader(shadowShader);

            var rightPaint = new Paint();
            rightPaint.SetStyle(Paint.Style.Fill);
            var rightShader = new LinearGradient(0, 0, 20, 0, new Android.Graphics.Color(0, 0, 0, 100), new Android.Graphics.Color(0, 0, 0, 0), Shader.TileMode.Mirror);
            rightPaint.SetShader(rightShader);

            // draw the bottom and right shadows
            canvas.DrawRect(12, rect.Bottom - 12, rect.Right - 12, rect.Bottom + 3, bottomPaint);
            canvas.DrawRect(rect.Right - 12, 14, rect.Right + 3, rect.Bottom, rightPaint);
            canvas.DrawRect(outline, strokePaint);
        }
    }
}

Look at the Draw override. We are setting up 3 different paints. The first is the Paint for our white border. bottomPaint sets up for our bottom shadow using a LinearGradient as the shader, and similarly, rightPaint sets up our shadow for the right side.

Finally, we call DrawRect for both our shadows, then our border. The values in the DrawRect calls for the shadows are just examples to play with padding and size values. You can play with those as you see fit to make your view look best.

I’ve tested this in some large ListViews to test against the classic Android overdraw issue that can happen, but because it consists of 3 pretty simple strokes, I haven’t run into any performance issues. Hopefully this solution works for you!

This is NOT a complete solution, or a real reusable control, but more of a general approach to rendering shadows around a control with a transparent background.

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!

Xamarin.Tips – Xamarin.Forms Android Custom TableView Section Titles

Xamarin.Forms’ TableView is a really useful control for displaying static content. However, it has some shortfalls. The one we will look at today is customizing the Section Title.

If you are looking for this solution on iOS, check it out here: Xamarin.Tips – Xamarin.Forms iOS Custom TableView Section Titles

Before reading ahead, I would suggest looking at your solution and assessing whether or not your TableView could be easily replaced by a ListView. I want to point this out, because ListViews have more flexibility for GroupHeader and allow you to put any control in there without having to leave your happy place that is XAML.

If that isn’t the case, keep reading; Let’s look at some simple custom renderers that can solve this problem for Android specifically.

First, let’s create a custom view that inherits TableView:

ColoredTableView.cs

 public partial class ColoredTableView : TableView
    {
        public static BindableProperty GroupHeaderColorProperty = BindableProperty.Create("GroupHeaderColor", typeof(Color), typeof(ColoredTableView), Color.White);
        public Color GroupHeaderColor
        {
            get
            {
                return (Color)GetValue(GroupHeaderColorProperty);
            }
            set
            {
                SetValue(GroupHeaderColorProperty, value);
            }
        }

        public ColoredTableView()
        {
            InitializeComponent();
        }
    }

Now we can create our Android renderer:

ColoredTableViewRenderer

[assembly: ExportRenderer(typeof(ColoredTableView), typeof(ColoredTableViewRenderer))]
namespace YOUR_ANDROID_NAMESPACE
{
    public class ColoredTableViewRenderer : TableViewRenderer
    {
       
        protected override TableViewModelRenderer GetModelRenderer(Android.Widget.ListView listView, TableView view)
        {
            return new CustomHeaderTableViewModelRenderer(Context, listView, view);
        }

        private class CustomHeaderTableViewModelRenderer : TableViewModelRenderer
        {
            private readonly ColoredTableView _coloredTableView;

            public CustomHeaderTableViewModelRenderer(Context context, Android.Widget.ListView listView, TableView view) : base(context, listView, view)
            {
                _coloredTableView = view as ColoredTableView;
            }

            public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
            {
                var view = base.GetView(position, convertView, parent);

                var element = GetCellForPosition(position);

                // section header will be a TextCell
                if (element.GetType() == typeof(TextCell))
                {
                    try
                    {
                        // Get the textView of the actual layout
                        var textView = ((((view as LinearLayout).GetChildAt(0) as LinearLayout).GetChildAt(1) as LinearLayout).GetChildAt(0) as TextView);

                        // get the divider below the header
                        var divider = (view as LinearLayout).GetChildAt(1);

                        // Set the color
                        textView.SetTextColor(_coloredTableView.GroupHeaderColor.ToAndroid());
                        textView.TextAlignment = Android.Views.TextAlignment.Center;
                        textView.Gravity = GravityFlags.CenterHorizontal;
                        divider.SetBackgroundColor(_coloredTableView.GroupHeaderColor.ToAndroid());
                    }
                    catch (Exception) { }
                }

                return view;
            }
        }
    }
}

Similar to the iOS solution, the important part is our override of GetView. What we are doing here is getting a reference to the inner child native views of our section header (why we check against the type and TextCell). Then we get our actual android TextView and the LinearLayout which is the divider below the header. From there we can change the properties of the views however we prefer. The default behavior of these views are to take the AccentColor from the android styles and use them as the BackgroundColor of the divider and the TextColor of the TextView.

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!

Xamarin.Controls – BadgeView

Who knew it could be so difficult to just put a number on a circle in Xamarin.Forms? Here’s a freebie to make your life easier in creating your own badges.

Before we dive in, I want to note that with the Xamarin.Forms.Themes, this can be easier. Check out the docs here: https://developer.xamarin.com/guides/xamarin-forms/user-interface/themes/. Basically, they added a StyleClass for BoxView that allows you to render it as a circle (although I’ve had problems with it in the past). This example is going to be avoiding the Themes package with a custom rolled implementation.

To start, we are going to create a custom CircleView. That CircleView is going to inherit from BoxView and use a custom renderer to give us the rounded edges we want. After that, we are going to make a reusable view called BadgeView that will essentially just slap a Label on top of our new CircleView.

Start here, with your CircleView in your PCL or Shared Library:

CircleView.cs

    public partial class CircleView : BoxView
    {
        public static readonly BindableProperty CornerRadiusProperty = BindableProperty.Create(nameof(CornerRadius), typeof(double), typeof(CircleView), 0.0);

        public double CornerRadius
        {
            get { return (double)GetValue(CornerRadiusProperty); }
            set { SetValue(CornerRadiusProperty, value); }
        }

        public CircleView()
        {
            InitializeComponent();
        }
    }

Now let’s create our custom renderers. I want to note, that for iOS it is much simpler, and could also be done as an Effect rather than a BoxRenderer, however, in order to be consistent with the more complicated Android implementation, we are doing both as renderers.

First, and easiest – iOS:

CircleViewRenderer.cs


[assembly: ExportRenderer(typeof(CircleView), typeof(CircleViewRenderer))]
namespace YourNamespace.iOS
{
    public class CircleViewRenderer : BoxRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);

            if (Element == null)
                return;

            Layer.MasksToBounds = true;
            Layer.CornerRadius = (float)((CircleView)Element).CornerRadius / 2.0f;
        }

    }
}

and of course Android:

CircleViewRenderer.cs


[assembly: ExportRenderer(typeof(CircleView), typeof(CircleViewRenderer))]
namespace YouNamespace.Droid
{
    public class CircleViewRenderer : BoxRenderer
    {
        private float _cornerRadius;
        private RectF _bounds;
        private Path _path;
        protected override void OnElementChanged(ElementChangedEventArgs<BoxView> e)
        {
            base.OnElementChanged(e);

            if (Element == null)
            {
                return;
            }
            var element = (CircleView)Element;

            _cornerRadius = TypedValue.ApplyDimension(ComplexUnitType.Dip, (float)element.CornerRadius, Context.Resources.DisplayMetrics);

        }

        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            base.OnSizeChanged(w, h, oldw, oldh);
            if (w != oldw && h != oldh)
            {
                _bounds = new RectF(0, 0, w, h);
            }

            _path = new Path();
            _path.Reset();
            _path.AddRoundRect(_bounds, _cornerRadius, _cornerRadius, Path.Direction.Cw);
            _path.Close();
        }

        public override void Draw(Canvas canvas)
        {
            canvas.Save();
            canvas.ClipPath(_path);
            base.Draw(canvas);
            canvas.Restore();
        }
    }
}

Cool. Now we can draw pretty circles in our Xamarin.Forms views:

<views:CircleView CornerRadius="16" WidthRequest="16" HeightRequest="16"/>

Now let’s apply that to a reusable BadgeView.

BadgeView.xaml

<Grid      xmlns="http://xamarin.com/schemas/2014/forms"      xmlns:local="clr-namespace:your_local_namespace"     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"      x:Class="your_local_namespace.BadgeView"     HeightRequest="16"     WidthRequest="16">
    <local:CircleView x:Name="BadgeCircle" HeightRequest="16" WidthRequest="16" CornerRadius="16" VerticalOptions="Center" HorizontalOptions="Center" />
    <Label x:Name="BadgeLabel" TextColor="White" VerticalOptions="Center" HorizontalOptions="Center" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" FontSize="10"/>
</Grid>

BadgeView.xaml.cs

 public partial class BadgeView : Grid
    {
        public static BindableProperty TextProperty = BindableProperty.Create("Text", typeof(string), typeof(BadgeView), "0", propertyChanged: (bindable, oldVal, newVal) =>
        {
            var view = (BadgeView)bindable;
            view.BadgeLabel.Text = (string)newVal;
        });

        public static BindableProperty BadgeColorProperty = BindableProperty.Create("BadgeColor", typeof(Color), typeof(BadgeView), Color.Blue, propertyChanged: (bindable, oldVal, newVal) =>
        {
            var view = (BadgeView)bindable;
            view.BadgeCircle.BackgroundColor = (Color)newVal;
        });

        public string Text
        {
            get
            {
                return (string)GetValue(TextProperty);
            }
            set
            {
                SetValue(TextProperty, value);
            }
        }
        public Color BadgeColor
        {
            get
            {
                return (Color)GetValue(BadgeColorProperty);
            }
            set
            {
                SetValue(BadgeColorProperty, value);
            }
        }
        public BadgeView()
        {
            InitializeComponent();
            BadgeLabel.Text = Text;
            BadgeCircle.BackgroundColor = BadgeColor;
        }
    }

This is obviously a super simple example, but you can always add any other properties you want such as handling changing sizes, corners, shapes, colors, etc.

But now we can see our final results when using our control:

 <Grid>
    <Label HorizontalTextAlignment="Center" Text="Look at me!"/>
    <views:BadgeView Text="3" BadgeColor="Green" VerticalOptions="Start" HorizontalOptions="End"/>
</Grid>

BadgeExample

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!

Xamarin.Forms Borderless Entry

Here’s a quick freebee. If you want to create an entry that has no border, it can be done with a pretty simple custom renderer.

Let’s first create a new control that inherits Entry:

BorderlessEntry.cs

 public partial class BorderlessEntry : Entry
    {
        public BorderlessEntry()
        {
            InitializeComponent();
        }
    }

We aren’t doing any special logic or anything here since all we need to do is remove the border.

Now let’s create our renderer on Android:

BorderlessEntryRenderer.cs


[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace YOUR_NAMESPACE
{
    public class BorderlessEntryRenderer : EntryRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);
            if (e.OldElement == null)
            {
                Control.Background = null;
            }
        }
    }
}

Note that setting the background drawable to null will kill the border (including the bottom line in AppCompt).

Now let’s create our renderer on iOS:

BorderlessEntryRenderer.cs

[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace YOUR_NAMESPACE
{
    public class BorderlessEntryRenderer : EntryRenderer
    {
        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);
            
            Control.Layer.BorderWidth = 0;
            Control.BorderStyle = UITextBorderStyle.None;
        }
    }
}

The main property here is the BorderStyle.

Lastly, UWP (this should also be the same thing for WP and Win8):

BorderlessEntryRenderer.cs

[assembly: ExportRenderer(typeof(BorderlessEntry), typeof(BorderlessEntryRenderer))]
namespace YOUR_NAMESPACE
{
    public class BorderlessEntryRenderer : EntryRenderer
    {
        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
            {
                Control.BorderThickness = new Thickness(0);
            }
        }
    }
}

That’s all there is to it!

Xamarin.Controls – Creating Your Own Android Markdown TextView

In a previous post, I talked about a Xamarin.Forms control I put on GitHub to allow you to display Markdown strings properly. It worked by parsing the markdown into html, then using the custom renderer to display the html string in a TextView for Android and a UILabel for iOS.

However, we are not always using Xamarin.Forms, so let’s take a look at achieving the same functionality with just Xamarin.Android.

We’ll break it down into a few parts:

  1. Parse a markdown string into an html string
  2. Parse the html string into an ICharSequence
  3. Create an extra tag handler to help show html elements that are not traditionally supported in a TextView
  4. Set the TextFormatted of the TextView

Parsing Markdown

This is traditionally the most difficult part. However, our community is awesome and open sourced a Markdown processor with an MIT license (so use it freely!).
I won’t put the actual code in here because it is overwhelmingly long, but here is a link to it:

https://github.com/SuavePirate/MarkdownTextView/blob/master/src/Forms/SPControls.MarkdownTextView/SPControls.MarkdownTextView/Markdown.cs

Note that this is portable, so you can use it in a PCL without a problem and share it between your platforms.

Now that we have our means of processing the Markdown, let’s create some extension methods to make it easier to parse and do some extra processing like cleaning up our tags, line breaks, etc.


 #region MARKDOWN STYLES
private const string ORIGINAL_PATTERN_BEGIN = "<code>";
private const string ORIGINAL_PATTERN_END = "</code>";
private const string PARSED_PATTERN_BEGIN = "<font color=\"#888888\" face=\"monospace\"><tt>";
private const string PARSED_PATTERN_END = "</tt></font>";

#endregion

public static string ToHtml(this string markdownText)
{
    var markdownOptions = new MarkdownOptions
    {
        AutoHyperlink = true,
        AutoNewlines = false,
        EncodeProblemUrlCharacters = false,
        LinkEmails = true,
        StrictBoldItalic = true
    };
    var markdown = new Markdown(markdownOptions);
    var htmlContent = markdown.Transform(markdownText);
    var regex = new Regex("\n");
    htmlContent = regex.Replace(htmlContent, "<br/>");

    var html = htmlContent.HtmlWrapped();
    var regex2 = new Regex("\r");
    html = regex.Replace(html, string.Empty);
    html = regex2.Replace(html, string.Empty);
    return html;
}

///
<summary>
/// Wrap html with a full html tag
/// </summary>

/// <param name="html"></param>
/// <returns></returns>
public static string HtmlWrapped(this string html)
{
    if (!html.StartsWith("<html>") || !html.EndsWith("</html>"))
    {
        html = $"<html><body>{html}</body></html>";
    }
    return html;
}

///<summary>
/// Parses html with code or pre tags and gives them proper
/// styled spans so that Android can parse it properly
/// </summary>

/// <param name="htmlText">The html string</param>
/// <returns>The html string with parsed code tags</returns>
public static string ParseCodeTags(this string htmlText)
{
    if (htmlText.IndexOf(ORIGINAL_PATTERN_BEGIN) < 0) return htmlText;
    var regex = new Regex(ORIGINAL_PATTERN_BEGIN);
    var regex2 = new Regex(ORIGINAL_PATTERN_END);

    htmlText = regex.Replace(htmlText, PARSED_PATTERN_BEGIN);
    htmlText = regex2.Replace(htmlText, PARSED_PATTERN_END);
    htmlText = htmlText.TrimLines();
    return htmlText;
}

public static bool EqualsIgnoreCase(this string text, string text2)
{
    return text.Equals(text2, StringComparison.CurrentCultureIgnoreCase);
}

public static string ReplaceBreaks(this string html)
{
    var regex = new Regex("<br/>");
    html = regex.Replace(html, "\n");
    return html;
}

public static string ReplaceBreaksWithSpace(this string html)
{
    var regex = new Regex("<br/>");
    html = regex.Replace(html, " ");
    return html;
}

public static string TrimLines(this string originalString)
{
    originalString = originalString.Trim('\n');
    return originalString;
}


Now we can properly parse markdown to html:


var markdown = "# Hello *World*";
var html = markdown.ToHtml();
// html = "<h1>Hello <strong>World</strong></h1>"

Parsing Html to ICharSequence

The next step is to take our processed html string and turn it into something that an Android TextView can use.

How about another extension method?


public static ICharSequence ToFormattedHtml(this string htmlText)
{
    try
    {
        ICharSequence html;
        if(Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.N)
        {
            html = Html.FromHtml(htmlText.ParseCodeTags(), FromHtmlOptions.ModeLegacy, null, new HtmlTagHandler()) as ICharSequence;
        }
        else
        {
            // handle legacy builds
            html = Html.FromHtml(htmlText.ParseCodeTags(), null, new HtmlTagHandler()) as ICharSequence;
        }
        // this is required to get rid of the end two "\n" that android adds with Html.FromHtml
        // see: http://stackoverflow.com/questions/16585557/extra-padding-on-textview-with-html-contents for example
        while (html.CharAt(html.Length() - 1) == '\n')
        {
            html = html.SubSequenceFormatted(0, html.Length() - 1);
        }
        return html;
    }
    catch
    {
        return null;
    }
}

To breakdown what this is doing: We check against the sdk version we are running against to ensure we don’t use an obsolete api, then call to get the Html as an ICharSequence. Then, we clean up the output and return it.

We’ll add one more extension method just to make the full conversion from markdown to html to formatted html:

public static ICharSequence MarkdownToHtml(this string markdown)
{
    return markdown.ToHtml().ToFormattedHtml();
}

There’s one piece here that is unique, and that is the HtmlTagHandler() used in the call to Html.FromHtml.

Extending the Tag Handler

Android, unfortunately, doesn’t support many html elements in its TextFormatted. Daniel Lew has a great post about the supported types.

In order to get around this, we need to use tags that are supported and combine those to simulate the missing tags; especially for things like ul and ol.

I’ve translated a popular processor from Java to C# so we can use it in Xamarin: HtmlTagHandler.cs

/// <summary>
/// Custom tag handler for parsing more html tags for android textviews
/// This is a port/translation of https://github.com/sufficientlysecure/html-textview/blob/master/HtmlTextView/src/main/java/org/sufficientlysecure/htmltextview/HtmlTagHandler.java
/// Which is considered the most robust handler
/// </summary>
public class HtmlTagHandler : Java.Lang.Object, Android.Text.Html.ITagHandler
{
    /**
     * Keeps track of lists (ol, ul). On bottom of Stack is the outermost list
     * and on top of Stack is the most nested list
     */
    Stack<String> lists = new Stack<String>();
    /**
     * Tracks indexes of ordered lists so that after a nested list ends
     * we can continue with correct index of outer list
     */
    Stack<Int32> olNextIndex = new Stack<Int32>();
    /**
     * List indentation in pixels. Nested lists use multiple of this.
     */
    private static int indent = 10;
    private static int listItemIndent = indent * 2;
    private static BulletSpan bullet = new BulletSpan(indent);
    private class Ul : Java.Lang.Object
    {
    }
    private class Ol : Java.Lang.Object
    {
    }

    private class Code : Java.Lang.Object
    {
    }
    private class Center : Java.Lang.Object
    {
    }

    private class Strike : Java.Lang.Object
    {
    }


    public void HandleTag(Boolean opening, String tag, Android.Text.IEditable output, IXMLReader xmlReader)
    {
        if (opening)
        {
            if (tag.ToLower() == "ul")
            {
                lists.Push(tag);
            }
            else if (tag.EqualsIgnoreCase("ol"))
            {
                lists.Push(tag);
                olNextIndex.Push(1);
            }
            else if (tag.EqualsIgnoreCase("li"))
            {
                if (output.Length() > 0 && output.CharAt(output.Length() - 1) != '\n')
                {
                    output.Append("\n");
                }
                String parentList = lists.Peek();
                if (parentList.EqualsIgnoreCase("ol"))
                {
                    Start(output, new Ol());
                     output.Append(olNextIndex.Peek().ToString()).Append('.').Append(' ');
                    olNextIndex.Push(olNextIndex.Pop() + 1);
                }
                else if (parentList.EqualsIgnoreCase("ul"))
                {
                    Start(output, new Ul());
                }
            }
            else if (tag.EqualsIgnoreCase("code"))
            {
                Start(output, new Code());
            }
            else if (tag.EqualsIgnoreCase("center"))
            {
                Start(output, new Center());
            }
            else if (tag.EqualsIgnoreCase("s") || tag.EqualsIgnoreCase("strike"))
            {
                Start(output, new Strike());
            }
        }
        else
        {
            if (tag.EqualsIgnoreCase("ul"))
            {
                lists.Pop();
            }
            else if (tag.EqualsIgnoreCase("ol"))
            {
                lists.Pop();
                olNextIndex.Pop();
            }
            else if (tag.EqualsIgnoreCase("li"))
            {
                if (lists.Peek().EqualsIgnoreCase("ul"))
                {
                    if (output.Length() > 0 && output.CharAt(output.Length() - 1) != '\n')
                    {
                        output.Append("\n");
                    }
                    // Nested BulletSpans increases distance between bullet and Text, so we must prevent it.
                    int bulletMargin = indent;
                    if (lists.Count > 1)
                    {
                        bulletMargin = indent - bullet.GetLeadingMargin(true);
                        if (lists.Count > 2)
                        {
                            // This get's more complicated when we add a LeadingMarginSpan into the same line:
                            // we have also counter it's effect to BulletSpan
                            bulletMargin -= (lists.Count - 2) * listItemIndent;
                        }
                    }
                    BulletSpan newBullet = new BulletSpan(bulletMargin);
                    End(output, typeof(Ul), false,
                            new LeadingMarginSpanStandard(listItemIndent * (lists.Count - 1)),
                            newBullet);
                }
                else if (lists.Peek().EqualsIgnoreCase("ol"))
                {
                    if (output.Length() > 0 && output.CharAt(output.Length() - 1) != '\n')
                    {
                        output.Append("\n");
                    }
                    int numberMargin = listItemIndent * (lists.Count - 1);
                    if (lists.Count > 2)
                    {
                        // Same as in ordered lists: counter the effect of nested Spans
                        numberMargin -= (lists.Count - 2) * listItemIndent;
                    }
                    End(output, typeof(Ol), false, new LeadingMarginSpanStandard(numberMargin));
               }
            }
            else if (tag.EqualsIgnoreCase("code"))
            {
                End(output, typeof(Code), false, new TypefaceSpan("monospace"));
            }
            else if (tag.EqualsIgnoreCase("center"))
            {
                End(output, typeof(Center), true, new AlignmentSpanStandard(Layout.Alignment.AlignCenter));
            }
            else if (tag.EqualsIgnoreCase("s") || tag.EqualsIgnoreCase("strike"))
            {
                End(output, typeof(Strike), false, new StrikethroughSpan());
            }
        }
    }

    /**
     * Mark the opening tag by using private classes
     */
    private void Start(IEditable output, Java.Lang.Object mark)
    {
        int len = output.Length();
        output.SetSpan(mark, len, len, SpanTypes.MarkMark);
    }

    /**
     * Modified from {@link Android.Text.Html}
     */
    private void End(IEditable output, Type kind, Boolean paragraphStyle, params Java.Lang.Object[] replaces)
    {
        Java.Lang.Object obj = GetLast(output, kind);
        // start of the tag
        int where = output.GetSpanStart(obj);
        // end of the tag
        int len = output.Length();
        output.RemoveSpan(obj);

       if (where != len)
       {
            int thisLen = len;
            // paragraph styles like AlignmentSpan need to end with a new line!
            if (paragraphStyle)
            {
                output.Append("\n");
                thisLen++;
            }
            foreach (Java.Lang.Object replace in replaces)
            {
                output.SetSpan(replace, where, thisLen, SpanTypes.ExclusiveExclusive);
            }


       }
   }

    /**
     * Get last marked position of a specific tag kind (private class)
     */
    private static Java.Lang.Object GetLast(IEditable Text, Type kind)
    {
        Java.Lang.Object[] objs = Text.GetSpans(0, Text.Length(), Java.Lang.Class.FromType(kind)); // TODO: LOl will this work?
        if (objs.Length == 0)
        {
            return null;
        }
        else
        {
            for (int i = objs.Length; i > 0; i--)
            {
                if (Text.GetSpanFlags(objs[i - 1]) == SpanTypes.MarkMark)
                {
                    return objs[i - 1];
                }
            }
           return null;
        }
    }

}

So now we can properly display list elements as well as code elements!

Now for the very last bit: Show it!

Assiging To the TextView

Pretty simple now that we have our useful extension methods!

var markdown = "# Hello *World*";
var myTextView = FindViewById<TextView>(Resource.Id.MyTextView);
myTextView.TextFormatted = markdown.MarkdownToHtml();

That’s it! Now you can see some cool stylized text in your labels.