Search code examples
android.netxamarinxamarin.formsxamarin-shell

Add a common Element that should always be visible for Xamarin Forms Shell


I want to make a layout like this:

enter image description here

I am using Shell with Tab (no Flyout for now) and I don't know how I can achieve this. I tried creating a Page but obviously it doesn't work:

    <ContentPage.Content>
        <StackLayout>
            <root:AppShell />
            <Label Text="My Content" />
        </StackLayout>
    </ContentPage.Content>

Is there a way to wrap the content/Shell with something? Also it's preferably that that content should be above the Tabs but it's okay to be below as well.


Solution

  • You could use Shell CustomRender to achieve this.

    Create TodoTabBar in the forms project:

    public class TodoTabBar : TabBar
      {
        public StackLayout BottomLayout { get; set; }
      }
    

    then set in the AppShell.xaml:

     <c:TodoTabBar> 
        <c:TodoTabBar.BottomLayout>
          <StackLayout  HorizontalOptions="StartAndExpand" HeightRequest="200">
              <Label Text="I'm bottomview"></Label>
          </StackLayout>
        </c:TodoTabBar.BottomLayout>
    
        <Tab ..>
            <ShellContent ContentTemplate="..." />
        </Tab>
    
        <Tab ...>
            <ShellContent ContentTemplate="..." />
        </Tab>
    </c:TodoTabBar>
    

    create TodoShellRenderer and TodoShellItemRenderer in your Android project:

    public class TodoShellRenderer : ShellRenderer
    {
        public TodoShellRenderer(Context context) : base(context)
        {
        }
    
        protected override IShellItemRenderer CreateShellItemRenderer(ShellItem shellItem)
        {
            return new TodoShellItemRenderer(this);
        }
    }
    

    and

    public class TodoShellItemRenderer : ShellItemRenderer
    {
        FrameLayout _shellOverlay;
        BottomNavigationView _bottomView;
        public TodoShellItemRenderer(IShellContext shellContext) : base(shellContext)
        {
        }
    
        public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            var outerlayout = base.OnCreateView(inflater, container, savedInstanceState);
            _bottomView = outerlayout.FindViewById<BottomNavigationView>(Resource.Id.bottomtab_tabbar);
            _shellOverlay = outerlayout.FindViewById<FrameLayout>(Resource.Id.bottomtab_tabbar_container);
    
            if (ShellItem is TodoTabBar todoTabBar && todoTabBar.BottomLayout != null)
                SetupBottomLayout();
    
            return outerlayout;
        }
    
        private async void SetupBottomLayout()
        {
            var todoTabBar = (TodoTabBar)ShellItem;
            var layout = new FrameLayout(Context);
    
            var stackLayout = todoTabBar.BottomLayout;
    
            var size = new Rectangle(0, 0, Context.Resources.DisplayMetrics.WidthPixels, stackLayout.HeightRequest);
    
            var vRenderer = RendererFactory.GetRenderer(stackLayout);
            var viewGroup = vRenderer.ViewGroup;
            vRenderer.Tracker.UpdateLayout();
            var layoutParams = new ViewGroup.LayoutParams((int)size.Width, (int)size.Height);
            viewGroup.LayoutParameters = layoutParams;
            stackLayout.Layout(size);
            viewGroup.Layout(0, 0, (int)stackLayout.WidthRequest, (int)stackLayout.HeightRequest);
            layout.AddView(viewGroup);
            _shellOverlay.RemoveAllViews();
            _shellOverlay.AddView(layout);
        }
     }
    

    the BottomTabLayout.xml (this is what we should replace the defaul xml,for this to work the IDs in the layout must match exactly what was in the Xamarin Androidn Platform layout):

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="match_parent">
    
        <LinearLayout
           android:layout_above="@id/bottomtab.tabbar.container"
           android:orientation="vertical"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
      
        <FrameLayout
           android:id="@+id/bottomtab.navarea"
           android:layout_width="match_parent"
           android:layout_height="0dp"
           android:layout_gravity="fill"
           android:layout_weight="1" />
        
        <com.google.android.material.bottomnavigation.BottomNavigationView
           android:id="@+id/bottomtab.tabbar"
           android:theme="@style/Widget.Design.BottomNavigationView"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"/>
    
        </LinearLayout>
    
        <FrameLayout
           android:id="@+id/bottomtab.tabbar.container"
           android:background="#f00"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_alignParentBottom="true"
         />
    
    </RelativeLayout>
    

    Inspired by the link from Can I add a static view above the tabbar in Xamarin Forms Shell?.