Back by popular demand, bringing more Material Design controls to you Xamarin.Forms app! This time we will look at implementing the standards in Material Design’s text fields by building a MaterialEntry
control. You can find the source code and example app here: https://github.com/SuavePirate/MaterialEntry but you can build your own by following this post.
Let’s build our Xamarin.Forms control to work the same on not only Android where Material Design is baked in, but also to run on iOS AND UWP. In the end, we should be able to use our floating label, set an accent color that expands on the label and underline when focused while being able to bind these properties through MVVM.
The first thing we need to do is create a BorderlessEntry
that removes the border from our entry on all 3 platforms. I’ve done this in a previous blog post here: Xamarin.Forms Borderless Entry, so we won’t be implementing it here. This code is also in the GitHub link above.
With our BorderlessEntry
we can now create our custom control WITHOUT ANY MORE CUSTOM RENDERERS!
Let’s set up the layout structure in our XAML file, and then wire up the animation logic in our code behind.
MaterialEntry.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" xmlns:local="clr-namespace:SuaveControls.MaterialEntry" x:Class="SuaveControls.MaterialEntry.MaterialEntry"> <ContentView.Content> <Grid ColumnSpacing="16" Margin="0,8"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="1"/> </Grid.RowDefinitions> <Label x:Name="HiddenLabel" FontSize="10" IsVisible="False" Margin="0"/> <local:BorderlessEntry x:Name="EntryField" Text="{Binding Text, Mode=TwoWay}" Margin="0,12,0,0"/> <BoxView x:Name="BottomBorder" BackgroundColor="Gray" Grid.Row="1" HeightRequest="1" Margin="0" HorizontalOptions="FillAndExpand"/> <BoxView x:Name="HiddenBottomBorder" BackgroundColor="Gray" Grid.Row="1" HeightRequest="1" Margin="0" WidthRequest="0" HorizontalOptions="Center"/> </Grid> </ContentView.Content> </ContentView>
We set up our BorderlessEntry
that will act as our formal point for entering text. We also add a label that is initially hidden and laid out on top of the BorderlessEntry
. This is the label we will be using to animate the floating action that Material Design uses based while we fade out the placeholder text. The last bit is two BoxViews
that act as the bottom line below the Entry
. One is the unfocused which has a standard gray color, while the other has a width of 0 and will have a background color of our selected AccentColor
. This will have an animated width expansion when the BorderlessEntry
is focused.
Now let’s look at the animation and bindings in the code behind:
MaterialEntry.xaml.cs
public partial class MaterialEntry : ContentView { public static void Init() { } public static BindableProperty TextProperty = BindableProperty.Create(nameof(Text), typeof(string), typeof(MaterialEntry), defaultBindingMode: BindingMode.TwoWay); public static BindableProperty PlaceholderProperty = BindableProperty.Create(nameof(Placeholder), typeof(string), typeof(MaterialEntry), defaultBindingMode: BindingMode.TwoWay, propertyChanged: (bindable, oldVal, newval) => { var matEntry = (MaterialEntry)bindable; matEntry.EntryField.Placeholder = (string)newval; matEntry.HiddenLabel.Text = (string)newval; }); public static BindableProperty IsPasswordProperty = BindableProperty.Create(nameof(IsPassword), typeof(bool), typeof(MaterialEntry), defaultValue: false, propertyChanged: (bindable, oldVal, newVal) => { var matEntry = (MaterialEntry)bindable; matEntry.EntryField.IsPassword = (bool)newVal; }); public static BindableProperty KeyboardProperty = BindableProperty.Create(nameof(Keyboard), typeof(Keyboard), typeof(MaterialEntry), defaultValue: Keyboard.Default, propertyChanged: (bindable, oldVal, newVal) => { var matEntry = (MaterialEntry)bindable; matEntry.EntryField.Keyboard = (Keyboard)newVal; }); public static BindableProperty AccentColorProperty = BindableProperty.Create(nameof(AccentColor), typeof(Color), typeof(MaterialEntry), defaultValue: Color.Accent); public Color AccentColor { get { return (Color)GetValue(AccentColorProperty); } set { SetValue(AccentColorProperty, value); } } public Keyboard Keyboard { get { return (Keyboard)GetValue(KeyboardProperty); } set { SetValue(KeyboardProperty, value); } } public bool IsPassword { get { return (bool)GetValue(IsPasswordProperty); } set { SetValue(IsPasswordProperty, value); } } public string Text { get { return (string)GetValue(TextProperty); } set { SetValue(TextProperty, value); } } public string Placeholder { get { return (string)GetValue(PlaceholderProperty); } set { SetValue(PlaceholderProperty, value); } } public MaterialEntry() { InitializeComponent(); EntryField.BindingContext = this; EntryField.Focused += async (s, a) => { HiddenBottomBorder.BackgroundColor = AccentColor; HiddenLabel.TextColor = AccentColor; HiddenLabel.IsVisible = true; if (string.IsNullOrEmpty(EntryField.Text)) { // animate both at the same time await Task.WhenAll( HiddenBottomBorder.LayoutTo(new Rectangle(BottomBorder.X, BottomBorder.Y, BottomBorder.Width, BottomBorder.Height), 200), HiddenLabel.FadeTo(1, 60), HiddenLabel.TranslateTo(HiddenLabel.TranslationX, EntryField.Y - EntryField.Height + 4, 200, Easing.BounceIn) ); EntryField.Placeholder = null; } else { await HiddenBottomBorder.LayoutTo(new Rectangle(BottomBorder.X, BottomBorder.Y, BottomBorder.Width, BottomBorder.Height), 200); } }; EntryField.Unfocused += async (s, a) => { HiddenLabel.TextColor = Color.Gray; if (string.IsNullOrEmpty(EntryField.Text)) { // animate both at the same time await Task.WhenAll( HiddenBottomBorder.LayoutTo(new Rectangle(BottomBorder.X, BottomBorder.Y, 0, BottomBorder.Height), 200), HiddenLabel.FadeTo(0, 180), HiddenLabel.TranslateTo(HiddenLabel.TranslationX, EntryField.Y, 200, Easing.BounceIn) ); EntryField.Placeholder = Placeholder; } else { await HiddenBottomBorder.LayoutTo(new Rectangle(BottomBorder.X, BottomBorder.Y, 0, BottomBorder.Height), 200); } }; } }
We first set up the BindableProperties
and public properties to enable the binding of the AccentColor
, Text
, Placeholder
, and Keyboard
. These BindableProperties
also handle their own PropertyChanged
events to update the view elements dynamically.
After that, we handle our constructor and wire up our Focused
and Unfocused
events on our BorderlessEntry
. In the Focused
event, we set the colors of the hidden bar, and the floating label to the accent color. We then start the animations of expanding the hidden bar, and the fade in and float up of the floating label.
On the Unfocused
event, we do the inverse of setting the floating label color back to the unfocused color, check if there is text, if there is not – float the label back down, and then animate the collapse of the colored bar.
With all these things together, we get a nicely animated text field that has a floating label and expanding bottom bar with a given accent color!
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.
Hi Alex. Works great, however, I am having trouble extending this entry to allowed for TextChange and Completed events. Can you please advise?
LikeLike
Hello! Alex, everything works great but I am facing some issue. On focus and unfocused event, app screen gets black for a second. Can you pls help
LikeLike
Hello Alex, All works great but facing some issue. During focused and unfocused event the screen got black for second. Can you help me?
LikeLike
On which platform?
LikeLike
Android with xamarin.form Version:2.5.0.91635 I guess in onfocus and unfocus event, await task.whenall is taking time to fade and translate in the label
LikeLike
It shouldn’t be though, it’s an animation on the uithread. It shouldn’t be causing blackouts. What devices is this happening on, because I’m not seeing it happen on any of mine
LikeLike
It is android emulator (Galaxy Nexus android 7.1-API25)
LikeLike
Hello Alex,
It works fine but i am facing some issues that i want to change the border color from gray to white. Please let me know
LikeLike
Hello Alex,
it works great but i am facing issues with the default border color and the colro of the lebel is gray after moving to the next entry. i want to change the color of the border to white or some other color.
Please Help
LikeLike
The latest code in my material form controls repository and what is up on Nuget allows for this https://github.com/SuavePirate/SuaveControls.MaterialFormControls
LikeLike
To clarify, use the DefaultColor property. These updates also include validation state / colors as well.
LikeLike
how to change the text color of material entry
LikeLike
You’ll need to set the text color of the underlying entry
LikeLike
Hello Alex,
Thank you for sharing this code with us however i have some questions
Is it possible to set a default value?
Is it possible to change the placeholder color?
LikeLike
Yes and in the next version once I find time to update the nuget
LikeLike
Thnak you for the fast reply .
LikeLike