Simple Custom Fonts Helper in Xamarin.Forms for Android

With the help of Custom Renderer, a unified API can be created.

good read
With the help of Custom Renderer, a unified API can be created.

When creating a mobile app, we want it to look great. Not just by design but also typography great. Xamarin.Forms already provide 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 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 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.

Sorry, no WinPhone and UWP implementation

This tutorial doesn't cover WinPhone and UWP version because I'm not using Windows for development and more likely people just stick with iOS and Android. So I apologize for WinPhone and UWP developer you have to stick with Xamarin's official guide.

Update on code efficiency

Code snippets are updated by moving ChangeFont() functions from each renderers into centralized function in FontManager class.

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 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 use fontPath as parameter and automatically guess the font name like 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. Also, if we couldn't find any font name inside dictionary, we register it by assuming it is located under Fonts directory (or maybe you can change it into throw exception).

public Typeface GetTypeface(string fontName)
{
  if (!_typefaces.ContainsKey(fontName))
  {
    RegisterTypeFace(
      fontName,
      string.Format("Fonts/{0}.ttf", fontName));
  }

  return _typefaces[fontName];
}

Finally, we need to add function to change font for each controls. The 3 controls we're going to use in this tutorial are derived from Android.Widget.TextView class. We'll set the font of the control by changing its Typeface. If the font family is not defined (null or empty string), we'll set it to Typeface.Default, which is the default typeface defined by Android.

public void ChangeFont(Android.Widget.TextView control, string fontFamily)
{
  control.TransformationMethod = null;
  var typeface = string.IsNullOrEmpty(fontFamily) ?
    Typeface.Default :
    GetTypeface(fontFamily);
  control.Typeface = typeface;
}

Now that our FontManager is completed and 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. These code snippets below are implementation in order of Button, Entry, and Label renderer.

// Code for Button Custom 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 (Control == null || Element == null)
        return;
      
      FontManager.Current.ChangeFont(Control, Element.FontFamily);
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      base.OnElementPropertyChanged(sender, e);

      if (Control == null || Element == null)
        return;
      
      if (e.PropertyName == Button.FontFamilyProperty.PropertyName)
      {
        FontManager.Current.ChangeFont(Control, Element.FontFamily);
      }
    }
  }
}

// Code for Entry Custom Renderer
[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 (Control == null || Element == null)
        return;
      
      FontManager.Current.ChangeFont(Control, Element.FontFamily);
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      base.OnElementPropertyChanged(sender, e);

      if (Control == null || Element == null)
        return;
      
      if (e.PropertyName == Entry.FontFamilyProperty.PropertyName)
      {
        FontManager.Current.ChangeFont(Control, Element.FontFamily);
      }
    }
  }
}

// Code for Label Custom Renderer
[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 || Element == null)
        return;

      FontManager.Current.ChangeFont(Control, Element.FontFamily);
    }

    protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
      base.OnElementPropertyChanged(sender, e);

      if (Control == null || Element == null)
        return;
      
      if (e.PropertyName == Label.FontFamilyProperty.PropertyName)
      {
        FontManager.Current.ChangeFont(Control, Element.FontFamily);
      }
    }
  }
}

As you can see that all three custom renderers look similar. All we need to care is to ensure that Control and Element properties are not null, because we need them to change the native control's font. 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 this fonts in MainActivity.cs right above of LoadApplication(new App()); with 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 rendered perfectly on Android and iOS, without adding OnPlatform selector. Here is an example UI I made with 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: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 following image.

Xamarin Forms Android Custom Font
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.

References


Except as otherwise noted, the article of this page is licensed under a Creative Commons Attribution 4.0 International License, and code samples are licensed under the MIT License. For details, see our Site Policies.

Subscribe to Junian.Net

Get the latest posts delivered right to your inbox.

Delivered by FeedBurner or subscribe via RSS with Feedly!