Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Xamarin

Simple Custom Fonts Helper in Xamarin.Forms for Android

4.47/5 (7 votes)
4 Mar 2017MIT4 min read 13.8K  
This post discusses simple custom fonts helper in Xamarin.Forms for Android

Image 1

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.

XML
<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:

XML
<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.

Java
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:

Java
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.

Java
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.

Java
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.

Java
[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;
    }
  }
}
Java
[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;
    }
  }
}
Java
[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:

Java
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:

XML
<?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 the 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.

License

This article, along with any associated source code and files, is licensed under The MIT License