Skip to content

Commit

Permalink
centralize bottom nav behaviors so all the bottom navs work the same (x…
Browse files Browse the repository at this point in the history
…amarin#5904)

* Reuse Bottom Nav more behavior for non shell

* - apply issues comments
  • Loading branch information
PureWeen authored Sep 27, 2019
1 parent a37c8b5 commit 7311345
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 93 deletions.
100 changes: 59 additions & 41 deletions Xamarin.Forms.Platform.Android/AppCompat/TabbedPageRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using AMenu = Android.Views.Menu;
using AColor = Android.Graphics.Color;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Xamarin.Forms.Platform.Android.AppCompat
{
Expand Down Expand Up @@ -463,7 +464,6 @@ void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs
else
{
SetupBottomNavigationView(e);
UpdateBottomNavigationViewIcons();
bottomNavigationView.SetOnNavigationItemSelectedListener(this);
}

Expand Down Expand Up @@ -631,54 +631,37 @@ void UpdateTabBarTranslation(int position, float offset)
}
}

void SetupBottomNavigationView(NotifyCollectionChangedEventArgs e)
List<(string title, ImageSource icon, bool tabEnabled)> CreateTabList()
{
if (IsDisposed)
return;

BottomNavigationView bottomNavigationView = _bottomNavigationView;

int startingIndex = 0;

if (e.Action == NotifyCollectionChangedAction.Add && e.NewStartingIndex == bottomNavigationView.Menu.Size())
startingIndex = e.NewStartingIndex;
else if (e.Action == NotifyCollectionChangedAction.Remove && (e.OldStartingIndex + 1) == bottomNavigationView.Menu.Size())
{
startingIndex = Element.Children.Count;
bottomNavigationView.Menu.RemoveItem(e.OldStartingIndex);
}
else
bottomNavigationView.Menu.Clear();

var items = new List<(string title, ImageSource icon, bool tabEnabled)>();

for (var i = startingIndex; i < Element.Children.Count; i++)
for (int i = 0; i < Element.Children.Count; i++)
{
Page child = Element.Children[i];
var menuItem = bottomNavigationView.Menu.Add(AMenu.None, i, i, child.Title);
if (Element.CurrentPage == child)
bottomNavigationView.SelectedItemId = menuItem.ItemId;
var item = Element.Children[i];
items.Add((item.Title, item.IconImageSource, item.IsEnabled));
}

if (Element.CurrentPage == null && Element.Children.Count > 0)
Element.CurrentPage = Element.Children[0];
return items;
}

void UpdateBottomNavigationViewIcons()
void SetupBottomNavigationView(NotifyCollectionChangedEventArgs e)
{
if (IsDisposed)
return;

BottomNavigationView bottomNavigationView = _bottomNavigationView;
var currentIndex = Element.Children.IndexOf(Element.CurrentPage);
var items = CreateTabList();

for (var i = 0; i < Element.Children.Count; i++)
{
Page child = Element.Children[i];
var menuItem = bottomNavigationView.Menu.GetItem(i);
_ = this.ApplyDrawableAsync(child, Page.IconImageSourceProperty, Context, icon =>
{
menuItem.SetIcon(icon);
});
}
BottomNavigationViewUtils.SetupMenu(
_bottomNavigationView.Menu,
_bottomNavigationView.MaxItemCount,
items,
currentIndex,
_bottomNavigationView,
Context);

if (Element.CurrentPage == null && Element.Children.Count > 0)
Element.CurrentPage = Element.Children[0];
}

void UpdateTabIcons()
Expand Down Expand Up @@ -868,11 +851,46 @@ public bool OnNavigationItemSelected(IMenuItem item)
if (Element == null || IsDisposed)
return false;

int selectedIndex = item.Order;
if (_bottomNavigationView.SelectedItemId != item.ItemId && Element.Children.Count > selectedIndex && selectedIndex >= 0)
var id = item.ItemId;
if (id == BottomNavigationViewUtils.MoreTabId)
{
var items = CreateTabList();
var bottomSheetDialog = BottomNavigationViewUtils.CreateMoreBottomSheet(OnMoreItemSelected, Context, items, _bottomNavigationView.MaxItemCount);
bottomSheetDialog.DismissEvent += OnMoreSheetDismissed;
bottomSheetDialog.Show();
}
else
{
if (_bottomNavigationView.SelectedItemId != item.ItemId && Element.Children.Count > item.ItemId)
Element.CurrentPage = Element.Children[item.ItemId];
}
return true;
}

void OnMoreSheetDismissed(object sender, EventArgs e)
{
var index = Element.Children.IndexOf(Element.CurrentPage);
using (var menu = _bottomNavigationView.Menu)
{
index = Math.Min(index, menu.Size() - 1);
if (index < 0)
return;
using (var menuItem = menu.GetItem(index))
menuItem.SetChecked(true);
}

if(sender is BottomSheetDialog bsd)
bsd.DismissEvent -= OnMoreSheetDismissed;
}

void OnMoreItemSelected(int selectedIndex, BottomSheetDialog dialog)
{
if (selectedIndex >= 0 && _bottomNavigationView.SelectedItemId != selectedIndex && Element.Children.Count > selectedIndex)
Element.CurrentPage = Element.Children[selectedIndex];

return true;
dialog.Dismiss();
dialog.DismissEvent -= OnMoreSheetDismissed;
dialog.Dispose();
}

bool IsDisposed
Expand Down Expand Up @@ -992,7 +1010,7 @@ int[] GetEmptyStateSet()
return _emptyStateSet;
}

int[] GetStateSet(System.Collections.Generic.IList<int> stateSet)
int[] GetStateSet(IList<int> stateSet)
{
var results = new int[stateSet.Count];
for (int i = 0; i < results.Length; i++)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Threading.Tasks;
using Android.Content;
using Android.Graphics;
using AImageView = Android.Widget.ImageView;

namespace Xamarin.Forms.Platform.Android
Expand Down Expand Up @@ -74,5 +76,10 @@ bool SourceIsNotChanged(IImageElement imageElement, ImageSource imageSource)
return (imageElement != null) ? imageElement.Source == imageSource : true;
}
}

internal static async void SetImage(this AImageView image, ImageSource source, Context context)
{
image.SetImageDrawable(await context.GetFormsDrawableAsync(source));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;

namespace Xamarin.Forms.Platform.Android
{
internal class BottomNavigationViewTracker : IDisposable
{

#region IDisposable
bool _isDisposed = false;
public void Dispose()
{
if (_isDisposed)
return;

_isDisposed = true;
}
#endregion
}
}
177 changes: 177 additions & 0 deletions Xamarin.Forms.Platform.Android/Renderers/BottomNavigationViewUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@
using Android.Widget;
using Android.Support.Design.Widget;
using Android.Support.Design.Internal;
using AColor = Android.Graphics.Color;
using AView = Android.Views.View;
using ColorStateList = Android.Content.Res.ColorStateList;
using IMenu = Android.Views.IMenu;
using LP = Android.Views.ViewGroup.LayoutParams;
using Orientation = Android.Widget.Orientation;
using Typeface = Android.Graphics.Typeface;
using TypefaceStyle = Android.Graphics.TypefaceStyle;
using Android.Graphics.Drawables;
using System.Threading.Tasks;

#if __ANDROID_28__
using ALabelVisibilityMode = Android.Support.Design.BottomNavigation.LabelVisibilityMode;
Expand All @@ -20,6 +30,173 @@ namespace Xamarin.Forms.Platform.Android
{
public static class BottomNavigationViewUtils
{
internal const int MoreTabId = 99;

public static Drawable CreateItemBackgroundDrawable()
{
var stateList = ColorStateList.ValueOf(Color.Black.MultiplyAlpha(0.2).ToAndroid());
return new RippleDrawable(stateList, new ColorDrawable(AColor.White), null);
}

internal static void UpdateEnabled(bool tabEnabled, IMenuItem menuItem)
{
if (menuItem.IsEnabled != tabEnabled)
menuItem.SetEnabled(tabEnabled);
}

internal static async void SetupMenu(
IMenu menu,
int maxBottomItems,
List<(string title, ImageSource icon, bool tabEnabled)> items,
int currentIndex,
BottomNavigationView bottomView,
Context context)
{
menu.Clear();
int numberOfMenuItems = items.Count;
bool showMore = numberOfMenuItems > maxBottomItems;
int end = showMore ? maxBottomItems - 1 : numberOfMenuItems;


List<IMenuItem> menuItems = new List<IMenuItem>();
List<Task> loadTasks = new List<Task>();
for (int i = 0; i < end; i++)
{
var item = items[i];
using (var title = new Java.Lang.String(item.title))
{
var menuItem = menu.Add(0, i, 0, title);
menuItems.Add(menuItem);
loadTasks.Add(SetMenuItemIcon(menuItem, item.icon, context));
UpdateEnabled(item.tabEnabled, menuItem);
if (i == currentIndex)
{
menuItem.SetChecked(true);
bottomView.SelectedItemId = i;
}
}
}

if (showMore)
{
var moreString = new Java.Lang.String("More");
var menuItem = menu.Add(0, MoreTabId, 0, moreString);
menuItems.Add(menuItem);
moreString.Dispose();

menuItem.SetIcon(Resource.Drawable.abc_ic_menu_overflow_material);
if (currentIndex >= maxBottomItems - 1)
menuItem.SetChecked(true);
}

bottomView.SetShiftMode(false, false);

if (loadTasks.Count > 0)
await Task.WhenAll(loadTasks);

foreach (var menuItem in menuItems)
menuItem.Dispose();
}

static async Task SetMenuItemIcon(IMenuItem menuItem, ImageSource source, Context context)
{
if (source == null)
return;
var drawable = await context.GetFormsDrawableAsync(source);
menuItem.SetIcon(drawable);
drawable?.Dispose();
}


public static BottomSheetDialog CreateMoreBottomSheet(
Action<int, BottomSheetDialog> selectCallback,
Context context,
List<(string title, ImageSource icon, bool tabEnabled)> items)
{
return CreateMoreBottomSheet(selectCallback, context, items, 5);
}

internal static BottomSheetDialog CreateMoreBottomSheet(
Action<int, BottomSheetDialog> selectCallback,
Context context,
List<(string title, ImageSource icon, bool tabEnabled)> items,
int maxItemCount)
{
var bottomSheetDialog = new BottomSheetDialog(context);
var bottomSheetLayout = new LinearLayout(context);
using (var bottomShellLP = new LP(LP.MatchParent, LP.WrapContent))
bottomSheetLayout.LayoutParameters = bottomShellLP;
bottomSheetLayout.Orientation = Orientation.Vertical;

// handle the more tab
for (int i = maxItemCount - 1; i < items.Count; i++)
{
var i_local = i;
var shellContent = items[i];

using (var innerLayout = new LinearLayout(context))
{
innerLayout.ClipToOutline = true;
innerLayout.SetBackground(CreateItemBackgroundDrawable());
innerLayout.SetPadding(0, (int)context.ToPixels(6), 0, (int)context.ToPixels(6));
innerLayout.Orientation = Orientation.Horizontal;
using (var param = new LP(LP.MatchParent, LP.WrapContent))
innerLayout.LayoutParameters = param;

// technically the unhook isn't needed
// we dont even unhook the events that dont fire
void clickCallback(object s, EventArgs e)
{
selectCallback(i_local, bottomSheetDialog);
if (!innerLayout.IsDisposed())
innerLayout.Click -= clickCallback;
}
innerLayout.Click += clickCallback;

var image = new ImageView(context);
var lp = new LinearLayout.LayoutParams((int)context.ToPixels(32), (int)context.ToPixels(32))
{
LeftMargin = (int)context.ToPixels(20),
RightMargin = (int)context.ToPixels(20),
TopMargin = (int)context.ToPixels(6),
BottomMargin = (int)context.ToPixels(6),
Gravity = GravityFlags.Center
};
image.LayoutParameters = lp;
lp.Dispose();

image.ImageTintList = ColorStateList.ValueOf(Color.Black.MultiplyAlpha(0.6).ToAndroid());
image.SetImage(shellContent.icon, context);

innerLayout.AddView(image);

using (var text = new TextView(context))
{
text.SetTypeface(Typeface.Create("sans-serif-medium", TypefaceStyle.Normal), TypefaceStyle.Normal);
text.SetTextColor(AColor.Black);
text.Text = shellContent.title;
lp = new LinearLayout.LayoutParams(0, LP.WrapContent)
{
Gravity = GravityFlags.Center,
Weight = 1
};
text.LayoutParameters = lp;
lp.Dispose();

innerLayout.AddView(text);
}

bottomSheetLayout.AddView(innerLayout);
}
}

bottomSheetDialog.SetContentView(bottomSheetLayout);
bottomSheetLayout.Dispose();

return bottomSheetDialog;
}


public static void SetShiftMode(this BottomNavigationView bottomNavigationView, bool enableShiftMode, bool enableItemShiftMode)
{
try
Expand Down
Loading

0 comments on commit 7311345

Please sign in to comment.