In my previous post I have explained how to deal with the selected and unselected state of the TabbedPage tabs. This post will be a continuation on the topic of TabbedPage customisation. I will show you how to remove labels from the tabs on Android.

BottomNavigationView without labels

Prerequisites

  • You will need to update Android Support libraries to 28.0.0.1
  • You will need to set Target Framework to Android Pie (9.0)

Implementation

Unfortunately, there's no property exposed on the TabbedPage that controls the visibility of the tabs, therefore we will need to create an Effect and write a bit of platform specific code.

In the platform-independent project (the .NET Standard project with Xamarin.Forms nuget package installed in it aka the UI project) create a RoutingEffect

using Xamarin.Forms;

namespace TabbedPagePlayground.UI.Effects
{
    public class HideTabLabelsEffect : RoutingEffect
    {
        public HideTabLabelsEffect()
            : base($"AppEffects.{nameof(HideTabLabelsEffect)}")
        {
        }
    }
}

In the Android project create a PlatformEffect

using System.Linq;
using Android.Support.Design.BottomNavigation;
using Android.Support.Design.Widget;
using TabbedPagePlayground.Droid.Effects;
using TabbedPagePlayground.Droid.Extensions;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;

[assembly: ExportEffect(typeof(HideTabLabelsEffect), nameof(HideTabLabelsEffect))]

namespace TabbedPagePlayground.Droid.Effects
{
    public class HideTabLabelsEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            var renderer = (Control ?? Container) as TabbedPageRenderer;

            var children = renderer?.ViewGroup?.RetrieveAllChildViews();
            if (children?.FirstOrDefault(x => x is BottomNavigationView) is BottomNavigationView bottomNav)
            {
                bottomNav.LabelVisibilityMode = LabelVisibilityMode.LabelVisibilityUnlabeled;
            }
        }

        protected override void OnDetached()
        {
        }
    }
}

In the above code we're setting the LabelVisibilityMode property of the BottomNavigationView to LabelVisibilityMode.LabelVisibilityUnlabeled, which according to the official Android docs will hide the labels:

Label is not shown on any navigation items.

Let me shed some light on the code that is responsible for retrieving the BottomNavigationView. We start from casting Control and/or Container property of the PlatformEffect onto TabbedPageRenderer. Then we use the ViewGroup property

A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers.

to drill down into the children of our rendered view using the RetrieveAllChildViews extension method (code below). Once we have all the children, we then look in that collection for a child of type BottomNavigationView. Then, it's a simple as setting the LabelVisibilityMode flag.

Extension method code:

using System.Collections.Generic;
using System.Linq;
using Android.Views;

namespace TabbedPagePlayground.Droid.Extensions
{
    public static class ViewExtensions
    {
        public static List<View> RetrieveAllChildViews(this View view)
        {
            if (!(view is ViewGroup group))
            {
                return new List<View> { view };
            }

            var result = new List<View>();

            for (var i = 0; i < group.ChildCount; i++)
            {
                var child = group.GetChildAt(i);

                var childList = new List<View> { child };
                childList.AddRange(RetrieveAllChildViews(child));

                result.AddRange(childList);
            }

            return result.Distinct().ToList();
        }
    }
}

The sample code for the above can be found in this github repo: https://github.com/Progrunning/TabbedPagePlayground


If you haven't registered Effects before (read more about the ResolutionGroupName attribute), please do so now. Otherwise, your code in the PlatformEffect won't work. I tend to put the ResolutionGroupName attribute inside of main MainLauncher activity

using Android.App;
using Android.Content.PM;
using Android.OS;
using TabbedPagePlayground.UI;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Resource = Android.Resource;

[assembly: ResolutionGroupName("AppEffects")] // <-- Here

namespace TabbedPagePlayground.Droid
{
    [Activity(Label = "TabbedPagePlayground",
              Icon = "@mipmap/icon",
              Theme = "@style/MainTheme",
              MainLauncher = true,
              ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);
            Forms.Init(this, savedInstanceState);
            LoadApplication(new App());
        }
    }
}

That's all folks!