Search code examples
c#androidxamarin.formscustom-rendererxamarin.forms.shell

How can I customize the appearance of top tabs (ShellSection) in Xamarin Shell?


I’ve done a lot of research:

However, I couldn’t find how to implement what I have in mind correctly.

My application uses Xamarin Shell with a flyout menu, and some of the pages present a top tab bar. To give you an example, my application looks like this:

enter image description here

Xamarin Shell provides a simple way to create these multi-tabs pages. Now, I want to customize these tabs and change the font, the color of the selection indicator, and so on. In the beginning, I thought I could just create a specific style in the styles.xml file of my Android project and reference it in android.support.design.widget.TabLayout. To give you an example, I did something like this in the Tabbar.xml file inside the Android project of my Xamarin solution:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.TabLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:id="@+id/sliding_tabs"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:background="?attr/colorPrimary"
   android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
   app:tabIndicatorColor="@android:color/white"
   style="@style/MyCustomTabLayout"
   app:tabGravity="fill"
   app:tabMode="fixed" />

and this inside the styles.xml file always inside the Android project of the solution:

<?xml version="1.0" encoding="utf-8" ?>
<resources>
  <style name="MainTheme" parent="MainTheme.Base">
    <item name="android:textAllCaps">false</item>
  </style>

  <style name="MyCustomTabLayout" parent="Widget.Design.TabLayout">
    <item name="tabIndicatorColor">#FFFFFF</item>
    <item name="tabIndicatorHeight">3dp</item>
    <item name="tabSelectedTextColor">#FFFFFF</item>
  </style>
</resources>

However, nothing happened, and the style wasn’t applied. I thought it was a misunderstanding of how to really implement what I had in mind, and I thought that maybe the "sub" top tab bar wasn’t considered a true TabLayout, since it was just a product of ShellSection. In the end, I found that maybe what I needed was a custom renderer. I’m on my way to implement it, but I’m stuck on this issue: I don’t understand how to set the appearance of the ShellSection. My current code for the custom renderer is this:

[assembly: ExportRenderer(typeof(AppShell), typeof(CustomShellRenderer))]
namespace A {
    internal class CustomShellRenderer : ShellRenderer {
        public CustomShellRenderer(Context context) : base(context) { }

        protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection) {
            return new CustomShellSectionAppearance(this);
        }
    }

    class CustomShellSectionAppearance : ShellSectionRenderer {
        public Fragment Fragment { get; }
        public event EventHandler AnimationFinished;
        public void Dispose() {
            throw new NotImplementedException();
        }

        public ShellSection ShellSection { get; set; }

        public CustomShellSectionAppearance(IShellContext shellContext) : base(shellContext) { }

        // I thought I need to make my customization here, but it was only a guess:
        // I found nobody talking about customizing a ShellSection on the Web
        protected override void SetAppearance(ShellAppearance appearance) {
            base.SetAppearance(appearance);
            appearance.TabBarDisabledColor = Color.Aqua; // ERROR: appearance has only "get" properties
        }
    }
}

Am I missing something in the process of customizing the appearance of the top tab bar of my Xamarin application?


Solution

  • You can achieve that with the following Shell custom renderer.

    • For colors: the parameters names of SetColors(TabLayout tabLayout, Color foreground, Color background, Color title, Color unselected) method are self-explanatory.
    • If Color type is not precede by a namespace in below code then it is from XF, because of using Color = Xamarin.Forms.Color;
    • For Font you need to create a custom view and define whatever attributes you want.
    • You may modify an attribute, change style or do any logic when selection changed using TabSelected and TabUnSelected events, in my demo I am applying bold on text and make it bigger when tab is selected and reverting back when tab is unselected, you may implement a nice animation there also.
    public class MyShellRenderer : ShellRenderer
    {
        public MyShellRenderer(Context context) : base(context)
        {
        }
    
        protected override IShellTabLayoutAppearanceTracker CreateTabLayoutAppearanceTracker(ShellSection shellSection)
            => new MyTabLayoutAppearanceTracker(this);
    }
    
    
    public class MyTabLayoutAppearanceTracker : ShellTabLayoutAppearanceTracker
    {
        public MyTabLayoutAppearanceTracker(IShellContext shellContext) : base(shellContext)
        {
        }
    
        public override void SetAppearance(TabLayout tabLayout, ShellAppearance appearance)
        {
            base.SetAppearance(tabLayout, appearance);
            tabLayout.TabSelected += TabLayout_TabSelected;
            tabLayout.TabUnselected += TabLayout_TabUnselected;
    
            for (var i = 0; i < tabLayout.TabCount; i++)
            {
                TabLayout.Tab tab = tabLayout.GetTabAt(i);
                if (tab.CustomView == null)
                {
                    TextView textview = new TextView(Android.App.Application.Context)
                    {
                        Text = tabLayout.GetTabAt(i).Text,
                        TextSize = 20,
                        Typeface = Typeface.DefaultBold
                    };
                    textview.SetTextColor(Android.Graphics.Color.Black);
                    tab.SetCustomView(textview);
                }
            }
            base.SetColors(tabLayout, Color.Red, Color.Yellow, Color.Black, Color.Gray);
        }
            private void TabLayout_TabUnselected(object sender, TabLayout.TabUnselectedEventArgs e)
            {
                if (e.Tab?.CustomView is TextView textView)
                {
                    textView.Typeface = Typeface.Default;
                    textView.TextSize = 15;
                }
            }
    
            private void TabLayout_TabSelected(object sender, TabLayout.TabSelectedEventArgs e)
            {
                if (e.Tab?.CustomView is TextView textView)
                {
                    textView.Typeface = Typeface.DefaultBold;
                    textView.TextSize = 20;
                }
            }
    }
    

    enter image description here


    EDIT

    An easier solution without custom renderer?

    Take a look at TabView from xamarin-communitytoolkit package.