Xamarin forms Add button in TabbedPage

Sep 30 2020

Eu tenho uma pergunta. Criei a seguinte 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>

Agora, em cada página, adicionei este FabMenu personalizado como este:

<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>

O problema é que cada página tem seu próprio FabMenu, então você o vê desaparecer e reaparecer em todas as páginas, então minha pergunta é: Existe algum tipo de visualização raiz que sobrepõe todas as guias na TabbedPage?

Por favor, deixe-me saber como faço isso!

Respostas

2 Deczaloth Oct 07 2020 at 10:29

aviso Legal

Eu descobri uma maneira de criar o efeito desejado usando apenas Xamarin.Forms puro. Leia e preste atenção às partes complicadas da solução.

Abstrato

Esta solução é conseguido implementar AbsoluteLayout, CarouselView, IndicatorViewe DataTemplateSelector. O Xamarin.Forms 4.8 é suposto a seguir. Se uma versão inferior for usada, leve em consideração que os recursos gostam CarouselViewou IndicatorViewpodem estar no status de Visualização .

DataTemplateSelector, CarouselViewe IndicatorViewsão usados ​​para simular um TabbedPage, e AbsoluteLayoutsão usados ​​para fornecer a sobreposição .

Então, agora com a solução:

Crie suas visualizações

Aqui você cria uma visualização para cada uma das páginas que deseja. Neste exemplo, quero que meu aplicativo consista em duas páginas, então crio duas visualizações (code behind permanece intocado):

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>

Criar uma DataTemplateSelector

Isso será usado pelo CarouselViewpara selecionar uma visualização ou outra dependendo da corrente 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;
        }
    }
}

Crie sua página principal

Página1.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>

E no código atrás, definimos o nome das guias. Aqui, por favor, preste atenção no fato de que estou supondo uma árvore de elementos de a StackLayout-> Frame-> Label. Se você alterar o IndicatorTemplate, terá que modificar também esta parte do código!

Página1.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++;
                    }
                }
            };

        }
    }

}

Por fim, defina essa página como a propriedade MainPage do App:

public App()
{
    InitializeComponent();

    MainPage = new Page1();
}

O resultado final é assim:

LucasZhang-MSFT Oct 01 2020 at 02:05

Como solução alternativa, você pode definir ToolbarItem de cada ContentPage (ou pode definir uma ContentPage base).

<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>
BrandonMinnick Oct 06 2020 at 17:24

Recomendo criar um BaseContentPageque inclua um static FloatingButton. Isso permite que cada página herde BaseContentPagee use a mesma FloatingButton.

Código

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");
}

LabelPage de exemplo

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

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

                Button
            }
        }
    }
}

Exemplo de ButtonPage

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

        Content = Button;
    }
}

App de exemplo

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

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

App de amostra

Aqui está o aplicativo de amostra usado para criar o GIF anexado: https://github.com/brminnick/TabbedPageButton/