CommunityToolkit / Maui

The .NET MAUI Community Toolkit is a community-created library that contains .NET MAUI Extensions, Advanced UI/UX Controls, and Behaviors to help make your life as a .NET MAUI developer easier
https://learn.microsoft.com/dotnet/communitytoolkit/maui
MIT License
2.27k stars 401 forks source link

[Proposal] TabView #121

Closed brminnick closed 1 year ago

brminnick commented 3 years ago

TabView

Summary

The TabView control allows the user to display a set of tabs and their content. The TabView is fully customizable, other than the native tab bars

Detailed Design

TabView.shared.cs

public class TabView : ContentView, IDisposable
{
  public static readonly BindableProperty TabItemsSourceProperty;
  public static readonly BindableProperty TabViewItemDataTemplateProperty;
  public static readonly BindableProperty TabContentDataTemplateProperty;
  public static readonly BindableProperty SelectedIndexProperty;
  public static readonly BindableProperty TabStripPlacementProperty;
  public static readonly BindableProperty TabStripBackgroundColorProperty;
  public static readonly BindableProperty TabStripBackgroundViewProperty;
  public static readonly BindableProperty TabStripBorderColorProperty;
  public static readonly BindableProperty TabContentBackgroundColorProperty;
  public static readonly BindableProperty TabStripHeightProperty;
  public static readonly BindableProperty IsTabStripVisibleProperty;
  public static readonly BindableProperty TabContentHeightProperty;
  public static readonly BindableProperty TabIndicatorColorProperty;
  public static readonly BindableProperty TabIndicatorHeightProperty;
  public static readonly BindableProperty TabIndicatorWidthProperty;
  public static readonly BindableProperty TabIndicatorViewProperty;
  public static readonly BindableProperty TabIndicatorPlacementProperty;
  public static readonly BindableProperty IsTabTransitionEnabledProperty;
  public static readonly BindableProperty IsSwipeEnabledProperty;

  public ObservableCollection<TabViewItem> TabItems { get; }
  public IList? TabItemsSource { get; set; }
  public DataTemplate? TabViewItemDataTemplate { get; set; }
  public DataTemplate? TabContentDataTemplate { get; set; }
  public int SelectedIndex { get; set; }
  public TabStripPlacement TabStripPlacement { get; set; }
  public Color TabStripBackgroundColor { get; set; }
  public View? TabStripBackgroundView { get; set; }
  public Color TabStripBorderColor { get; set; }
  public Color TabContentBackgroundColor { get; set; }
  public double TabStripHeight { get; set; }
  public bool IsTabStripVisible { get; set; }
  public double TabContentHeight { get; set; }
  public Color TabIndicatorColor { get; set; }
  public double TabIndicatorHeight { get; set; }
  public double TabIndicatorWidth  { get; set; }
  public View? TabIndicatorView { get; set; }
  public TabIndicatorPlacement TabIndicatorPlacement { get; set; }
  public bool IsTabTransitionEnabled { get; set; }
  public bool IsSwipeEnabled { get; set; }

  public event TabSelectionChangedEventHandler? SelectionChanged;
  public event TabViewScrolledEventHandler? Scrolled;

  public void Dispose();
}

Usage Syntax

XAML Usage

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
             x:Class="MyLittleApp.MainPage">

     <Grid>
        <xct:TabView
                TabStripPlacement="Bottom"
                TabStripBackgroundColor="Blue"
                TabStripHeight="60"
                TabIndicatorColor="Yellow"
                TabContentBackgroundColor="Yellow">

                <xct:TabViewItem
                    Icon="triangle.png"
                    Text="Tab 1"
                    TextColor="White"
                    TextColorSelected="Yellow"
                    FontSize="12">
                    <Grid 
                        BackgroundColor="Gray">
                        <Label
                            HorizontalOptions="Center"
                            VerticalOptions="Center"
                            Text="TabContent1" />
                    </Grid>
                </xct:TabViewItem>

                <xct:TabViewItem
                    Icon="circle.png"
                    Text="Tab 2"
                    TextColor="White"
                    TextColorSelected="Yellow"
                    FontSize="12">
                    <Grid>
                        <Label    
                            HorizontalOptions="Center"
                            VerticalOptions="Center"
                            Text="TabContent2" />
                    </Grid>
                </xct:TabViewItem>
        </xct:TabView>
  </Grid>

</ContentPage>

C# Usage

Content = new Grid
{
  new TabVIew
  {
    TabItems = 
    {
      new TabItem
      {
        Icon = "circle.png",
        Text = "Tab 2",
        TextColor = Colors.White,
        TextColorSelected = Colors.Yellow,
        FontSize = 12
        View = new Label { Text = "TabContent2" }.Center()
      }
    }
  }
}
ChaplinMarchais commented 2 years ago

I am currently working on a project which requires a TabView component, so I have began to implement a prototype according to your specification. If you would like for me to do some work toward this issue I would be glad to contribute!

brminnick commented 2 years ago

Thanks @ChaplinMarchais! That'd be fantastic!

I'll assign this to you 👍

ChaplinMarchais commented 2 years ago

@brminnick just a quick question for you about how you guys want events to be implemented in the TabView for SelectionChanged and Scrolled. Currently I am using the following to ensure that the lifetime of any handlers are correctly respected. Although I am not sure if this is the best way of doing it within the Maui framework.

    readonly WeakEventManager selectionChangedManager = new();
    readonly WeakEventManager tabViewScrolledManager = new();

    public event EventHandler<TabSelectionChangedEventArgs> SelectionChanged
    {
        add => selectionChangedManager.AddEventHandler(value);
        remove => selectionChangedManager.RemoveEventHandler(value);
    }

    public event EventHandler<TabViewScrolledEventArgs> Scrolled
    {
        add => tabViewScrolledManager.AddEventHandler(value);
        remove => tabViewScrolledManager.RemoveEventHandler(value);
    }

Any suggestions for improvements would be welcomed!

brminnick commented 2 years ago

Yup! That looks good 👍

jakubjenis commented 2 years ago

I really like how https://github.com/roubachof/Sharpnado.Tabs approaches this control. It decouples tab view items from TabStrip and only binds them together through SelectedIndex property. This enables a lot of flexibility, such as implementing a custom tab switching through buttons (if I want my tabs to look like android chips controls). What I lack in most TabView controls is the ability to switch out how the TabStrip is visualy represented - sometimes I want to underline the text, sometimes highlight an icon etc. Setting a TabStripPlacement and TextColorSelected just doesn't cut it sometimes. Would it be worth exploring more flexible options?

eamonn-alphin commented 2 years ago

How's this coming along? I've been trying to roll my own tab view but it feels like it's held together with popsicle sticks and glue...

bijington commented 2 years ago

How's this coming along? I've been trying to roll my own tab view but it feels like it's held together with popsicle sticks and glue...

The description shows that this has not been started yet. This sadly won't make it in to our first official release because we are in a feature freeze ready so it won't be ready any time soon.

We would love your help writing the code! The Community Toolkit contains features for, and created by, the .NET MAUI Community. We are all volunteers, contributing code in our spare time on nights and weekends. If you'd like to contribute, you can learn more here: https://devblogs.microsoft.com/dotnet/contributing-to-net-maui-community-toolkit/

XamerDev commented 2 years ago

Hello TabView and LazyView are crazy needed especially for the people who are using basic navigation, and want to have fully customized tabs. Looking forward to it <3

eamonn-alphin commented 2 years ago

Since it's not going to be in the first release, I downloaded the Xamarin community toolkit sample code and isolated the TabView and lazy view parts, then refactored them to work with maui. It took about 2 work days but wasn't too hard. Its really just a Grid with a carousel view and another grid (as the tab bar). The hard part was simulating stack navigation within a "tab"

eamonn-alphin commented 2 years ago

Here's the lazy view code:

using System.Threading.Tasks;
using Microsoft.Maui.Controls;

namespace Xamarin.CommunityToolkit.UI.Views
{
    /// <summary>
    /// Abstract base class for <see cref="LazyView{TView}"/>
    /// Taken from Xamarin's community toolkit to use with their Tabview
    /// <notes>
    /// Eamonn Alphin: April 2022: Imported and refactored to work. 
    /// </notes>
    /// </summary>
    public abstract class BaseLazyView : ContentView, IDisposable
    {
        internal static readonly BindablePropertyKey IsLoadedPropertyKey = BindableProperty.CreateReadOnly(nameof(IsLoaded), typeof(bool), typeof(BaseLazyView), default);

        /// <summary>
        /// This is a read-only <see cref="BindableProperty"/> that indicates when the view is loaded.
        /// </summary>
        public static readonly BindableProperty IsLoadedProperty = IsLoadedPropertyKey.BindableProperty;

        /// <summary>
        /// This is a read-only property that indicates when the view is loaded.
        /// </summary>
        public new bool IsLoaded => (bool)GetValue(IsLoadedProperty);

        /// <summary>
        /// This method change the value of the <see cref="IsLoaded"/> property.
        /// </summary>
        /// <param name="isLoaded"></param>
        protected void SetIsLoaded(bool isLoaded) => SetValue(IsLoadedPropertyKey, isLoaded);

        /// <summary>
        /// Use this method to do the initialization of the <see cref="View"/> and change the status IsLoaded value here.
        /// </summary>
        /// <returns><see cref="ValueTask"/></returns>
        public abstract ValueTask LoadViewAsync();

        /// <summary>
        /// This method dispose the <see cref="ContentView.Content"/> if it's <see cref="IDisposable"/>.
        /// </summary>
        public void Dispose()
        {
            if (Content is IDisposable disposable)
            {
                disposable.Dispose();
                GC.SuppressFinalize(this); //todo: check this if lazy loaded views aren't getting disposed correctly. 
            }
        }

        protected override void OnBindingContextChanged()
        {
            if (Content is not null and not ActivityIndicator)
            {
                Content.BindingContext = BindingContext;
            }

        }
    }
}

using System.Threading.Tasks;

namespace Xamarin.CommunityToolkit.UI.Views
{
    /// <summary>
    /// This a basic implementation of the LazyView based on <see cref="BaseLazyView"/> use this an exemple to create yours
    /// </summary>
    /// <notes>
    /// Eamonn Alphin April 2022: Imported and refactored. 
    /// </notes>
    /// <typeparam name="TView">Any <see cref="View"/></typeparam>
    public class LazyView<TView> : BaseLazyView where TView : View, new()
    {
        /// <summary>
        /// This method initializes your <see cref="LazyView{TView}"/>.
        /// </summary>
        /// <returns><see cref="ValueTask"/></returns>
        public override ValueTask LoadViewAsync()
        {
            View view = new TView { BindingContext = BindingContext };

            Content = view;

            SetIsLoaded(true);
            return new ValueTask(Task.FromResult(true));
        }
    }
}
bijington commented 2 years ago

@eamonn-alphin that is interesting to hear! Would you be willing to assist with the TabView implementation in the toolkit?

My fear with the TabView is that there are a lot of issues open against the Xamarin Community Toolkit which we should look to avoid inheriting https://github.com/xamarin/XamarinCommunityToolkit/issues?q=is%3Aissue+is%3Aopen+tabview+

pictos commented 2 years ago

Extending the @bijington comment, if you want to do the migration of the LazyView to here, you're more than welcome <3

Here's the issue for lazy view #112

eamonn-alphin commented 2 years ago

@eamonn-alphin that is interesting to hear! Would you be willing to assist with the TabView implementation in the toolkit?

My fear with the TabView is that there are a lot of issues open against the Xamarin Community Toolkit which we should look to avoid inheriting https://github.com/xamarin/XamarinCommunityToolkit/issues?q=is%3Aissue+is%3Aopen+tabview+

I'm willing to share the code I have for the sake of helping better minds than mine move tabviews along, but it still comes with whatever issues the latest Xamarin CT version has. But it runs at least.

brminnick commented 2 years ago

Thanks @eamonn-alphin!

We would love your help writing the code! The Community Toolkit contains features for, and created by, the .NET MAUI Community. We are all volunteers, contributing code in our spare time on nights and weekends. If you'd like to contribute, you can learn more here: https://devblogs.microsoft.com/dotnet/contributing-to-net-maui-community-toolkit/

For future reference, you don’t need to copy/paste any code from Xamarin.CommunityToolkit to get to work with .NET MAUI. We created Xamarin.CommunityToolkit.MauiCompat which is the exact Xamarin.CommunityToolkit library ported to .NET MAUI: https://devblogs.microsoft.com/xamarin/introducing-net-maui-compatibility-for-the-xamarin-community-toolkit

brminnick commented 2 years ago

FYI - I’m hiding the LazyView comments as “off-topic” to avoid creating noise for future devs interested in helping with this TabView Proposal

AswinPG commented 2 years ago

I would like to implement this by porting the old TabView with some upgrades but first I need this issue to be fixed - https://github.com/dotnet/maui/issues/6412

brminnick commented 2 years ago

@ChaplinMarchais What is the status of your progress? Would you like to continue working on TabView, or would you prefer to hand off the work to another developer?

Dreamescaper commented 2 years ago

@brminnick

For future reference, you don’t need to copy/paste any code from Xamarin.CommunityToolkit to get to work with .NET MAUI. We created Xamarin.CommunityToolkit.MauiCompat which is the exact Xamarin.CommunityToolkit library ported to .NET MAUI:

It doesn't work, unfortunately... https://github.com/xamarin/XamarinCommunityToolkit/issues/1873

And @VladislavAntonyuk mentions here, that it's not possible to use it at all, if I understand that correctly: https://github.com/xamarin/XamarinCommunityToolkit/issues/1750#issuecomment-985645536

ewerspej commented 2 years ago

@ChaplinMarchais @brminnick Will this component be compatible with Shell Navigation?

AdamDiament commented 2 years ago

Since it's not going to be in the first release, I downloaded the Xamarin community toolkit sample code and isolated the TabView and lazy view parts, then refactored them to work with maui. It took about 2 work days but wasn't too hard. Its really just a Grid with a carousel view and another grid (as the tab bar). The hard part was simulating stack navigation within a "tab"

@eamonn-alphin this sounds awesome. Do you have a code sample you would be happy to share please? I am still in XF but I have a large maui refactor on the horizon, so I am wary to add a dependency to something from the XF toolkit that will be unsupported when going to MAUI.

AdamDiament commented 2 years ago

btw for someone wanting a XF and Maui compatible solution now (Oct 2022) there is https://github.com/roubachof/Sharpnado.Tabs

ewerspej commented 2 years ago

btw for someone wanting a XF and Maui compatible solution now (Oct 2022) there is https://github.com/roubachof/Sharpnado.Tabs

That's indeed a very powerful implementation. However, it is not compatible with Shell and uses Views instead of Pages. I do not regard it as a substitute for the native Shell.TabBar

ChaplinMarchais commented 2 years ago

@ChaplinMarchais What is the status of your progress? Would you like to continue working on TabView, or would you prefer to hand off the work to another developer?

@brminnick Sorry about going totally MIA! Life ended up throwing some major personal things my way. Long story short I wasn't able to get access to a computer for the last 5 months or so. That being said, I will begin work again on the implementation for this proposal!

brminnick commented 2 years ago

@ChaplinMarchais glad to have you back!

ChaplinMarchais commented 1 year ago

@ChaplinMarchais @brminnick Will this component be compatible with Shell Navigation?

@ewerspej What exactly do you mean, as far as functionality goes, by "compatible"? Are you looking for the TabView to generate ShellContent items for the Shell or just to have the TabView integrate your navigation history with the Shell? A little bit more detail would help to determine the feasibility of integrating with this feature!

ewerspej commented 1 year ago

@ChaplinMarchais If this control should be a way to substitute the native TabBar, then it would make sense that it also taps into Shell's navigation so that the content of the TabView can be controlled using Routes/URIs using Shell.Current.GoToAsync(). That way it would fully integrate into Shell and can actually replace the native TabBar while still being able to use things like Shell's built-in dependency injection.

ChaplinMarchais commented 1 year ago

@ewerspej ok I am going to take a look at the Shell implementation over the next couple days, and see how exactly they are integrating with the NavigationManager and make sure there is nothing that is going to blow up in our faces. I know that currently the Shell is pretty limited as to what kind of Types it will accept as a navigation target. For instance I believe that we would have to provide the TabViewItem.Content as a Page rather than currently allowing it to be any IView implementation. That is the only thing that I have discovered so far that may possibly restrict us from integrating with it.

@brminnick do you have any further insight into the inner-workings of Shell or know who I could talk to about what would be required for us to integrate in a fully supported manner?

mouralabank commented 1 year ago

Any updates on this issue?

bijington commented 1 year ago

Any updates on this issue?

It doesn't look like it. Would you be willing to provide assistance?

VladislavAntonyuk commented 1 year ago

@ewerspej it would be easier to make shell looks like TabView rather then integrating navigation. Here is an example how you can customize shell appearance: https://vladislavantonyuk.github.io/articles/Customizing-.NET-MAUI-Shell

JRosanowski commented 1 year ago

I think trying to incorporate Shell will side-track this issue. If you want tabs these options are available but with limitations to Shell usage.

  1. TabbedPage https://learn.microsoft.com/en-us/dotnet/maui/user-interface/pages/tabbedpage
  2. Tab within Shell https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/shell/tabs

This proposed TabView would be a good addition and should be unrelated to Shell so there's freedom to use it elsewhere. For e.g. I want to use Shell and Flyout but I want tabs on one of the pages.

Kapusch commented 1 year ago

Hi @JRosanowski , were you able to validate for the second point ?

standamikes commented 1 year ago

Any updates on this Proposal?

VladislavAntonyuk commented 1 year ago

Hello @ChaplinMarchais! I see you referenced some commits with this issue. What is your progress with the issue? Could you create a PR with your implementation? Do you need any help?

smardine commented 1 year ago

I'll be happy to help too

VladislavAntonyuk commented 1 year ago

I would suggest to investigate if it can be achived using InticatorView and CarouselView. if yes, we can close this proposal. https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/indicatorview#define-indicator-appearance

bijington commented 1 year ago

I don't think the combination of CarouselView and IndicatorView will replicate the implementation from XCT. That being said the XCT had a lot of issues so I'm not against the idea of it does the bulk of the functionality

VladislavAntonyuk commented 1 year ago

possible solutions: https://github.com/VladislavAntonyuk/MauiSamples/tree/main/MauiTabView

vhugogarcia commented 1 year ago

I would suggest to investigate if it can be achived using InticatorView and CarouselView. if yes, we can close this proposal.

https://learn.microsoft.com/en-us/dotnet/maui/user-interface/controls/indicatorview#define-indicator-appearance

Yes, I can confirm 1000% that a tabview system can be achieved by using CarouselView and Indicator View. I have developed apps simulating tabs with this approach and work fantastic!!

Also I have developed an app using CollectionView with horizontal scrolling simulating the tabs, then on each item selected I perform UI changes to show or hide content views.

Additionally, CarouselView and CollectionView allows you to perform loading on-demand, this means that you don't have to load all tabs contents at first load.

Alert!: While developing the apps, there are some performance impacts when loading the information while switching the position in a carousel view. So, for simple tabs where you render only a ListView/CollectionView with no heavy data it is fine. However, if what it renders is heavy, then this approach may not work.

I just wanted to share my grain of salt.

cc: @VladislavAntonyuk

VladislavAntonyuk commented 1 year ago

@vhugogarcia let's discuss it on standup. I will show different implementations. and we can decide together if we can close it or not

vhugogarcia commented 1 year ago

@vhugogarcia let's discuss it on standup. I will show different implementations. and we can decide together if we can close it or not

sounds like a plan! Thanks @VladislavAntonyuk 😃

VladislavAntonyuk commented 1 year ago

TabView can be easily implemented using the combination of .NET MAUI controls, like CarouselView and IndicatorView; ContentView and RadioButton, HorizontalStackLayout and ContentView. The different implementations can be found here: https://github.com/VladislavAntonyuk/MauiSamples/tree/main/MauiTabView

sharpwood commented 1 year ago

可以使用 .NET MAUI 控件(如 CarouselView 和 IndicatorView)的组合轻松实现 TabView;ContentView 和 RadioButton、HorizontalStackLayout 和 ContentView。可以在此处找到不同的实现: https://github.com/VladislavAntonyuk/MauiSamples/tree/main/MauiTabView

Since it is easy to implement and there is a high demand for it, why not package it more comprehensively and include it in the community toolkit?

brminnick commented 1 year ago

@sharpwood Feel free to submit us a Pull Request, complete with Unit Tests and Documentation!

upswing1 commented 4 months ago

The toolkit would benefit greatly from having a tabview control instead of having thousands of custom implementations. A tabview control could then be used as the base control to expand functionality has a developer seems fit.