When creating a mobile app, we want it to look great. Not just by design but also typography great. Xamarin.Forms already provides custom font mechanism. But I don't see it efficient enough because it has to be declared differently for each platform. Look at the example snippet below.
<Label Text="Hello Forms with XAML">
<Label.FontFamily>
<OnPlatform x:TypeArguments="x:String">
<OnPlatform.iOS>Oswald-Regular</OnPlatform.iOS>
<OnPlatform.Android>Oswald-Regular.ttf#Oswald-Regular</OnPlatform.Android>
<OnPlatform.WinPhone>Assets/Fonts/Oswald-Regular.ttf#Oswald</OnPlatform.WinPhone>
</OnPlatform>
</Label.FontFamily>
</Label>
See that how for each platform it's a different string
. On iOS, you just need to set font name. As for Android and WinPhone, you need to declare path to font file and font name. All I want to do is to just call the font name like iOS does and for every platform, it'll render correctly like the following snippet:
<Label Text="Hello Forms with XAML"
FontFamily="Oswald-Regular"></Label>
So in this tutorial, we're going to create a helper class for Android version of Xamarin.Forms based on how iOS custom font works. You can see that they already post a blog post on how to use custom font in Xamarin.iOS. You just need to add your font to the project, define it under info.plist
. We'll do something similar with custom font helper and custom renderers.
This tutorial doesn't cover WinPhone version because I'm not using Windows for development and more likely people just stick with iOS and Android. So I apologize, for WinPhone developers, you have to stick with Xamarin's official guide.
Creating Custom Font Helper
Create a new Xamarin.Forms solution and let's name it CustomFont
. Let's go to Android project and create a new helper class called FontManager
. To make this object accessible from another class, let's make it as a singleton class. We also need one dictionary
object to store these fonts.
private IDictionary<string, Typeface> _typefaces = null;
protected FontManager()
{
_typefaces = new Dictionary<string, Typeface>();
}
private static FontManager _current = null;
public static FontManager Current
{
get
{
return _current ?? (_current = new FontManager());
}
}
Then, we need to create a function to register font file to font name. This function will have two parameters: fontName
as the name of the font and fontPath
as the font file location. We're going to create a dictionary to store Typeface
objects with fontName
as the key. For those who don't know, Typeface
is Android class used to define a custom font. This function should look like the following snippet:
private FontManager RegisterTypeFace(string fontName, string fontPath)
{
Typeface newTypeface = null;
try
{
newTypeface = Typeface.CreateFromAsset(
Application.Context.Assets,
fontPath);
}
catch (Exception ex)
{
Console.WriteLine("Typeface service: " + ex);
newTypeface = Typeface.Default;
}
_typefaces.Add(fontName, newTypeface);
return this;
}
To make it simpler, we can also create a function that only uses fontPath
as parameter and automatically guesses the font name like the following snippet.
public FontManager RegisterTypeFace(string fontPath)
{
var fontName = System.IO.Path.GetFileNameWithoutExtension(fontPath);
Console.WriteLine("fontName: " + fontName);
return RegisterTypeFace(fontName, fontPath);
}
With those functions, we can register as many fonts as we need. Now we need a function to get this font to use it on our controls. This one is very simple because we just need to look it up on our dictionary
object.
public Typeface GetTypeface(string fontName)
{
if (!_typefaces.ContainsKey(fontName))
{
RegisterTypeFace(
fontName,
string.Format("Fonts/{0}.ttf", fontName));
}
return _typefaces[fontName];
}
Now that our FontManager
is ready to use, we need to modify default Xamarin.Forms controls using CustomRenderer
.
Creating Custom Renderers
For this tutorial, we'll only create three custom renderer of Xamarin.Forms controls. They are Label
, Entry
, and Button
. All of them will have similar implementation which is font replacement happened inside OnElementPropertyChanged
and OnElementChanged
events. Three code blocks below are implementations in order of Button
, Entry
, and Label
renderer.
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
namespace CustomFont.Droid.Renderers
{
public class CustomButtonRenderer: ButtonRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if (Control == null)
return;
ChangeFont();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null)
return;
if (Control == null)
return;
if (e.PropertyName == Button.FontFamilyProperty.PropertyName)
{
ChangeFont();
}
}
private void ChangeFont()
{
Control.TransformationMethod = (null);
var typeface = string.IsNullOrEmpty(Element.FontFamily) ?
Typeface.Default :
FontManager.Current.GetTypeface(Element.FontFamily);
Control.Typeface = typeface;
}
}
}
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
namespace CustomFont.Droid.Renderers
{
public class CustomEntryRenderer: EntryRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Element == null)
return;
if (Control == null)
return;
ChangeFont();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Element == null)
return;
if (Control == null)
return;
if (e.PropertyName == Entry.FontFamilyProperty.PropertyName)
{
ChangeFont();
}
}
private void ChangeFont()
{
Control.TransformationMethod = (null);
var typeface = string.IsNullOrEmpty(Element.FontFamily) ?
Typeface.Default :
FontManager.Current.GetTypeface(Element.FontFamily);
Control.Typeface = typeface;
}
}
}
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
namespace CustomFont.Droid.Renderers
{
public class CustomLabelRenderer: LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control == null)
return;
if (Element == null)
return;
ChangeFont();
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (Control == null)
return;
if (Element == null)
return;
if (e.PropertyName == Label.FontFamilyProperty.PropertyName)
{
ChangeFont();
}
}
private void ChangeFont()
{
var typeface = string.IsNullOrEmpty(Element.FontFamily) ?
Typeface.Default :
FontManager.Current.GetTypeface(Element.FontFamily);
Control.Typeface = typeface;
}
}
}
Now that our custom renderers are ready, it's time to use it in action.
See It in Action
Before we start using our new custom font helper, we need to download some fonts. You can get anything you want from Google Fonts Service. For this tutorial, I use two fonts: Oswald-Regular and Pangolin.
Add your fonts inside Droid/Assets folder. Then register these fonts in MainActivity.cs right above LoadApplication(new App());
with the following snippets:
FontManager.Current
.RegisterTypeFace("Fonts/Oswald/Oswald-Regular.ttf")
.RegisterTypeFace("Fonts/Pangolin/Pangolin-Regular.ttf");
After our fonts are registered, we can use it on our Xamarin.Forms control. We can change the font by changing FontFamily
property with the name of the font and it'll render perfectly on Android and iOS, without adding OnPlatform
selector. Here is an example UI I made with XAML:
="1.0"="utf-8"
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:CustomFont"
x:Class="CustomFont.CustomFontPage">
<StackLayout VerticalOptions="CenterAndExpand"
HorizontalOptions="Fill"
Padding="10,10,10,10">
<Label Text="Welcome to Xamarin Forms!"
FontFamily="Pangolin-Regular"
HorizontalOptions="Center" />
<Entry Text="Edit Me"
FontFamily="Pangolin-Regular"
HorizontalOptions="Fill"
HorizontalTextAlignment="Start"/>
<Button Text="Click Me"
FontFamily="Oswald-Regular"
HorizontalOptions="Center" />
</StackLayout>
</ContentPage>
When you run this on your Android device or emulator, it should look like the following image.
Xamarin.Forms Custom Font Android Result
When you run it on iOS, it'll show the same font as Android does. So in case next time you want to change your control's font, you can do it peacefully.
Summary
Once again, we know that Xamarin.Forms's API still isn't perfect for every problems. But with custom renderers, we can see how easily to add a feature we want. Thanks for reading this post.
You can download this complete solution on GitHub.
CodeProject