Xamarin Android TabbedPage show FontAwesome icon on tab

410 views Asked by At

I want to show icons and text on my Android TabbedPage tabs in my Xamarin application, and I want them to be FontAwesome glyphs rather than png files.

My TabbedPage is defined in code, and I originally set the IconImageSource for the child pages in their constructors. In iOS, that's all I seem to need to do for png files to show up, but in Android that didn't work (though I think it should have, no idea why it didn't) so I seem to need a custom renderer even for the pngs. This renderer works for pngs if I add an "IconName" field to each of my ContentPage classes and set its value to the name of the png file (without the .png extension):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Support.Design.Internal;
using Android.Support.Design.Widget;
using Android.Support.V4.Content;
using Android.Views;
using Android.Widget;
using Origami.MobileForms.Droid.Renderers;
using Origami.MobileForms.Pages;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;

[assembly: ExportRenderer(typeof(FormTabbedPage), typeof(ExtendedTabbedPageRenderer))]
namespace Origami.MobileForms.Droid.Renderers
{
    public class ExtendedTabbedPageRenderer : TabbedRenderer
    {
        public ExtendedTabbedPageRenderer(Context context) : base(context) { }

        protected override void DispatchDraw(global::Android.Graphics.Canvas canvas)
        {
            base.DispatchDraw(canvas);

            SetTabIcons();
        }

        private void SetTabIcons()
        {

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

            Activity activity = this.Context as Activity;
            if ((null != activity && null != activity.ActionBar && activity.ActionBar.TabCount > 0))
            {
                for (int i = 0; i < element.Children.Count; i += 1)
                {
                    Android.App.ActionBar.Tab tab = activity.ActionBar.GetTabAt(i);
                    var page = element.Children[i];
                    if ((null != tab) && (null != page) && (null != page.IconImageSource))
                    {
                        var customImagePage = page as ICustomImagePage;
                        if(customImagePage != null)
                        {
                            var resourceId = Resources.GetIdentifier(customImagePage.IconName.ToLower(), "drawable", Context.PackageName);
                            tab.SetIcon(resourceId);
                        }
                    }
                }
            }
        }
    }
}

I'm trying to figure out how to use FontAwesome icons instead of .png files here. I did figure out how to use a FontAwesome icon as the IconImageSource in my ContentPage:

this.IconImageSource = new FontImageSource { FontFamily = "fa-solid", Size = 30,  Glyph= "\uf15c", Color = Color.Red }; 

with this attribute added in my shared project over the namespace of the App class:

[assembly: ExportFont("fa-solid-900.ttf", Alias = "fa-solid")]

However, the SetIcon method that I'm using in the TabbedRenderer override only accepts a ResourceID or a Drawable as a parameter, and I'm not sure how to generate either one of those from a FontAwesome icon. I tried using the TextDrawable NuGet package by eddydn as follows in my custom renderer, which compiles and runs, but no icon shows up:

private void SetTabIcons()
{

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

    Activity activity = this.Context as Activity;
    if ((null != activity && null != activity.ActionBar && activity.ActionBar.TabCount > 0))
    {
        for (int i = 0; i < element.Children.Count; i += 1)
        {
            var page = element.Children[i];
            if ((null != tab) && (null != page) && (null != page.IconImageSource))
            {
                var customImagePage = page as ICustomImagePage;
                if(customImagePage != null)
                {
                    Typeface font = Typeface.Create("fa-solid", TypefaceStyle.Normal);
                    TextDrawable icon = new TextDrawable.Builder().BeginConfig().TextColor(Android.Graphics.Color.Red).UseFont(font)
                        .FontSize(30).EndConfig()
                        .BuildRect("\uf15c", Android.Graphics.Color.Red);
                    tab.SetIcon(icon);
                }
            }
        }
    }
}

I have also tried another way to get the drawable object for the SetIcon call, using the IconDrawable class defined here (and modifying the line in IconDrawable that sets the Typeface to use Create or CreateFromAsset based on how I imported FontAwesome) like this:

var drawable = new IconDrawable(this.Context, "\uf15c";", "fa-solid").Color(Xamarin.Forms.Color.Red.ToAndroid()).SizeDp(30);

Thank you in advance for any assistance you may be able to offer!

1

There are 1 answers

2
user756366 On BEST ANSWER

I was almost there with my last attempt above - the issue was that I was trying to set up the IconDrawable as:

Typeface.Create(fontFacePath, TypefaceStyle.Normal);

I hoped that would create the Typeface using the files I had included in my shared project and added to the code of that project via attributes, but maybe including them that newer way doesn't make the font accessible in the Droid project... Anyway, when I instead used the old way of adding FontAwesome to a project - add FontAwesome under the Assets folder in the Droid project (either an .otf or a .ttf) and call

Typeface.CreateFromAsset(context.Assets, $"{fontFacePath}.otf") 

(or .ttf) to get the typeface - that worked fine.