Friday 30 March 2018

Custom TabbedPage in Xamarin.Forms MVVM

Introduction

The Xamarin.Forms TabbedPage consists of a list of tabs and a larger detail area, with each tab loading content into the detail area. But If we want to put 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
  • This article's source code is prepared by using Visual Studio with MAC machine. And it is better to install the latest Visual Studio updates from here.
  • This application is targeted for Android, iOS. And tested on Android device & iOS simulator.
  • This project is Xamarin.Forms PCL project.
Description:

In this article created three custom tabs such as C# Corner, Xamarin, Microsoft. and taking Webview control on each tab selection loading specified URL on Webview source.


Let's start creating Custom TabbedView.


Step1:

First, follow below steps to create the new Xamarin.Forms project.
  • Open Visual Studio for Mac.
  • Click on the File menu, and select New Solution.
  • In the left pane of the dialog, let's select the type of templates to display. Multiplatform > App > Xamarin.Forms > Blank Forms App and click on Next.
  • Next Enter your App Name (Ex: CustomTabbedPage). And Bottom Select Target Platforms to Android & iOS and Shared Code to Portable Class Library and click on Next button.
  • Then Choose project location with the help of Browse button and Click on Create.
 Now 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.

Step2:

Portable class library(PCL):
Take a class RibbonView 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.     
  8. namespace CustomTabbedPage.ControlsToolkit.Custom    
  9. {    
  10.     public class RibbonView : ContentView    
  11.     {    
  12.   
  13.         #region ItemsSource property    
  14.         public static readonly BindableProperty ItemsSourceProperty =    
  15.             BindableProperty.Create(    
  16.                 nameof(ItemsSource),    
  17.                 typeof(IList),    
  18.                 typeof(RibbonView),    
  19.                 null,    
  20.                 propertyChanged: OnItemsSourceModified);    
  21.         public IList ItemsSource    
  22.         {    
  23.             get    
  24.             {    
  25.                 return (IList)GetValue(ItemsSourceProperty);    
  26.             }    
  27.             set    
  28.             {    
  29.                 SetValue(ItemsSourceProperty, value);    
  30.             }    
  31.         }    
  32.         private static void OnItemsSourceModified(object sender, object oldValue, object newValue)    
  33.         {    
  34.             RibbonView rv = (RibbonView)sender;    
  35.             rv._itemsContainerLayout.Children.Clear();    
  36.     
  37.             IEnumerator iter = ((IList)newValue).GetEnumerator();    
  38.             int index = 0;    
  39.             while (iter.MoveNext())    
  40.             {    
  41.                 String label = (String)iter.Current;    
  42.     
  43.                 StackLayout layout = new StackLayout()    
  44.                 {    
  45.                     Orientation = StackOrientation.Vertical,    
  46.                     VerticalOptions = LayoutOptions.FillAndExpand,    
  47.                     HorizontalOptions = LayoutOptions.FillAndExpand,    
  48.                     Padding = new Thickness(10, 10, 10, 0),    
  49.                 };    
  50.                 Label titleLabel = new Label()    
  51.                 {    
  52.                     Text = label,    
  53.                     TextColor = rv.TextColor,    
  54.                     FontSize = rv.FontSize,    
  55.                     Style = rv.Style,    
  56.                     VerticalTextAlignment = TextAlignment.Center,    
  57.                     HorizontalTextAlignment = TextAlignment.Center,    
  58.                     VerticalOptions = LayoutOptions.FillAndExpand,    
  59.                     HorizontalOptions = LayoutOptions.FillAndExpand    
  60.                 };    
  61.     
  62.                 BoxView box = new BoxView()    
  63.                 {    
  64.                     BackgroundColor = rv.BarColor,    
  65.                     VerticalOptions = LayoutOptions.EndAndExpand,    
  66.                     HeightRequest = 2,    
  67.                     HorizontalOptions = LayoutOptions.FillAndExpand    
  68.                 };    
  69.                 layout.Children.Add(titleLabel);    
  70.                 layout.Children.Add(box);    
  71.     
  72.                 layout.GestureRecognizers.Add(new TapGestureRecognizer()    
  73.                 {    
  74.                     Command = new Command((obj) =>    
  75.                     {    
  76.                         rv.OnSelectedItem(label);    
  77.                     })    
  78.                 });    
  79.     
  80.                 ++index;    
  81.     
  82.                 rv._itemsContainerLayout.Children.Add(layout);    
  83.             }    
  84.     
  85.             rv.OnSelectedItem(((List<String>)rv.ItemsSource).ElementAt(rv.SelectedItemIndex));    
  86.         }    
  87.         #endregion    
  88.   
  89.         #region Textcolor property    
  90.         public static readonly BindableProperty TextColorProperty =    
  91.             BindableProperty.Create(nameof(TextColor), typeof(Color), typeof(RibbonView), Color.Black);    
  92.         public Color TextColor    
  93.         {    
  94.             get    
  95.             {    
  96.                 return (Color)GetValue(TextColorProperty);    
  97.             }    
  98.             set    
  99.             {    
  100.                 SetValue(TextColorProperty, value);    
  101.             }    
  102.         }    
  103.         #endregion    
  104.     
  105.         public static new readonly BindableProperty StyleProperty =    
  106.             BindableProperty.Create(nameof(Style), typeof(Style), typeof(RibbonView), null);    
  107.         public new Style Style    
  108.         {    
  109.             get    
  110.             {    
  111.                 return (Style)GetValue(StyleProperty);    
  112.             }    
  113.             set    
  114.             {    
  115.                 SetValue(StyleProperty, value);    
  116.             }    
  117.         }    
  118.   
  119.         #region Textcolor property    
  120.         public static readonly BindableProperty BarColorProperty =    
  121.             BindableProperty.Create(nameof(BarColor), typeof(Color), typeof(RibbonView), Color.Black);    
  122.         public Color BarColor    
  123.         {    
  124.             get    
  125.             {    
  126.                 return (Color)GetValue(BarColorProperty);    
  127.             }    
  128.             set    
  129.             {    
  130.                 SetValue(BarColorProperty, value);    
  131.             }    
  132.         }    
  133.         #endregion    
  134.   
  135.         #region Textcolor property    
  136.         public static readonly BindableProperty FontSizeProperty =    
  137.             BindableProperty.Create(nameof(FontSize), typeof(int), typeof(RibbonView), 13);    
  138.         public int FontSize    
  139.         {    
  140.             get    
  141.             {    
  142.                 return (int)GetValue(FontSizeProperty);    
  143.             }    
  144.             set    
  145.             {    
  146.                 SetValue(FontSizeProperty, value);    
  147.             }    
  148.         }    
  149.         #endregion    
  150.   
  151.         #region SelectedItem property    
  152.         public static readonly BindableProperty SelectedItemIndexProperty =    
  153.             BindableProperty.Create(nameof(SelectedItemIndex), typeof(int), typeof(RibbonView), 0);    
  154.         public int SelectedItemIndex    
  155.         {    
  156.             get    
  157.             {    
  158.                 return (int)GetValue(SelectedItemIndexProperty);    
  159.             }    
  160.             set    
  161.             {    
  162.                 SetValue(SelectedItemIndexProperty, value);    
  163.             }    
  164.         }    
  165.         #endregion    
  166.   
  167.         #region Textcolor property    
  168.         public static readonly BindableProperty ItemSelectedProperty =    
  169.             BindableProperty.Create(nameof(ItemSelected), typeof(ICommand), typeof(RibbonView), null);    
  170.         public ICommand ItemSelected    
  171.         {    
  172.             get    
  173.             {    
  174.                 return (ICommand)GetValue(ItemSelectedProperty);    
  175.             }    
  176.             set    
  177.             {    
  178.                 SetValue(ItemSelectedProperty, value);    
  179.             }    
  180.         }    
  181.         #endregion    
  182.     
  183.         StackLayout _mainLayout;    
  184.         StackLayout _itemsContainerLayout;    
  185.         public RibbonView()    
  186.         {    
  187.             _mainLayout = new StackLayout()    
  188.             {    
  189.                 HorizontalOptions = LayoutOptions.FillAndExpand,    
  190.                 Padding = new Thickness(0),    
  191.                 Spacing = 0    
  192.             };    
  193.             _itemsContainerLayout = new StackLayout()    
  194.             {    
  195.                 Orientation = StackOrientation.Horizontal,    
  196.                 HorizontalOptions = LayoutOptions.FillAndExpand,    
  197.                 VerticalOptions = LayoutOptions.FillAndExpand,    
  198.                 Padding = new Thickness(5, 5, 5, 0),    
  199.                 Spacing = 0    
  200.             };    
  201.             _mainLayout.Children.Add(_itemsContainerLayout);    
  202.             // _mainLayout.Children.Add(new BoxView() { HeightRequest = 1, BackgroundColor = Color.Silver, HorizontalOptions = LayoutOptions.FillAndExpand });    
  203.     
  204.             Content = _mainLayout;    
  205.         }    
  206.     
  207.         private void OnSelectedItem(String labelTitle)    
  208.         {    
  209.             int i = 0;    
  210.             IEnumerator iter = ItemsSource.GetEnumerator();    
  211.     
  212.             if (this.SelectedItemIndex > -1)    
  213.             {    
  214.                 StackLayout itemStack = (StackLayout)_itemsContainerLayout.Children.ToArray()[this.SelectedItemIndex];    
  215.                 BoxView bv = (BoxView)itemStack.Children.ToArray()[1];    
  216.                 bv.BackgroundColor = this.BackgroundColor;    
  217.             }    
  218.     
  219.             while (iter.MoveNext())    
  220.             {    
  221.                 StackLayout itemStack = (StackLayout)_itemsContainerLayout.Children.ToArray()[i];    
  222.                 BoxView bv = (BoxView)itemStack.Children.ToArray()[1];    
  223.     
  224.                 if (((string)iter.Current).CompareTo(labelTitle) == 0)    
  225.                 {    
  226.                     bv.BackgroundColor = this.BarColor;    
  227.                     this.SelectedItemIndex = i;    
  228.                 }    
  229.                 else    
  230.                 {    
  231.                     bv.BackgroundColor = this.BackgroundColor;    
  232.                 }    
  233.                 i += 1;    
  234.             }    
  235.     
  236.             if (ItemSelected != null)    
  237.             {    
  238.                 ItemSelected.Execute(this.SelectedItemIndex);    
  239.             }    
  240.     
  241.         }    
  242.     }    
  243. }    

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.         
  9.     <ContentPage.Resources>    
  10.         <ResourceDictionary>    
  11.             <Style x:Key="DisplayDataValueStyle" TargetType="Label">    
  12.                 <Setter Property="FontFamily" Value="Times New Roman" />    
  13.                 <Setter Property="TextColor" Value="#1B3B5F" />    
  14.                 <Setter Property="FontSize" Value="15" />    
  15.             </Style>    
  16.         </ResourceDictionary>    
  17.     </ContentPage.Resources>    
  18.         
  19.     <ContentPage.Content>    
  20.         <Grid HorizontalOptions="FillAndExpand" BackgroundColor="Transparent" VerticalOptions="FillAndExpand" RowSpacing="0" Padding="0,0,0,0" >    
  21.             <Grid.RowDefinitions>    
  22.                 <RowDefinition Height="Auto" />    
  23.                 <RowDefinition Height="Auto" />    
  24.                 <RowDefinition Height="*" />    
  25.             </Grid.RowDefinitions>    
  26.             <Grid Grid.Row="0" Padding="20,20,20,10">    
  27.                 <Button BackgroundColor="White" TextColor="#533F95" FontSize="15" FontAttributes="Bold" BorderRadius="0" Text="Custom TabbedPage" HorizontalOptions="FillAndExpand"/>    
  28.             </Grid>    
  29.             <toolkitcustom:RibbonView x:Name="ribbonViews" Padding="20,0,20,0"    
  30.                 Grid.Row="1"    
  31.                 HorizontalOptions="FillAndExpand"    
  32.                 BackgroundColor="Transparent"    
  33.                 BarColor="White"    
  34.                 TextColor="White"    
  35.                 Style="{StaticResource DisplayDataValueStyle}"    
  36.                 ItemsSource="{Binding RibbonOptions}"    
  37.                 ItemSelected="{Binding OptionSelectionChangedCommand}"/>     
  38.            <WebView x:Name="webview" Source="{Binding LoadURI}" BackgroundColor="White" Margin="5" Grid.Row="2" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"/>    
  39.             <ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" Grid.Row="2" HeightRequest="50" WidthRequest="50" Color="Maroon" HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"/>    
  40.         </Grid>    
  41.     </ContentPage.Content>    
  42. </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.     
  4. namespace CustomTabbedPage.Views    
  5. {    
  6.     public partial class HomePage : ContentPage    
  7.     {    
  8.         public HomePage()    
  9.         {    
  10.             InitializeComponent();    
  11.             var homePageViewModel= new HomePageViewModel();    
  12.             BindingContext = homePageViewModel;    
  13.     
  14.             webview.Navigated+=(sender, e) => {    
  15.                 if(e.Url!=null)    
  16.                 {    
  17.                     homePageViewModel.IsBusy = false;    
  18.                 }    
  19.             };    
  20.         }    
  21.     }    
  22. }    


Here we gave ViewModel object to BindingContext to the view. And BindingContext will help to gets or sets 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 following properties and commands.

Properties:


  • RibbonOptions(for tabs list).
  • LoadURI(for dynamic WebView URL based on tab selection).
  • IsBusy(for ActivityIndicator).

Commands:

  • OptionSelectionChangedCommand(For tab selection)



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

In HomeViewModel constructor we are assigning items to listTabs it will return the no of tabs, so, how tabs we want to display those items, need to add listTabs.

Note:
In OptionSelectionChangedcommand we will get the SelectedTab index and based on that index we need to change the content of 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 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:


iOS: 


Please download the sample from below.

No comments:

Post a Comment