Introduction

The Xamarin.Forms TabbedPage consists of a list of tabs and a larger detailed area, with each tab loading content into the detail area. But If we want to add some extra controls top of tabbed page or bottom of the tabbed page we can't; in that case, we need to create our own custom tabs. So, this article will explain to you how we can create our own custom tabbed page.

Requirements

Description

In this article, we going to create three custom tabs such as C# Corner, Xamarin, Microsoft.

Let's start creating Custom TabbedView.

Step 1

First, follow the below steps to create the new Xamarin.Forms project.

Now, the project structure will be created like below.

  • CustomTabbedPage
    It is for Shared Code

  • CustomTabbedPage.Droid
    It is for Android.

  • CustomTabbedPage.iOS
    It is for iOS
Step 2

PCL

Create a class with RibbonView name it should inherit from ContentView and place it inside ControlsToolkit -->Custom folder and add code.

RibbonView.cs

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Windows.Input;
  6. using Xamarin.Forms;
  7. namespace CustomRabbedPageControlsToolkit.Custom {
  8. public class RibbonView: ContentView {
  9. #region ItemsSource property
  10. public static readonly BindableProperty ItemsSourceProperty = BindableProperty.Create(nameof(ItemsSource), typeof(IList), typeof(RibbonView), null, propertyChanged: OnItemsSourceModified);
  11. public IList ItemsSource {
  12. get {
  13. return (IList) GetValue(ItemsSourceProperty);
  14. }
  15. set {
  16. SetValue(ItemsSourceProperty, value);
  17. }
  18. }
  19. private static void OnItemsSourceModified(object sender, object oldValue, object newValue) {
  20. RibbonView rv = (RibbonView) sender;
  21. rv._itemsContainerLayout.Children.Clear();
  22. IEnumerator iter = ((IList)newValue).GetEnumerator();
  23. int index = 0;
  24. while (iter.MoveNext()) {
  25. String label = (String)iter.Current;
  26. StackLayout layout = new StackLayout() {
  27. Orientation = StackOrientation.Vertical,
  28. VerticalOptions = LayoutOptions.FillAndExpand,
  29. HorizontalOptions = LayoutOptions.FillAndExpand,
  30. Padding = new Thickness(10, 10, 10, 0),
  31. };
  32. Label titleLabel = new Label() {
  33. Text = label,
  34. TextColor = rv.TextColor,
  35. FontSize = rv.FontSize,
  36. Style = rv.Style,
  37. VerticalTextAlignment = TextAlignment.Center,
  38. HorizontalTextAlignment = TextAlignment.Center,
  39. VerticalOptions = LayoutOptions.FillAndExpand,
  40. HorizontalOptions = LayoutOptions.FillAndExpand
  41. };
  42. BoxView box = new BoxView() {
  43. BackgroundColor = rv.BarColor,
  44. VerticalOptions = LayoutOptions.EndAndExpand,
  45. HeightRequest = 2,
  46. HorizontalOptions = LayoutOptions.FillAndExpand
  47. };
  48. layout.Children.Add(titleLabel);
  49. layout.Children.Add(box);
  50. layout.GestureRecognizers.Add(new TapGestureRecognizer() {
  51. Command = new Command((obj) => {
  52. rv.OnSelectedItem(label);
  53. })
  54. });
  55. ++index;
  56. rv._itemsContainerLayout.Children.Add(layout);
  57. }
  58. rv.OnSelectedItem(((List<String>)rv.ItemsSource).ElementAt(rv.SelectedItemIndex));
  59. }
  60. #endregion
  61. #region Textcolor property
  62. public static readonly BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(RibbonView), Color.Black);
  63. public Color TextColor {
  64. get {
  65. return (Color)GetValue(TextColorProperty);
  66. }
  67. set {
  68. SetValue(TextColorProperty, value);
  69. }
  70. }
  71. #endregion
  72. public static new readonly BindableProperty StyleProperty = BindableProperty.Create(nameof(Style), typeof(Style), typeof(RibbonView), null);
  73. public new Style Style {
  74. get {
  75. return (Style)GetValue(StyleProperty);
  76. }
  77. set {
  78. SetValue(StyleProperty, value);
  79. }
  80. }
  81. #region Barcolor property
  82. public static readonly BindableProperty BarColorProperty = BindableProperty.Create(nameof(BarColor), typeof(Color), typeof(RibbonView), Color.Black);
  83. public Color BarColor {
  84. get {
  85. return (Color) GetValue(BarColorProperty);
  86. }
  87. set {
  88. SetValue(BarColorProperty, value);
  89. }
  90. }
  91. #endregion
  92. #region FontSize property
  93. public static readonly BindableProperty FontSizeProperty = BindableProperty.Create(nameof(FontSize), typeof(int), typeof(RibbonView), 13);
  94. public int FontSize {
  95. get {
  96. return (int) GetValue(FontSizeProperty);
  97. }
  98. set {
  99. SetValue(FontSizeProperty, value);
  100. }
  101. }
  102. #endregion
  103. #region SelectedItemIndex property
  104. public static readonly BindableProperty SelectedItemIndexProperty = BindableProperty.Create(nameof(SelectedItemIndex), typeof(int), typeof(RibbonView), 0);
  105. public int SelectedItemIndex {
  106. get {
  107. return (int) GetValue(SelectedItemIndexProperty);
  108. }
  109. set {
  110. SetValue(SelectedItemIndexProperty, value);
  111. }
  112. }
  113. #endregion
  114. #region ItemSelected property
  115. public static readonly BindableProperty ItemSelectedProperty = Create(nameof(ItemSelected), typeof(ICommand), typeof(RibbonView), null);
  116. public ICommand ItemSelected {
  117. get {
  118. return (ICommand) GetValue(ItemSelectedProperty);
  119. }
  120. set {
  121. SetValue(ItemSelectedProperty, value);
  122. }
  123. }
  124. #endregion
  125. StackLayout _mainLayout;
  126. StackLayout _itemsContainerLayout;
  127. public RibbonView() {
  128. _mainLayout = new StackLayout() {
  129. HorizontalOptions = LayoutOptions.FillAndExpand,
  130. Padding = new Thickness(0),
  131. Spacing = 0
  132. };
  133. _itemsContainerLayout = new StackLayout() {
  134. Orientation = StackOrientation.Horizontal,
  135. HorizontalOptions = LayoutOptions.FillAndExpand,
  136. VerticalOptions = LayoutOptions.FillAndExpand,
  137. Padding = new Thickness(5, 5, 5, 0),
  138. Spacing = 0
  139. };
  140. _mainLayout.Children.Add(_itemsContainerLayout);
  141. // _mainLayout.Children.Add(new BoxView() { HeightRequest = 1, BackgroundColor = Color.Silver, HorizontalOptions = LayoutOptions.FillAndExpand });
  142. Content = _mainLayout;
  143. }
  144. private void OnSelectedItem(String labelTitle) {
  145. int i = 0;
  146. IEnumerator iter = ItemsSource.GetEnumerator();
  147. if (this.SelectedItemIndex > -1) {
  148. StackLayout itemStack = (StackLayout) _itemsContainerLayout.Children.ToArray()[this.SelectedItemIndex];
  149. BoxView bv = (BoxView)itemStack.Children.ToArray()[1];
  150. bv.BackgroundColor = this.BackgroundColor;
  151. }
  152. while (iter.MoveNext()) {
  153. StackLayout itemStack = (StackLayout) _itemsContainerLayout.Children.ToArray()[i];
  154. BoxView bv = (BoxView) itemStack.Children.ToArray()[1];
  155. if (((string) iter.Current).CompareTo(labelTitle) == 0) {
  156. bv.BackgroundColor = this.BarColor;
  157. this.SelectedItemIndex = i;
  158. } else {
  159. bv.BackgroundColor = this.BackgroundColor;
  160. }
  161. i += 1;
  162. }
  163. if (ItemSelected != null) {
  164. ItemSelected.Execute(this.SelectedItemIndex);
  165. }
  166. }
  167. }
  168. }

Step 3

Create your own Xaml page named HomePage.xaml, and make sure to refer to "RibbonView" class in Xaml by declaring a namespace for its location and using the namespace prefix on the control element. The following code example shows how the "RibbonView" ContentView can be consumed by a XAML page:

HomePage.xaml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  4. x:Class="CustomTabbedPage.Views.HomePage"
  5. Title="Home"
  6. BackgroundColor="#533F95"
  7. xmlns:toolkitcustom="clr-namespace:CustomTabbedPage.ControlsToolkit.Custom">
  8. <ContentPage.Resources>
  9. <ResourceDictionary>
  10. <Style x:Key="DisplayDataValueStyle" TargetType="Label">
  11. <Setter Property="FontFamily" Value="Times New Roman" /><Setter Property="TextColor" Value="#1B3B5F" /><Setter Property="FontSize" Value="15" />
  12. </Style>
  13. </ResourceDictionary>
  14. </ContentPage.Resources>
  15. <ContentPage.Content>
  16. <Grid HorizontalOptions="FillAndExpand" BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RowSpacing="0" Padding="0,0,0,0">
  17. <Grid.RowDefinitions>
  18. <RowDefinition Height="Auto" />
  19. <RowDefinition Height="Auto" />
  20. <RowDefinition Height="*" /> </Grid.RowDefinitions>
  21. <Grid Grid.Row="0" Padding="20,20,20,10">
  22. <Button BackgroundColor="White" TextColor="#533F95" FontSize="15" FontAttributes="Bold" BorderRadius="0" Text="Custom TabbedPage" HorizontalOptions="FillAndExpand" /> </Grid>
  23. <toolkitcustom:RibbonView x:Name="ribbonViews" Padding="20,0,20,0" Row="1" HorizontalOptions="FillAndExpand" BackgroundColor="Transparent" BarColor="White" TextColor="White" Style="{StaticResource DisplayDataValueStyle}" ItemsSource="{Binding RibbonOptions}" ItemSelected="{Binding OptionSelectionChangedCommand}" />
  24. <WebView x:Name="webview" Source="{Binding LoadURI}" BackgroundColor="White" Margin="5" Grid.Row="2" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" />
  25. <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" Grid.Row="2" HeightRequest="50" WidthRequest="50" Color="Maroon" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" /> </Grid>
  26. </ContentPage.Content>
  27. </ContentPage>

Note
The "xmlns:toolkitcustom" namespace prefix can be named anything. However, the clr-namespace and assembly values must match the details of the ContentView class. Once the namespace is declared the prefix is used to reference the custom View/control/layout

HomePage.xaml.cs

  1. using CustomTabbedPage.ViewModels;
  2. using Xamarin.Forms;
  3. namespace CustomTabbedPage.Views {
  4. public partial class HomePage: ContentPage {
  5. public HomePage() {
  6. InitializeComponent();
  7. var homePageViewModel = new HomePageViewModel();
  8. BindingContext = homePageViewModel;
  9. webView.Navigated += (sender, e) => {
  10. if (e.Url != null) {
  11. homePageViewModel.IsBusy = false;
  12. }
  13. };
  14. }
  15. }
  16. }

Here we gave ViewModel object to BindingContext to the view. And BindingContext will help to get or set an object that contains the properties that will be targeted by the bound properties that belong to this BindingContext.

HomePageViewModel.cs

In this, we need to create the following properties and commands.

Properties

Commands

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Runtime.CompilerServices;
  6. using System.Windows.Input;
  7. using Xamarin.Forms;
  8. namespace CustomTabbedPage.ViewModels {
  9. public class HomePageViewModel: INotifyPropertyChanged {
  10. private IList _ribbonOptions;
  11. public IList RibbonOptions {
  12. get {
  13. return _ribbonOptions;
  14. }
  15. set {
  16. SetProperty(ref _ribbonOptions, value);
  17. }
  18. }
  19. private string _loadURI;
  20. public string LoadURI {
  21. get {
  22. return _loadURI;
  23. }
  24. set {
  25. SetProperty(ref _loadURI, value);
  26. }
  27. }
  28. private bool _isBusy;
  29. public bool IsBusy {
  30. get {
  31. return _isBusy;
  32. }
  33. set {
  34. if (_isBusy == value) return;
  35. _isBusy = value;
  36. OnPropertyChanged("IsBusy");
  37. }
  38. }
  39. private ICommand _optionSelectionChangedCommand;
  40. public ICommand OptionSelectionChangedCommand {
  41. get {
  42. return _optionSelectionChangedCommand;
  43. }
  44. set {
  45. SetProperty(ref _optionSelectionChangedCommand, value);
  46. }
  47. }
  48. protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "", Action onChanged = null) {
  49. if (EqualityComparer<T>.Default.Equals(backingStore, value))
  50. return false;
  51. backingStore = value;
  52. onChanged?.Invoke();
  53. OnPropertyChanged(propertyName);
  54. return true;
  55. }
  56. public event PropertyChangedEventHandler PropertyChanged;
  57. protected void OnPropertyChanged([CallerMemberName] string propertyName = "") {
  58. var changed = PropertyChanged;
  59. if (changed == null)
  60. return;
  61. changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
  62. }
  63. public HomePageViewModel() {
  64. List<String> lst = new List<String>() {
  65. "C# Corner",
  66. "Xamarin",
  67. "Microsoft"
  68. };
  69. this.RibbonOptions = lst;
  70. LoadURI = "https://www.c-sharpcorner.com/resources/aboutus.aspx";
  71. IsBusy = true;
  72. this.OptionSelectionChangedCommand = new Command((obj) => {
  73. var selectedItemRibbonIndex = obj.ToString();
  74. IsBusy = true;
  75. if (selectedItemRibbonIndex == "0") {
  76. LoadURI = "https://www.c-sharpcorner.com/resources/aboutus.aspx";
  77. } else if (selectedItemRibbonIndex == "1") {
  78. LoadURI = "https://docs.microsoft.com/en-us/xamarin/xamarin-forms/";
  79. } else {
  80. LoadURI = "https://www.microsoft.com/en-in?SilentAuth=1&wa=wsignin1.0";
  81. }
  82. });
  83. }
  84. }
  85. }

In HomeViewModel constructor we are assigning items to listTabs; it will return the number of tabs, so, however many tabs we want for displaying those items, we need to add to listTabs.

Note
In OptionSelectionChangedcommand we will get the SelectedTab index and based on that index we need to change the content of the detail area for each tab. In this article, we are changing the Web Source. For example, if we want to display the listview with a different source for each tab in that case just change the ItemsSource based on selected item index. And one more example is if want to display different UI for each tab just make a design with StackLayout and based on tab selection index, with the help of IsVisible Binding for layout we can display different UI for each tab.

Output:

Android

Xamarin

iOS

Xamarin

Please, download the sample from here.