Xamarin.Forms: Fully Customize Bottom Tabs on Android & Turn Off Shifting
It seems as if the BottomNavigationView aka Bottom Tabs on Android is one of the top things that I write about on my blogs. There was an introduction post, how to use reflection to remove shifting on native Xamarin.Android, official support in Xamarin.Forms, and most recently how to get a nice drop shadow. I am back one more time to talk specifically about how to finely tune those bottom tabs on android and stop the ridiculous default Android behavior of shifting the tab icons and text.
Let's start with the problem. If you have more than 3 tabs in your bottom navigation view (aka bottom tabs) then the text of the icons vanishes and you are left with just the icons that "shift". As you can see from this example it is kind of annoying:
What is very interesting is that not a single Google specific app that uses bottom tabs has the shift on at all. To adjust this behavior it used to be extremely difficult and required a bunch of Java reflection (which is no fun). However, in the most recent Android Support 28 libraries Google fixed it with the LabelVisibilityMode. So first things first, make sure you are set to compile against Android 9.0 and update those Support Libraries:
Compile:
Libraries:
Now, we just need to write a simple effect to apply to our Xamarin.Forms TabbedPage (no renderer required!).
Here is our base NoShiftEffect.cs inside of our Android project:
using Android.Support.Design.BottomNavigation;
using Android.Support.Design.Widget;
using Android.Views;
using App16.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(NoShiftEffect), "NoShiftEffect")]
namespace App16.Droid
{
public class NoShiftEffect : PlatformEffect
{
protected override void OnAttached ()
{
if (!(Container.GetChildAt(0) is ViewGroup layout))
return;
if (!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
return;
// This is what we set to adjust if the shifting happens
bottomNavigationView.LabelVisibilityMode = LabelVisibilityMode.LabelVisibilityLabeled;
}
protected override void OnDetached ()
{
}
}
}
Now inside of your shared code create the Routing Effect:
using Xamarin.Forms;
namespace App16
{
public class NoShiftEffect : RoutingEffect
{
public NoShiftEffect() : base("MyCompany.NoShiftEffect")
{
}
}
}
And finally add the effect:
<TabbedPage.Effects>
<local:NoShiftEffect />
</TabbedPage.Effects>
Using LabelVisibilityLabeled for the mode will turn off shifting and properly display the labels:
Here is what the other modes do:
- LabelVisibilityAuto: Label behaves as "labeled" when there are 3 items or less, or "selected" when there are 4 items or more.
- LabelVisibilityLabeled: Label is shown on all navigation items.
- LabelVisibilitySelected: Label is shown on the selected navigation item.
- LabelVisibilityUnlabled: Label is not shown on any navigation items.
Here is what the last one looks like:
If you are using an older version of the support libraries then you can fall back on my old reflection trick and a custom renderer.
I think that is it! I think I can retire from bottom tabs for now! Enjoy and don't forget to read all about Bottom Tabs on the official documentation.