Search code examples
xamlxamarinxamarin.formsxamarin.androidxamarin.ios

Xamarin forms Add button in TabbedPage


I have a question. I created the following TabbedPage:

<?xml version="1.0" encoding="utf-8" ?>
<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:MyApp.Views"
            mc:Ignorable="d"
            x:Class="MyApp.Views.MainPage"
            xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
            android:TabbedPage.ToolbarPlacement="Bottom"
            BarBackgroundColor="White"
            BarTextColor="Black"
            android:TabbedPage.BarItemColor="#B2B2B2"
            android:TabbedPage.BarSelectedItemColor="#56D7A5"
            android:TabbedPage.IsSwipePagingEnabled="False">    

    <TabbedPage.Children>
        <NavigationPage Title="page1" IconImageSource="navbar_page1">
            <x:Arguments>
                <views:page1 NavigationPage.HasNavigationBar="False" />
            </x:Arguments>
        </NavigationPage>

        <NavigationPage Title="page2" IconImageSource="navbar_page2">
            <x:Arguments>
                <views:page2 NavigationPage.HasNavigationBar="False" />
            </x:Arguments>
        </NavigationPage>

        <NavigationPage Title="page3" IconImageSource="navbar_page3">
            <x:Arguments>
                <views:page3 NavigationPage.HasNavigationBar="False" />
            </x:Arguments>
        </NavigationPage>
    
</TabbedPage>

Now on every page I have added this custom FabMenu like this:

<c:FloatingMenu Margin="0, 0, 10, 10" BGColor="#56D7A5" OpenIcon="openFab_icon" CloseIcon="closeFab_icon"
                AbsoluteLayout.LayoutBounds=".95,.95" AbsoluteLayout.LayoutFlags="PositionProportional">
    <c:FloatingButton x:Name="btnAddHomework" BGColor="#59E1FF" IconSrc="add_homework_icon" OnClickCommand="{Binding btnAddHomeworkCommand}" />
    <c:FloatingButton x:Name="btnAddDeadline" BGColor="#0FF1A0" IconSrc="add_deadline_icon"/>
    <c:FloatingButton x:Name="btnAddTest" BGColor="#5988FF" IconSrc="add_test_icon"/>
</c:FloatingMenu>

The problem is that every page has his own FabMenu, so you see it dissapear and reappear on every page, so my question is: Is there some kind of root view that overlays all the tabs in the TabbedPage?

Please let me know how I do that!


Solution

  • Disclaimer

    I came up with a way to create the effect wanted using only pure Xamarin.Forms. Read along and pay attention to the tricky parts of the solution.

    Abstract

    This solution is achieved implementing AbsoluteLayout, CarouselView, IndicatorView and DataTemplateSelector. Xamarin.Forms 4.8 is supposed in what follows. If a lower version is used, please take into account that features like CarouselView or IndicatorView could be in Preview status.

    DataTemplateSelector, CarouselView and IndicatorView are used to simulate a TabbedPage, and AbsoluteLayout is used to provide the Overlay.

    So, now with the solution:

    Create your Views

    Here you create a view for each of the pages you want. In this example i want my application to consist of two pages, so i create two views (code behind remains untouched):

    View1.xaml

    <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="overlayTest.View1"
                 BackgroundColor="Black">
      <ContentView.Content>
          <StackLayout>
                <Label Text="Welcome to Xamarin.Forms 1!"
                       TextColor="White"
                    VerticalOptions="CenterAndExpand" 
                    HorizontalOptions="CenterAndExpand" />
            </StackLayout>
      </ContentView.Content>
    </ContentView>
    

    View2.xaml

    <?xml version="1.0" encoding="UTF-8"?>
    <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 x:Class="overlayTest.View2">
      <ContentView.Content>
          <StackLayout>
                <Label Text="Welcome to Xamarin.Forms 2!"
                    VerticalOptions="CenterAndExpand" 
                    HorizontalOptions="CenterAndExpand" />
            </StackLayout>
      </ContentView.Content>
    </ContentView>
    

    Create a DataTemplateSelector

    This will be used by the CarouselView in order to select one view or the other depending on the current Position.

    using System;
    using Xamarin.Forms;
    
    namespace overlayTest
    {
        class MyTemplateSelector : DataTemplateSelector
        {
    
            readonly DataTemplate view1, view2;
    
            public MyTemplateSelector()
            {
                view1 = new DataTemplate(typeof(View1));
                view2 = new DataTemplate(typeof(View2));
            }
    
            protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
            {
                String s = item.ToString();
                if(s == "1")
                {
                    return view1;
                }
                
                return view2;
            }
        }
    }
    

    Create your Main Page

    Page1.xaml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:t="clr-namespace:overlayTest"
                 x:Class="overlayTest.Page1">
        <ContentPage.Resources>
            <ResourceDictionary>
                <t:MyTemplateSelector x:Key="templateSelector"/>
            </ResourceDictionary>
        </ContentPage.Resources>
        <ContentPage.Content>
            <AbsoluteLayout>
                <StackLayout AbsoluteLayout.LayoutBounds="0,0,1,1"
                         AbsoluteLayout.LayoutFlags="All"
                         Padding="0"
                         Spacing="0">
                    <CarouselView ItemTemplate="{StaticResource templateSelector}"
                              IndicatorView="indicatorView">
                        <CarouselView.ItemsSource>
                            <x:Array Type="{x:Type x:String}">
                                <x:String>1</x:String>
                                <x:String>2</x:String>
                            </x:Array>
                        </CarouselView.ItemsSource>
                    </CarouselView>
    
                    <IndicatorView x:Name="indicatorView">
                        <IndicatorView.IndicatorTemplate>
                            <DataTemplate>
                                <StackLayout HorizontalOptions="FillAndExpand">
                                    <Frame Margin="10">
                                        <Label/>
                                    </Frame>
                                </StackLayout>
                            </DataTemplate>
                        </IndicatorView.IndicatorTemplate>
                    </IndicatorView>
    
                </StackLayout>
    
                <ContentView 
                         IsVisible="True" VerticalOptions="Start"
                         AbsoluteLayout.LayoutBounds="0,0,1,1"
                         AbsoluteLayout.LayoutFlags="All"
                         BackgroundColor="Transparent">
                    <Frame CornerRadius="10"
                       Margin="20"
                       VerticalOptions="StartAndExpand"
                       HorizontalOptions="CenterAndExpand" InputTransparent="False">
                        <StackLayout Padding="0">
                            <Label 
                               FontSize="Medium"
                               TextColor="Black"/>
    
                            <StackLayout Orientation="Horizontal"
                                     HorizontalOptions="CenterAndExpand">
                                <Label Text="I am floating here"/>
                                <Switch IsToggled="True" />
                            </StackLayout>
    
    
                            <Button Text="Save"
                                   BackgroundColor="Accent"/>
                        </StackLayout>
                    </Frame>
                </ContentView>
            </AbsoluteLayout>
        </ContentPage.Content>
    </ContentPage>
    

    And in the code behind we set the name of the tabs. Here please put attention in the fact that i am supposing an element tree of a StackLayout -> Frame -> Label. If you change the IndicatorTemplate, you will have to also modify this part of the code!

    Page1.xaml.cs

    using System.Linq;
    
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    namespace overlayTest
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class Page1 : ContentPage
        {
            public Page1()
            {
                
                InitializeComponent();
    
                indicatorView.PropertyChanged += (s, a) =>
                {
                    if (a.PropertyName == IndicatorView.HeightProperty.PropertyName)
                    {
                        var indicators = indicatorView.IndicatorLayout.Children.ToList();
    
                        int counter = 0;
    
                        foreach(var indicator in indicators)
                        {
                            var indicatorBaseStack = (StackLayout)indicator;
                            var indicatorFrame = (Frame)indicatorBaseStack.Children[0];
                            var indicatorFrameLabel = (Label)indicatorFrame.Content;
    
                            indicatorFrameLabel.Text = counter == 0 ? "View1" : "View2";
                            counter++;
                        }
                    }
                };
    
            }
        }
    
    }
    

    Finally set that Page to the MainPage property of App:

    public App()
    {
        InitializeComponent();
    
        MainPage = new Page1();
    }
    

    The final result looks like this: enter image description here