1

I've created a basic sample app with Xamarin.Forms, where I tried to repoduce a UI like Twitter or Instagram with:

  • a MasterDetail page as main page, where the default "hamburger" icon is replaced by a custom icon
  • a "bottom" TabbedPage as detail of the MasterDetail page
  • a control allowing to implement top Tabs in the TabbedPage
  • ContentPages that are hosted in NavigationPage for each tab of the TabbedPage

So, the the pages architecture of my app looks like this:

|-- MasterDetailPage
..|--TabbedPage
....|-- NavigationPage
......|-- ContentPage

For achieving this, I've used:

  • the Naxam BottomTabedPage, to implement "bottom" TabbedPage on Android (like BottomNavigationView)
  • the Syncfusion SfTabView control to implement the "top" Tabs
  • a custom renderer to manage the use of custom icon as "hamburger" icon on the main level, and the "arrow" icon on the childs levels

This renderer looks like this:

public class CustomNavigationPageRenderer : MasterDetailPageRenderer
{
    protected override void OnLayout(bool changed, int l, int t, int r, int b)
    {
        base.OnLayout(changed, l, t, r, b);
        var toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);

        for (var i = 0; i < toolbar.ChildCount; i++)
        {
            var imageButton = toolbar.GetChildAt(i) as ImageButton;
            var drawerArrow = imageButton?.Drawable as DrawerArrowDrawable;
            if (drawerArrow == null)
                continue;

            bool displayBack = false;
            var app = Xamarin.Forms.Application.Current;
            //var navPage = ((app.MainPage.Navigation.ModalStack[0] as MasterDetailPage).Detail as NavigationPage);
            var detailPage = (app.MainPage as MasterDetailPage).Detail;
            if (detailPage.GetType() == typeof(BottomTp.Views.NaxamMainPage))
            {
                var tabPage = detailPage as BottomTabbedPage;
                var curPage = tabPage.CurrentPage;
                var navPageLevel = curPage.Navigation.NavigationStack.Count;
                if (navPageLevel > 1)
                    displayBack = true;
            }

            if (!displayBack)
                ChangeIcon(imageButton, Resource.Drawable.icon);
        }
    }

    private void ChangeIcon(ImageButton imageButton, int id)
    {
        if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Lollipop)
            imageButton.SetImageDrawable(Context.GetDrawable(id));
        imageButton.SetImageResource(id);
    }
}

This works well, but there is a last problem when I "return" to the main page:

  • there is the default transition animation from arrow to "hamburger" icon
  • after this, the "hamburger" icon is replaced by my custom icon

Here is a short animation to illustrate this issue:

enter image description here

Is there a way to deactivate this animation? How could I fix this?

Machavity
  • 30,841
  • 27
  • 92
  • 100
Gold.strike
  • 1,269
  • 13
  • 47
  • Please refer to [this](https://stackoverflow.com/questions/27772072/disable-hamburger-to-arrow-animation), `public class MasterDetailPageRenderer : DrawerLayout`, so you can use `DrawerLayout` to achieve your goal. – Robbit Apr 20 '18 at 07:54
  • Hi @JoeLv-MSFT I've already seen this link, but I don't see how to use DrawerLayout in my renderer. – Gold.strike Apr 20 '18 at 08:18
  • Hi, [here](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/listview#creating-the-custom-renderer-on-android), in the `OnElementChanged` method, add the `DrawerClosed` and `DrawerOpened` event. Can you give me a demo? – Robbit Apr 20 '18 at 08:34
  • But there is no `OnElementChanged` method in the `MasterDetailPageRenderer`. – Gold.strike Apr 20 '18 at 08:38
  • Please check [here](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs). – Robbit Apr 20 '18 at 08:40
  • I'm not sure to understand your suggestion... – Gold.strike Apr 20 '18 at 09:15
  • There should be the `OnElementChanged` method, this [link](https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/AppCompat/MasterDetailPageRenderer.cs), line 249, you will find `protected virtual void OnElementChanged(VisualElement oldElement, VisualElement newElement)`, but why you said there is no `OnElementChanged` method? I am confused. – Robbit Apr 20 '18 at 09:19
  • I finally found `OnElementChanged` method: I thought this by mistake because this method isn't purposed defaultly when you type "`override`". – Gold.strike Apr 20 '18 at 09:28
  • So I've added `OnElementChanged` and I've added `DrawerClosed` and `DrawerOpened` like this: `this.DrawerClosed += delegate (object sender, Android.Support.V4.Widget.DrawerLayout.DrawerClosedEventArgs e) { OnDrawerClosed(); };` – Gold.strike Apr 20 '18 at 09:30
  • But it seems that the method `OnElementChanged` is never called when I place breakpoint... – Gold.strike Apr 20 '18 at 09:32
  • No, it should be called firstly, [here](https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/entry) is `EntryRenderer`, see this sentence "The EntryRenderer class exposes the OnElementChanged method, which is called when the Xamarin.Forms control is created to render the corresponding native control. " – Robbit Apr 20 '18 at 09:36
  • Ah sorry, the events are well treated. But only when we open/close the Drawer pane. So this doesn't taken into account the animation that is displayed during the "navigation"... – Gold.strike Apr 20 '18 at 09:37
  • Would you like provide a demo? – Robbit Apr 20 '18 at 09:41
  • The "demo" is still the same: the GIF that is displayed in the main post. You can also get this sample's [sources](https://github.com/Goldstrike/BottomTp). – Gold.strike Apr 20 '18 at 09:52
  • You will probably have to deactivate the Syncfusion control that is used in this sample. – Gold.strike Apr 20 '18 at 09:55

1 Answers1

2

You need use a custom NavigationPage. Here is my demo based on yours.

There are three steps to achieve it:

1) Add a folder named CustomControl, create a class MyNavigationPage Inherited from NavigationPage:

namespace BottomTp.CustomControl
{
    public class MyNavigationPage : NavigationPage
    {
        public MyNavigationPage() { }
        public MyNavigationPage(Page root) : base(root) { }
    }
}

2) change the tag in your NaxamMainPage.xaml file,

Add xmlns:controls="clr-namespace:BottomTp.CustomControl;assembly=BottomTp" in your naxam:BottomTabbedPage tag, and then change your NavigationPage tag to controls:MyNavigationPage:

<controls:MyNavigationPage Title="Browse" Icon="md-view-list"
                x:Name="NP1">
    <x:Arguments>
        <views:SfItemsPage   />
    </x:Arguments>
</controls:MyNavigationPage>
<controls:MyNavigationPage Title="About" Icon="md-help"
                x:Name="NP2">
    <x:Arguments>
        <views:AboutPage />
    </x:Arguments>
</controls:MyNavigationPage>

3) create custom render MyCustomNavigationPageRenderer on Android platform:

[assembly: ExportRenderer(typeof(MyNavigationPage), typeof(MyCustomNavigationPageRenderer))]
namespace BottomTp.Droid.Renderers
{
    class MyCustomNavigationPageRenderer : NavigationPageRenderer
    {
        public MyCustomNavigationPageRenderer(Context c) : base(c)
        { }

        protected override Task<bool> OnPopToRootAsync(Page page, bool animated)
        {
            return base.OnPopToRootAsync(page, false);
        }

        protected override Task<bool> OnPopViewAsync(Page page, bool animated)
        {
            return base.OnPopViewAsync(page, false);
        }

        protected override Task<bool> OnPushAsync(Page view, bool animated)
        {
            return base.OnPushAsync(view, false);
        }

    }
}
Robbit
  • 4,300
  • 1
  • 13
  • 29