dewango / BottomNavigationBarXF

Bottom Navigation Bar for Xamarin Forms
MIT License
187 stars 97 forks source link

Use Fragments for smoother page transitions #76

Open AlexanderMelchers opened 6 years ago

AlexanderMelchers commented 6 years ago

Hi all,

With the current implementation of the BottomNavigationBarXF, switching between tabs/pages is rather slow the first time a page/tab is opened, causing a couple of seconds of blank (background-coloured) screen to appear for the duration that the page's underlying ViewGroup is laid out and drawn. This delay, though small, is rather detrimental to our overall user-experience, as the issue does not occur in the iOS-version of our App, nor when using the traditional Xamarin-tabs on Android.

However, taking lead from the latter, the use of Fragments significantly speeds up page/tab load times and provides an overall smoother transition.

To make use of this functionality, however, you'll need to convert Page-objects into Fragments, which, as far as I know, is not possible in Xamarin, unless by way of the Xamarin-internal FragmentContainer-class. One can obtain access to this class using the following code reflection, however:

Type referenceType = typeof(Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer),
    fragmentContainerType =
        referenceType.Assembly.GetType($"{referenceType.Namespace}.{typeof(FragmentContainer).Name}");
if (fragmentContainerType == default(Type))
{
    throw new TypeLoadException($"Unable to find the fragment container type '{referenceType.Namespace}.{typeof(FragmentContainer).Name}'.");
}

return fragmentContainerType.GetMethods().FirstOrDefault(method => method.Name == nameof(Activator.CreateInstance));

This will result in a MethodInfo-object that can be used to instantiate the needed fragments. Instantiation (suggested to cache the results somehow) now simply proceeds as follows:

(Fragment) FragmentContainerCreateInstance.Invoke(default(object), new object[] {page})

Now that the scaffolding has been set up, we can simply override the SwitchContent-method to achieve fragment-based tab-paging:

protected override void SwitchContent(Page view)
{
    int tabIndex = this.BottomBar.CurrentTabPosition;

    FragmentTransaction pageSwitch = this.FragmentManager.BeginTransaction();
    pageSwitch.Replace(BottomNavigationBar.Resource.Id.bb_user_content_container, this.ContentFragments[tabIndex]);
    pageSwitch.Commit();
}

Thus, although I do realize there is some assembly required, the above code should help you obtain smoother tab-transitions using the bottom navigation bar. Though I have spotted some minor issues that derive from this mechanism (for instance, an image load event seems to trigger again unnecessarily when switching back and forth between two tabs), I still consider it an improvement over the current implementation - at least for our App. Hope you find it useful too!

csampaio26 commented 6 years ago

What is that this.FragmentManager? Can you explain please?

AlexanderMelchers commented 6 years ago
private static readonly Lazy<FragmentManager> LazyFragmentManager =
    new Lazy<FragmentManager>(() => ((FormsAppCompatActivity) Forms.Context).SupportFragmentManager);

private FragmentManager FragmentManager => LazyFragmentManager.Value;

The fragment manager is a native Android class (Android.Support.V4.App.FragmentManager) especially designed for working with view fragments, and that I've been using to implement transactioned transitions between the tab-pages accessible through the bottom navigation bar, as the fragment manager does some pre-rendering of the fragments it manages. As a result, page transitions become a lot smoother, as load-operations are not visable on-screen.

nhdanh commented 6 years ago

Do you have a sample project on github ?

AlexanderMelchers commented 6 years ago

Hi @nhdanh,

I'm afraid that the project I implemented this for is a proprietary product, which I'm not at liberty to disclose. However, the basic steps can be surmised as that you need to create your own implementation of and based upon the BottomBarPageRenderer in this project. The SwitchContent-method referred to above is an override of this renderer.

I have since deviated from the above implementation, though, and am now using a ViewPager (which internally still uses fragments through a FragmentPagerAdapter), which is what Xamarin.Forms uses for their implementation of TabbedPageRenderer as well (their source can be found online: https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs). My implementation mimics the general structure of this TabbedPageRenderer, and I can only advise you to have a good look at this code (especially concerning the ViewPager), and check both the source code on this Github and that of the BottomBar itself to see how best to hook it all up.

Good luck!