Close modal

Blog Post

Customising the Tabbar Xamarin/Android

Development
Tue 26 July 2016
0 Comments


Rationale

Everyone likes icons, at least most people expect their tab-bars to have them, Xamarin/Android doesn't fill them out based on the root page item as you'd expect, so let's roll up our sleeves and get to work. We'll also be seeing how to change the colour/style of the action & tab bars first.

Method

Changing some colours with style resource

The easiest and most consistent way to change the tab or action bar is via the styles file, some of these properties can be changed in custom renderers, however some are also tricky if not impossible (like the tab bar background). The other advantage is that this will take effect right away when the app starts, where-as changing the actionbar colour on mainactivity may have some delay.

This practice is not specific to Xamarin at all and is just Android procedure, but I wish there would have been something explaining it to me like this being a cross-platform developer and not having had the native Android introduction. Essentially you need to create a named theme (which is a <style> in and of itself), and then you need to add items of <style> as children - they will inherit from some Android style, which is defined in AppCompat (basically a compatability for pre-lollipop down to gingerbread devices to have the styling).

There are many, many properties but some of the most common items you want to change are below:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <style name="MyTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
        <item name="android:actionBarTabTextStyle">@style/MyActionBarTabText</item>
        <item name="android:actionBarTabBarStyle">@style/MyActionBarTabBar</item>
        <item name="android:actionBarStyle">@style/MyActionBar</item>
    </style>
    <style name="MyActionBarTabText" parent="@android:style/Widget.Holo.ActionBar.TabText">
        <item name="android:textColor">#525252</item>
    </style>
    <style name="MyActionBar" parent="@android:style/Widget.Holo.Light.ActionBar">
        <item name="android:background">#ff845c</item>
    </style>
    <style name="MyActionBarTabBar" parent="@android:style/Widget.AppCompat.ActionBar.TabBar">
        <item name="android:background">#f9f9f9</item>
    </style>
</resources>
Breaking it down

The top style defines a bunch of named items with the xml value being the named path to the item beginning with @ which tells Android what the style is and where your have specified it.

android:actionBarTabTextStyle       =>  @style/MyActionBarTabText
android:actionBarTabBarStyle        =>  @style/MyActionBarTabBar
android:actionBarStyle              =>  @style/MyActionBar

MyActionBarTabText is of course just a name I defined, hence the my.

The styles for the individual design elements need to inherit from the correct parent (this can be looked up - but if you are only interested in what I have used for illustrations they will work.

MyActionBarTabText      : android:style/Widget.Holo.ActionBar.TabText
MyActionBarTabBar       : android:style/Widget.Holo.Light.ActionBar
MyActionBar             : android:style/Widget.AppCompat.ActionBar.TabBar

Finally within each style you can see that I have defined certain item which equates to an attribute of that style such as text color ot background, and in this example that's all I have used, more do exist however.

Update your MainActivity to take advantage of the theme
namespace MyApp.Android {
[Activity (Label = "MyApp", Theme = "@style/MyTheme", Icon = "@drawable/icon", MainLauncher = true)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity {
    ...
    }
}

Adding Icons to the tab item

Defining custom renderers

If you're like me, and primarily used to iOS, you should think that a tab bar should have an icon and a text out of the box by assigning it to the page/controller within that tab right? wrong; but it's not hard to fix let's add a custom tab renderer for android for Xamarin.Forms.TabbedPage.

using Android.App;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer (typeof (TabbedPage), typeof (IconTabbedRenderer))]
public class IconTabbedRenderer : TabbedRenderer
{
    protected override void DispatchDraw (global::Android.Graphics.Canvas canvas)
    {
        base.DispatchDraw (canvas);
        SetTabIcons ();
    }

    private void SetTabIcons ()
    {
        Activity activity = Context as Activity; //A bit hacky, but necessary
        if (Element != null && activity != null && activity.ActionBar != null) {
            for (int i = 0; i < element.Children.Count; i += 1) {
                var tab = activity.ActionBar.GetTabAt (i);
                var page = element.Children [i];
                var img = page.Icon != null ? page.Icon.File.Replace (".png", "") : "tab0";
                tab.SetIcon (Context.Resources.GetIdentifier (img, "drawable", Context.PackageName));
            }
        }
    }
}

As you can see it's fairly simple, we assume that the page's Icon source (which may or may not contain .png) maps to the name of a resource that's able to be resolved, and that's how we dynamically set the tab icons based on the information we already assigned that iOS for example would pick up on.

Defining custom tab views

If you wish to go one step further, and use a completly custom view for your tabs you may do so by inflating a custom view in the standard way and assigning it as a custom view.

NB. you'll need to store a flag/reference that a particular tab has ALREADY had its custom view added as this method may get called multiple times and you'll be stuck with repeitions of the subview assigned to the tab.

In this example we assumer we have Resources/Layout/customtab.axml and within this are the custom views with Ids tabText (text view) and tabIcon (image view) which we retrieve by resource and manipulate as required.

To do this, inside the iteration of a particular tab within SetTabIcons:

var inflater = LayoutInflater.From (Context);
View view = inflater.Inflate (MyApp.Droid.Resource.Layout.customtab, null);
TextView txtTitle = (TextView)view.FindViewById (MyApp.Droid.Resource.Id.tabText);
txtTitle.SetText (tab.Text, TextView.BufferType.Normal);
ImageView image = (ImageView)view.FindViewById (MyApp.Droid.Resource.Id.tabIcon);
image.SetImageResource (resourceId);
tab.SetCustomView (view);

And of course your customTab.xaml may look like the following (example):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="horizontal"
    android:background="@android:color/transparent">
    <ImageView
        android:id="@+id/tabIcon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:paddingTop="2dp"
        android:background="@android:color/transparent" />
    <TextView
        android:id="@+id/tabText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textColor="@android:color/transparent" />
</LinearLayout>

That's a wrap. Hopefully this guide is useful to someone navigating the Android waters of Xamarin's cross-platform environment.


Comments !