Xamarin.Forms provides a simple control for adding tabs to any application but the standard implementation has some limitations. With a little knowledge of how iOS and Android work you can start creating beautiful Tabs in your Xamarin.Forms applications that support custom colors and custom text.
Just about every Xamarin.Forms project I start has some need for a TabbedPage
and the team quickly outgrows the default implementation because the standard colors are very boring. They also don’t always fit into the color guide that the designers have picked.
Custom Color Let’s learn how to add custom icon colors and custom text color to really make our TabbedPage pop.
Define the API
Before we get started let’s look at a standard TabbedPage
implementation and determine what APIs we need.
Here is the example that is pulled right out of the Xamarin.Forms Docs
1
2
3
4
5
6
7
8
9
10
11
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TabbedPageWithNavigationPage;assembly=TabbedPageWithNavigationPage"
x:Class="TabbedPageWithNavigationPage.MainPage">
<local:TodayPage />
<NavigationPage Title="Schedule" Icon="schedule.png">
<x:Arguments>
<local:SchedulePage />
</x:Arguments>
</NavigationPage>
</TabbedPage>
Missing APIs
There are a handful of APIs that we need to fully control our TabbedPage
API | Description |
---|---|
SelectedIconColor | The color of the icon for the selected tab |
UnselectedIconColor | The default icon color for unselected tabs |
SelectedTextColor | The font color for the selected tab |
UnselectedTextColor | The Font Color for the unselected tab |
Example Usage
Before we start writing our custom renderer and implementation let’s look at what we are building. We have established the APIs that want to add to control both the Icon and the Font color for the text that appears below the icon. In our example below we have a special TabbedPage
that is using a CustomTabbedPage
which includes the new APIs that we defined above. The new APIs give us granular control for the colors depending on application state.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<c:CustomTabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:TabbedSample.Views"
xmlns:c="clr-namespace:TabbedSample.Controls"
Title="Tabbed Sample"
BarBackgroundColor="Green"
BarTextColor="White"
UnselectedIconColor="White"
SelectedIconColor="Red"
x:Class="TabbedSample.Views.SampleTabbedPage">
<c:CustomTabbedPage.Children>
<local:MainPage />
<local:MainPage2 />
</c:CustomTabbedPage.Children>
</c:CustomTabbedPage>
Now that we have an idea of what we are working towards, let’s get started with our shared code.
Shared Code Control
I always put my custom controls in a folder called “Controls” that is at the root directory of my shared code.
Inside this folder create a CustomTabbedPage
.cs and make sure it is just a standard C# file, as we are going to be extending the base TabbedPage
functionality.
SelectedIconColor
1
2
3
4
5
6
7
8
9
10
11
public Color SelectedIconColor
{
get { return (Color)GetValue(SelectedIconColorProperty); }
set { SetValue(SelectedIconColorProperty, value); }
}
public static readonly BindableProperty SelectedIconColorProperty = BindableProperty.Create(
nameof(SelectedItemProperty),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
UnselectedIconColor
1
2
3
4
5
6
7
8
9
10
11
public Color UnselectedIconColor
{
get { return (Color)GetValue(UnelectedIconColorProperty); }
set { SetValue(UnelectedIconColorProperty, value); }
}
public static readonly BindableProperty UnelectedIconColorProperty = BindableProperty.Create(
nameof(UnselectedIconColor),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
SelectedTextColor
1
2
3
4
5
6
7
8
9
10
11
public Color SelectedTextColor
{
get { return (Color)GetValue(SelectedTextColorProperty); }
set { SetValue(SelectedTextColorProperty, value); }
}
public static readonly BindableProperty SelectedTextColorProperty = BindableProperty.Create(
nameof(SelectedTextColor),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
UnselectedTextColor
1
2
3
4
5
6
7
8
9
10
11
public Color UnselectedTextColor
{
get { return (Color)GetValue(UnselectedTextColorProperty); }
set { SetValue(UnselectedTextColorProperty, value); }
}
public static readonly BindableProperty UnselectedTextColorProperty = BindableProperty.Create(
nameof(UnselectedTextColor),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
Completed CustomTabbedPage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class CustomTabbedPage : TabbedPage
{
public Color SelectedIconColor
{
get { return (Color)GetValue(SelectedIconColorProperty); }
set { SetValue(SelectedIconColorProperty, value); }
}
public static readonly BindableProperty SelectedIconColorProperty = BindableProperty.Create(
nameof(SelectedItemProperty),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
public Color UnselectedIconColor
{
get { return (Color)GetValue(UnelectedIconColorProperty); }
set { SetValue(UnelectedIconColorProperty, value); }
}
public static readonly BindableProperty UnelectedIconColorProperty = BindableProperty.Create(
nameof(UnselectedIconColor),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
public Color SelectedTextColor
{
get { return (Color)GetValue(SelectedTextColorProperty); }
set { SetValue(SelectedTextColorProperty, value); }
}
public static readonly BindableProperty SelectedTextColorProperty = BindableProperty.Create(
nameof(SelectedTextColor),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
public Color UnselectedTextColor
{
get { return (Color)GetValue(UnselectedTextColorProperty); }
set { SetValue(UnselectedTextColorProperty, value); }
}
public static readonly BindableProperty UnselectedTextColorProperty = BindableProperty.Create(
nameof(UnselectedTextColor),
typeof(Color),
typeof(CustomTabbedPage),
Color.White);
}
Android Renderer
Let’s first look at building the custom renderer for the Android Platform. I prefer to put all of my custom renderer implementations in the Renderers
folder.
Create an empty renderer class the inherits from the default renderer
1
2
3
4
public class CustomTabbedPageRenderer : TabbedPageRenderer
{
public CustomTabbedPageRenderer(Context context) : base(context) { }
}
Default Colors
In my implementation I found it useful to create a set of default colors just in case the shared code doesn’t specify a color. This will prevent any runtime errors happening on the Android platform.
- Navigate to Resources->values->colors.xml
Add the following properties:
Default Colors
1
2
<color name="tabBarUnselected">#80ffffff</color>
<color name="tabBarSelected">#ffffff</color>
colors.xml
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="launcher_background">#FFFFFF</color>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="tabBarUnselected">#80ffffff</color>
<color name="tabBarSelected">#ffffff</color>
</resources>
Android Implementation
The algorithm is as follows:
- Iterate through each tab
- Get the
TabLayout.Tab.Icon
for each tab - Update the icon color based on the selected tab vs unselected tab value
Algorithm
1
2
3
4
5
6
7
8
9
10
11
for (int i = 0; i < _layout.TabCount; i++)
{
var tab = _layout.GetTabAt(i);
var icon = tab.Icon;
if (icon != null)
{
var color = tab.IsSelected ? selectedColor : unselectedColor;
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
icon.SetColorFilter(color, PorterDuff.Mode.SrcIn);
}
}
Full Implementation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
using Android.Content;
using Android.Graphics;
using Android.Support.Design.Widget;
using Android.Support.V4.Content;
using Android.Support.V4.View;
using Android.Views;
using System.ComponentModel;
using TabbedSample.Controls;
using TabbedSample.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(CustomTabbedPage), typeof(CustomTabbedPageRenderer))]
namespace TabbedSample.Droid.Renderers
{
public class CustomTabbedPageRenderer : TabbedPageRenderer
{
private bool _isConfigured = false;
private ViewPager _pager;
private TabLayout _layout;
public CustomTabbedPageRenderer(Context context) : base(context) { }
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
_pager = (ViewPager)ViewGroup.GetChildAt(0);
_layout = (TabLayout)ViewGroup.GetChildAt(1);
var control = (CustomTabbedPage)sender;
Android.Graphics.Color selectedColor;
Android.Graphics.Color unselectedColor;
if (control != null)
{
selectedColor = control.SelectedIconColor.ToAndroid();
unselectedColor = control.UnselectedIconColor.ToAndroid();
}
else
{
selectedColor = new Android.Graphics.Color(ContextCompat.GetColor(Context, Resource.Color.tabBarSelected));
unselectedColor = new Android.Graphics.Color(ContextCompat.GetColor(Context, Resource.Color.tabBarUnselected));
}
for (int i = 0; i < _layout.TabCount; i++)
{
var tab = _layout.GetTabAt(i);
var icon = tab.Icon;
if (icon != null)
{
var color = tab.IsSelected ? selectedColor : unselectedColor;
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
icon.SetColorFilter(color, PorterDuff.Mode.SrcIn);
}
}
}
}
}
iOS Renderer
I find the iOS code slightly easier to manage by the way the APIs are accessible. There is no additional resources or special default files you need to create like we did in Android. The algorithm is very similar (this is only for updating the text color)
- Iterate through each tab
- Get the
TabBar.Items[i]
- Update the text color
The icon color is handled differently as there is an easy to use api that we call to update it.
Icon Color
1
2
TabBar.UnselectedItemTintColor = unselectedColor;
TabBar.SelectedImageTintColor = selectedColor;
UpdateItem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void UpdateItem(UITabBarItem item, UIColor selected, UIColor unselected)
{
if (item == null) return;
item.SetTitleTextAttributes(new UITextAttributes
{
TextColor = selected
}, UIControlState.Selected);
item.SetTitleTextAttributes(new UITextAttributes
{
TextColor = unselected
}, UIControlState.Normal);
}
Complete Renderer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class CustomTabbedPageRenderer : TabbedRenderer
{
public override void ViewWillAppear(bool animated)
{
if (TabBar?.Items == null) return;
var control = (CustomTabbedPage)Element;
UIColor selectedColor;
UIColor unselectedColor;
UIColor selectedTextColor;
UIColor unselectedTextColor;
if (control != null)
{
selectedColor = control.SelectedIconColor.ToUIColor();
unselectedColor = control.UnselectedIconColor.ToUIColor();
selectedTextColor = control.SelectedTextColor.ToUIColor();
unselectedTextColor = control.UnselectedTextColor.ToUIColor();
}
else
{
selectedColor = UIColor.White;
unselectedColor = UIColor.Black;
selectedTextColor = UIColor.White;
unselectedTextColor = UIColor.White;
}
TabBar.UnselectedItemTintColor = unselectedColor;
TabBar.SelectedImageTintColor = selectedColor;
var tabs = Element as TabbedPage;
if (tabs != null)
{
for (int i = 0; i < TabBar.Items.Length; i++)
{
UpdateItem(TabBar.Items[i], selectedTextColor, unselectedTextColor);
}
}
base.ViewWillAppear(animated);
}
public override void ItemSelected(UITabBar tabbar, UITabBarItem item)
{
var page = ((TabbedPage)Element).CurrentPage;
}
private void UpdateItem(UITabBarItem item, UIColor selected, UIColor unselected)
{
if (item == null) return;
item.SetTitleTextAttributes(new UITextAttributes
{
TextColor = selected
}, UIControlState.Selected);
item.SetTitleTextAttributes(new UITextAttributes
{
TextColor = unselected
}, UIControlState.Normal);
}
}
Xamarin.Forms 4.0.0
If you are using the latest and greatest version of Xamarin.Forms you don’t need to do this because it is now built into the platform.
-Happy Coding