2
votes

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!

3
I just want to say most of these replies are NOT true. Adding a BUTTON to a TABBED PAGE is POSSIBLE (although not easy, you could say) using custom renderers. Refer to this question: linkuser14090664
I mean a button that doesn't disappear when you switch tabs, so an overlay for the tabs. The link is for a image inside the gabbar, but that is not what I meanA. Vreeswijk
I think in your case ToolBarItem is a good workaroundLucas Zhang
It is most certainly possible. I recommend using a static instance of the FloatingMenu. @LucasZhang-MSFT - As a fellow Microsoft employee, I highly recommend that you avoid saying something is "impossible" unless you know with 100% certainty that is cannot be done. A better response is "I am not aware how to achieve this" or no response at all. We both represent Microsoft and the advice we give our fellow developers carries a lot of weight and will be received as truth.Brandon Minnick
Tabbed Page is a just a container of ContentPage you need to add the Floating Button on each ContentPage.Lucas Zhang

3 Answers

2
votes

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

0
votes

As a workaround, you could set ToolbarItem of each ContentPage (or you can define a base ContentPage).

<ContentPage.ToolbarItems>
    <ToolbarItem Text="Example Item"
             IconImageSource="xxx.png"
             Order="Secondary"
             Clicked="{Binding xx}"
             Priority="0" />

    <ToolbarItem Text="Example Item"
             IconImageSource="xxx.png"
             Order="Secondary"
             Priority="1" />

    <ToolbarItem Text="Example Item"
             IconImageSource="xxx.png"
             Order="Secondary"
             Priority="2" />

</ContentPage.ToolbarItems>
0
votes

I recommend creating a BaseContentPage that includes a static FloatingButton. This allows every page to inherit from BaseContentPage and use the same FloatingButton.

Code

BaseContentPage

abstract class BaseContentPage : ContentPage
{
    protected static Button Button { get; } = new Button { Text = $"This button was created at {DateTimeOffset.UtcNow}" }.Invoke(button => button.Clicked += HandleButtonClicked);

    static async void HandleButtonClicked(object sender, EventArgs e) =>
        await Application.Current.MainPage.DisplayAlert("Button Clicked", "This is the same button on both pages", "OK");
}

Example LabelPage

class LabelPage : BaseButtonPage
{
    public LabelPage()
    {
        Title = "LabelPage";

        Content = new StackLayout
        {
            Children =
            {
                new Label { Text = "Label Page" }.TextCenter().Center(),

                Button
            }
        }
    }
}

Example ButtonPage

class ButtonPage : BaseButtonPage
{
    public ButtonPage()
    {
        Title = "ButtonPage";

        Content = Button;
    }
}

Example App

public class App : Application
{
    public App()
    {
        Device.SetFlags(new[] { "Markup_Experimental" });

        MainPage = new TabbedPage
        {
            Children =
            {
                new ButtonPage(),
                new LabelPage()
            }
        };
    }
}

Sample App

Here is the sample app used to create the attached GIF: https://github.com/brminnick/TabbedPageButton/