2
votes

I am trying to create bottom tabbed page in Xamarin forms and i am doing this for Android.

here i am using latest version of Xamarin Forms.

My sample Bottom tabbed page like below.

<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:views="clr-namespace:SampleTabbedPage.Views"
            xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
            android:TabbedPage.ToolbarPlacement="Bottom"
            android:TabbedPage.BarItemColor="Gray"
            android:TabbedPage.BarSelectedItemColor="Blue"
             mc:Ignorable="d"
             x:Class="SampleTabbedPage.Views.SampleTabbed">
  <!--Pages can be added as references or inline-->

    <NavigationPage
        Title="Main"
        NavigationPage.HasNavigationBar="False">
        <x:Arguments>
            <views:SampleDetailsPage/>
        </x:Arguments>
    </NavigationPage>

    <ContentPage Title="Tab 1" />
  <ContentPage Title="Tab 2" />
</TabbedPage>

My sample details page like

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="SampleTabbedPage.Views.SampleDetailsPage"
             BackgroundColor="Gray">
    <ContentPage.Content>
        <StackLayout>
            <ListView BackgroundColor="White" ItemTapped="ListView_ItemTapped">
                <ListView.ItemsSource>
                    <x:Array Type="{x:Type x:String}">
                        <x:String>Mango</x:String>
                        <x:String>Banana</x:String>
                        <x:String>Pinaple</x:String>
                        <x:String>Apple</x:String>
                        <x:String>Avacado</x:String>
                        <x:String>Coconut</x:String>
                        <x:String>Dragan Fruit</x:String>
                        <x:String>Pomaganate</x:String>
                        <x:String>Wood Apple</x:String>
                    </x:Array>
                </ListView.ItemsSource>
            </ListView>

        </StackLayout>
    </ContentPage.Content>
</ContentPage>

And the navigation i am doing as follows

in App.xamal.cs

MainPage = new NavigationPage(new FirstPage());

I have simple first page

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="SampleTabbedPage.Views.FirstPage">
    <ContentPage.Content>
        <StackLayout>
            <Button Text="Click Me!" Clicked="Button_Clicked"
                VerticalOptions="CenterAndExpand" 
                HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

here in button click event i do the navigation to "Sample Tabbed Page"

await Navigation.PushAsync(new SampleTabbed());

This is working fine. but there is a performance issue.

if i run this and click on button in First page it is taking 3 seconds to load tabbed page.

if i remove list view in sample detail page it take 2 seconds to load.

but if i add just a content page to click event it take only milliseconds to load.

Am i doing some thing wrong with implementing tabbed page?

or

is there any way to improve the loading performance with tabbed pages.

1
Could it work ?Leo Zhu - MSFT
Have you get any solution?Md. Asaduzzaman
yes the below answer worked for meChandu Ranwala

1 Answers

3
votes

A solution is to make the heavy pages load their content in a lazy manner, only when their tab becomes selected. This way, since these pages are now empty when TabbedPage is created, navigating to the TabbedPage suddenly becomes very fast!

1.create a behavior for the TabbedPage page, called ActivePageTabbedPageBehavior.

class ActivePageTabbedPageBehavior : Behavior<TabbedPage>
{
 protected override void OnAttachedTo(TabbedPage tabbedPage)
  {
    base.OnAttachedTo(tabbedPage);
    tabbedPage.CurrentPageChanged += OnTabbedPageCurrentPageChanged;
  }

 protected override void OnDetachingFrom(TabbedPage tabbedPage)
  {
    base.OnDetachingFrom(tabbedPage);
    tabbedPage.CurrentPageChanged -= OnTabbedPageCurrentPageChanged;
  }

 private void OnTabbedPageCurrentPageChanged(object sender, EventArgs e)
  {
    var tabbedPage = (TabbedPage)sender;

    // Deactivate previously selected page
    IActiveAware prevActiveAwarePage = tabbedPage.Children.OfType<IActiveAware>()
        .FirstOrDefault(c => c.IsActive && tabbedPage.CurrentPage != c);
    if (prevActiveAwarePage != null)
    {
        prevActiveAwarePage.IsActive = false;
    }

    // Activate selected page
    if (tabbedPage.CurrentPage is IActiveAware activeAwarePage)
    {
        activeAwarePage.IsActive = true;
    }
  }
}

2.define IActiveAware interface

interface IActiveAware
  {
    bool IsActive { get; set; }
    event EventHandler IsActiveChanged;
  }

3.create a base generic abstract class called LoadContentOnActivateBehavior

abstract class LoadContentOnActivateBehavior<TActivateAwareElement> : Behavior<TActivateAwareElement>
   where TActivateAwareElement : VisualElement
 {
  public DataTemplate ContentTemplate { get; set; }

  protected override void OnAttachedTo(TActivateAwareElement element)
   {
     base.OnAttachedTo(element);
     (element as IActiveAware).IsActiveChanged += OnIsActiveChanged;
   }

  protected override void OnDetachingFrom(TActivateAwareElement element)
   {
     (element as IActiveAware).IsActiveChanged -= OnIsActiveChanged;
     base.OnDetachingFrom(element);
   }

  void OnIsActiveChanged(object sender, EventArgs e)
   {
     var element = (TActivateAwareElement)sender;
     element.Behaviors.Remove(this);
     SetContent(element, (View)ContentTemplate.CreateContent());
   }

  protected abstract void SetContent(TActivateAwareElement element, View contentView);
}

4.the specialized LazyContentPageBehavior

class LazyContentPageBehavior : LoadContentOnActivateBehavior<ContentView>
 {
   protected override void SetContent(ContentView element, View contentView)
    {
      element.Content = contentView;
    }
 }

then we can use in xaml like this:

<TabbedPage>
  <TabbedPage.Behaviors>
    <local:ActivePageTabbedPageBehavior />
  </TabbedPage.Behaviors>

<ContentPage Title="First tab">
    <Label Text="First tab layout" />
</ContentPage>

<local:LazyLoadedContentPage Title="Second tab">
    <ContentPage.Behaviors>
        <local:LazyContentPageBehavior ContentTemplate="{StaticResource ContentTemplate}" />
    </ContentPage.Behaviors>
    <ContentPage.Resources>
        <ResourceDictionary>
            <DataTemplate x:Key="ContentTemplate">
                <!-- Complex and slow to render layout -->
                <local:SlowContentView />
            </DataTemplate>
        </ResourceDictionary>
    </ContentPage.Resources>
</local:LazyLoadedContentPage>
</TabbedPage>

we moved the ContentPage complex layout to become a DataTemplate.

Here's the custom LazyLoadedContentPage page which is activation aware:

class LazyLoadedContentPage : ContentPage, IActiveAware
{
  public event EventHandler IsActiveChanged;

  bool _isActive;
  public bool IsActive
   {
     get => _isActive;
     set
      {
        if (_isActive != value)
        {
            _isActive = value;
            IsActiveChanged?.Invoke(this, EventArgs.Empty);
        }
      }
   }
 }

SlowContentView do some complex things

 public partial class SlowContentView : ContentView
{
    public SlowContentView()
    {
        InitializeComponent();

        // Simulating a complex view
        ...
    }
}