//BottomTabbedPageExtensions.cs
using Xamarin.Forms;
namespace SchedulingTool.Helpers
{
public class BottomTabbedPageExtensions
{
public static readonly BindableProperty TabColorProperty = BindableProperty.CreateAttached(
"TabColor",
typeof(Color),
typeof(BottomTabbedPageExtensions),
Color.Transparent);
public static readonly BindableProperty BadgeCountProperty = BindableProperty.CreateAttached( "BadgeCount", typeof(int), typeof(BottomTabbedPageExtensions), 0); public static readonly BindableProperty BadgeColorProperty = BindableProperty.CreateAttached( "BadgeColor", typeof(Color), typeof(BottomTabbedPageExtensions), Colors.OrangeColor); public static readonly BindableProperty IsTabVisibleProperty = BindableProperty.CreateAttached( "IsTabVisible", typeof(bool), typeof(BottomTabbedPageExtensions), true); public static void SetIsTabVisible(BindableObject bindable, bool visible) { bindable.SetValue(IsTabVisibleProperty, visible); } public static bool GetIsTabVisible(BindableObject bindable) { return (bool)bindable.GetValue(IsTabVisibleProperty); } public static void SetTabColor(BindableObject bindable, Color color) { bindable.SetValue(TabColorProperty, color); } public static Color GetTabColor(BindableObject bindable) { return (Color)bindable.GetValue(TabColorProperty); } public static void SetBadgeCount(BindableObject bindable, int badgeCount) { bindable.SetValue(BadgeCountProperty, badgeCount); } public static int GetBadgeCount(BindableObject bindable) { return (int)bindable.GetValue(BadgeCountProperty); } public static void IncreaseBadgeCountBy(BindableObject bindable, int increaseBy) { int currentValue = GetBadgeCount(bindable); if(currentValue == 0 && increaseBy < 0) { bindable.SetValue(BadgeCountProperty, 0); } if(increaseBy < 0 && (increaseBy > currentValue)) { bindable.SetValue(BadgeCountProperty, 0); } bindable.SetValue(BadgeCountProperty, currentValue + increaseBy); } public static void SetBadgeColor(BindableObject bindable, Color color) { bindable.SetValue(BadgeColorProperty, color); } public static Color GetBadgeColor(BindableObject bindable) { return (Color)bindable.GetValue(BadgeColorProperty); } }
}
//DroidBottomTabbedPageRenderer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Android.Content;
using Android.Support.Design.Widget;
using Android.Views;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android.AppCompat;
using View = Android.Views.View;
using AndroidRelativeLayout = Android.Widget.RelativeLayout;
using RelativeLayoutParams = Android.Widget.RelativeLayout.LayoutParams;
using Android.Support.Design.Internal;
using Xamarin.Forms.Platform.Android;
using System.ComponentModel;
using Android.Widget;
using Android.Graphics.Drawables;
using Android.Graphics.Drawables.Shapes;
using Android.Util;
using Android.Support.V4.View;
[assembly: ExportRenderer(typeof(ExtendedBottomTabbedPage), typeof(DroidBottomTabbedPageRenderer))]
namespace SchedulingTool.Droid.Renderers
{
public class DroidBottomTabbedPageRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener
{
IDictionary<Page, string> _formsBadges;
List _androidBadges;
bool _isShiftModeSet; int _l, _t, _r, _b, _width, _height, _tabsHeight; bool _firstTime; int _bottomBarHeight; Context _context; TabLayout _topBar; TabbedPage _tabbedPage; BottomNavigationView _bottomBar; AndroidRelativeLayout _container; RelativeLayoutParams _layoutParams; List<BottomNavigationItemView> _tabBarItems; ExtendedBottomTabbedPage _extendedTabbedPage; public DroidBottomTabbedPageRenderer(Context context) : base(context) { _context = context; } protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e) { base.OnElementChanged(e); if (e.NewElement != null) { _tabbedPage = e.NewElement; _extendedTabbedPage = (ExtendedBottomTabbedPage)_tabbedPage; _firstTime = true; _tabBarItems = new List<BottomNavigationItemView>(); _androidBadges = new List<TextView>(); var children = GetAllChildViews(ViewGroup); foreach (var bottomNavItemView in children) { if (bottomNavItemView is BottomNavigationItemView) { var tab = (BottomNavigationItemView)bottomNavItemView; _tabBarItems.Add(tab); AddBadge(tab); } } if (children.SingleOrDefault(x => x is BottomNavigationView) is BottomNavigationView bottomNav) { _bottomBar = bottomNav; _bottomBar.SetOnNavigationItemReselectedListener(this); } if(children.SingleOrDefault(x => x is AndroidRelativeLayout) is AndroidRelativeLayout container) { _container = container; } if (children.SingleOrDefault(x => x is TabLayout) is TabLayout topNav) { _topBar = topNav; } SetTabBadges(); AddPropertyChangedHandlersForPages(); } } protected override void OnLayout(bool changed, int l, int t, int r, int b) { try { base.OnLayout(changed, l, t, r, b); _width = r - l; _height = b - t; _tabsHeight = Math.Min(_height, Math.Max(_bottomBar.MeasuredHeight, _bottomBar.MinimumHeight)); _l = l; _t = t; _r = r; _b = b; if (!_isShiftModeSet) { _bottomBar.SetShiftMode(false, false); _isShiftModeSet = true; } } catch (Exception ex) { ExceptionHandler.LogException(this, nameof(OnLayout), ex); } } protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { base.OnElementPropertyChanged(sender, e); if (e.PropertyName == nameof(ExtendedBottomTabbedPage.BottomTabBarHidden)) { HideTabbedPage(); } } public async void OnNavigationItemReselected(IMenuItem item) { await _extendedTabbedPage.CurrentPage.Navigation.PopToRootAsync(); } List<View> GetAllChildViews(View view) { if (!(view is ViewGroup group)) { return new List<View> { view }; } var result = new List<View>(); for (int i = 0; i < group.ChildCount; i++) { var child = group.GetChildAt(i); var childList = new List<View> { child }; childList.AddRange(GetAllChildViews(child)); result.AddRange(childList); } return result.Distinct().ToList(); } void AddPropertyChangedHandlersForPages() { foreach (var page in _extendedTabbedPage.Children) { page.PropertyChanged += OnPagePropertyChanged; } } void OnPagePropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == BottomTabbedPageExtensions.BadgeCountProperty.PropertyName) { var page = (Page)sender; UpdateBadgeForPage(page); } } void SetTabBadges() { var tabCount = _tabbedPage.Children.Count(); _formsBadges = new Dictionary<Page, string>(tabCount); for (var i = 0; i < tabCount; i++) { var page = _tabbedPage.Children[i]; } } void AddBadge(BottomNavigationItemView frame) { View badge = LayoutInflater.From(_context).Inflate(Resource.Layout.NotificationBadge, frame, false); frame.AddView(badge); TextView textViewBadge = (TextView)badge.FindViewById(Resource.Id.notifications_badge); var backgroundShape = CreateBackgroundShape(); backgroundShape.Paint.Color = Colors.OrangeColor.ToAndroid(); ViewCompat.SetBackground(textViewBadge, backgroundShape); _androidBadges.Add(textViewBadge); } void UpdateBadgeForPage(Page page) { if (_tabbedPage == null) return; var pageIndex = _tabbedPage.Children.IndexOf(page); var badgeCount = BottomTabbedPageExtensions.GetBadgeCount(page); if (!_formsBadges.ContainsKey(page)) { _formsBadges.Add(page, page.Title); } var badge = _androidBadges[pageIndex]; var tab = _tabBarItems[pageIndex]; if (badgeCount <= 0) { badge.Visibility = ViewStates.Gone; return; } badge.Visibility = ViewStates.Visible; badge.Text = badgeCount > 99 ? "99+" : badgeCount.ToString(); } void HideTabbedPage() { if (_firstTime) { _layoutParams = (RelativeLayoutParams)_bottomBar.LayoutParameters; _l = _layoutParams.LeftMargin; _t = _layoutParams.TopMargin; _r = _layoutParams.RightMargin; _b = _layoutParams.BottomMargin; _bottomBarHeight = _layoutParams.Height; _firstTime = false; } if (_extendedTabbedPage.BottomTabBarHidden) { _layoutParams.Height = 0; _bottomBar.LayoutParameters = _layoutParams; //_topBar.Visibility = ViewStates.Gone; //_bottomBar.LayoutParameters = new global::Android.Widget.RelativeLayout.LayoutParams(0,0); //_container.Invalidate(); //_bottomBar.Visibility = ViewStates.Gone; //Measure(MeasureSpecFactory.MakeMeasureSpec(_width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(_tabsHeight, MeasureSpecMode.Exactly)); //Layout(_l, _t, _r, _b); } else { _layoutParams.Height = _bottomBarHeight; _bottomBar.LayoutParameters = _layoutParams; //_topBar.Visibility = ViewStates.Visible; //_container.Invalidate(); //_bottomBar.Visibility = ViewStates.Visible; //Measure(MeasureSpecFactory.MakeMeasureSpec(_width, MeasureSpecMode.Exactly), MeasureSpecFactory.MakeMeasureSpec(_tabsHeight, MeasureSpecMode.Exactly)); //Layout(_l, _t, _r, _b); } } ShapeDrawable CreateBackgroundShape() { var radius = DpToPixels(12); var outerR = new float[] { radius, radius, radius, radius, radius, radius, radius, radius }; return new ShapeDrawable(new RoundRectShape(outerR, null, null)); } int DpToPixels(float dip) { return (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, dip, Resources.DisplayMetrics); } } public static class AndroidHelpers { public static void SetShiftMode(this BottomNavigationView bottomNavigationView, bool enableShiftMode, bool enableItemShiftMode) { try { var menuView = bottomNavigationView.GetChildAt(0) as BottomNavigationMenuView; if (menuView == null) { System.Diagnostics.Debug.WriteLine("Unable to find BottomNavigationMenuView"); return; } var shiftMode = menuView.Class.GetDeclaredField("mShiftingMode"); shiftMode.Accessible = true; shiftMode.SetBoolean(menuView, enableShiftMode); shiftMode.Accessible = false; shiftMode.Dispose(); for (int i = 0; i < menuView.ChildCount; i++) { var item = menuView.GetChildAt(i) as BottomNavigationItemView; if (item == null) continue; item.SetShiftingMode(enableItemShiftMode); item.SetChecked(item.ItemData.IsChecked); } menuView.UpdateMenuView(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Unable to set shift mode: {ex}"); } } }
}
//ExtendedBottomTabbedPage.cs
using Xamarin.Forms;
namespace SchedulingTool.Renderers
{
public class ExtendedBottomTabbedPage : TabbedPage
{
#region Properties & Commands
public static readonly BindableProperty TabBarHiddenProperty = BindableProperty.Create(nameof(BottomTabBarHidden), typeof(bool), typeof(ExtendedBottomTabbedPage), false); public bool BottomTabBarHidden { get { return (bool)GetValue(TabBarHiddenProperty); } set { SetValue(TabBarHiddenProperty, value); } } public enum BarThemeTypes { Light, DarkWithAlpha, DarkWithoutAlpha } public BarThemeTypes BarTheme { get; set; } public bool FixedMode { get; set; } #endregion #region Methods public void RaiseCurrentPageChanged() { OnCurrentPageChanged(); } #endregion }
}