Skip to content
This repository has been archived by the owner on May 1, 2024. It is now read-only.

[Android] TabbedPage: Bottom Tab Bar #1748

Merged
merged 8 commits into from
May 9, 2018
Merged

[Android] TabbedPage: Bottom Tab Bar #1748

merged 8 commits into from
May 9, 2018

Conversation

mikescandy
Copy link
Contributor

@mikescandy mikescandy commented Jan 30, 2018

Description of Change

Adding a flag allowing a tabbed page to be rendered as a Bottom Navigation view on Android. Add the ability to set the tint color of unselected and selected items.

Bugs Fixed

fixes #1675

API Changes

Added:

  • AndroidSpecific.SetToolbarPlacement // Top, Bottom, Default
On<Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
<?xml version="1.0" encoding="utf-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" 
    xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
    android:TabbedPage.ToolbarPlacement="Bottom"
...
  • AndroidSpecific.GetMaxItemCount // returns 5 for bottom and maxvalue for top
  • AndroidSpecific.SetBarItemColor // sets the tint color for the icons (and the text color if text color is default)
  • AndroidSpecific.SetBarSelectedItemColor //sets the tint color for the selected icon (and the text color if the text color is default)
  • In the Android TabbedPageRenderer added
protected virtual ColorStateList GetItemTextColorStates()
protected virtual ColorStateList GetItemIconTintColorState()

Allowed the user to define there own ColorStateList relationship as they pertain to the Tabs

PR Checklist

  • Has tests (if omitted, state reason in description)
  • Rebased on top of master at time of PR
  • Changes adhere to coding standard
  • Consolidate commits as makes sense

@dnfclas
Copy link

dnfclas commented Jan 30, 2018

CLA assistant check
All CLA requirements met.

@pauldipietro pauldipietro added this to New in Triage Jan 30, 2018
@PureWeen PureWeen moved this from New to Proposal in Triage Jan 30, 2018
@jassmith
Copy link

Hey thank you! Unfortunately I am not in a position to actually run this for a couple days. I would really appreciate some screenshots however just so I can see how it looks. I will give the code a detailed review tomorrow, however a quick glance looks good.


if (width > 0 && height > 0)
((IPageController)Element).ContainerArea = new Rectangle(0, 0, _relativeLayout.MeasuredWidth, _relativeLayout.GetChildAt(0).MeasuredHeight);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without having run it or really grokked everything in here yet this seems wrong. The ContainerArea should be the region of the TabbedPage that does NOT contain the tabs itself. So in general this would be 0, 0, r - l, b - t - bottomTabBarHeight

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's absolutely right. I was doing a bit more test and trying to understand that code a bit better and I realized the same. I'll push a better version, along with some screenshots

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(the OnLayout override is the most mysterious to me right now)

@mikescandy
Copy link
Contributor Author

mikescandy commented Jan 31, 2018

screenshot_1517410328
screenshot_1517410322
screenshot_1517410332
image

@mikescandy
Copy link
Contributor Author

mikescandy commented Jan 31, 2018

I have a few questions on how to handle the colors for icons and text, in terms of selected/not selected/disabled.
Also, we should probably disable the swipe gesture.

@StephaneDelcroix
Copy link
Member

I'd avoid using BottomNavigation wording. if the default changes in the future on Android, that property would become awkward.

I'd expose this property with a name like xxPositionxxx and it's value being an enum Default, Top, Bottom

@hartez
Copy link
Contributor

hartez commented Jan 31, 2018

See also: UWP toolbar placement: https://github.com/xamarin/Xamarin.Forms/blob/74cb5c4a97dcb123eb471f6b1dffa1267d0305aa/Xamarin.Forms.Core/PlatformConfiguration/WindowsSpecific/ToolbarPlacement.cs

@mikescandy
Copy link
Contributor Author

While we are exposing it using the same element (tabbedpage), tabs and bottomnavigation view behave quite differently. My initial implementation was actually using a different control, but then I saw the enhancement proposal and I merged my implementation inside the tabbedpage, which simplified things a lot.
The bottomnavigationview support a max number of 5 elements, while tabs are effectively unlimited.
I would interpret the placement as same control in different position. Not sure really.

else if (newTextColor.IsDefault && _defaultColor.HasValue && currentColor != _defaultColor)
_tabLayout.SetTabTextColors(_defaultColor.Value, _defaultColor.Value);
int selectedIndex = item.Order;
if (_bottomNavigationView.SelectedItemId != item.Order && Element.Children.Count > selectedIndex && selectedIndex >= 0)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the first condition is meant to be _bottomNavigationView.SelectedItemId != item.ItemId

using Android.Views;
using Xamarin.Forms.Internals;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;

namespace Xamarin.Forms.Platform.Android.AppCompat
{
public class TabbedPageRenderer : VisualElementRenderer<TabbedPage>, TabLayout.IOnTabSelectedListener, ViewPager.IOnPageChangeListener, IManageFragments
public class TabbedPageRenderer : VisualElementRenderer<TabbedPage>, TabLayout.IOnTabSelectedListener, ViewPager.IOnPageChangeListener, IManageFragments, BottomNavigationView.IOnNavigationItemSelectedListener
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest making modifications to OnPagePropertyChanged to handle when a page's title changes (e.g. via data binding). The current renderer already does this when the native element is a TabLayout. I think the code you'd need for the BottomNavigationView is something like

var page = (Page)sender;
var index = Element.Children.IndexOf(page);
var tab = _bottomNavigationView.Menu.GetItem(index);
tab.SetTitle(page.Title);

@MaikuB
Copy link

MaikuB commented Feb 4, 2018

i'm interested in this being officially supported by Xamarin.Forms as well. Added some comments/suggestions to the PR

@samhouts samhouts changed the title Android: TabbedPage: Bottom Tab Bar [Android] TabbedPage: Bottom Tab Bar Feb 7, 2018
@samhouts samhouts added this to In Progress in Enhancements Feb 12, 2018
@samhouts samhouts added this to Ready in v3.1.0 via automation Feb 12, 2018
@samhouts samhouts moved this from Ready to In Review in v3.1.0 Feb 12, 2018
@rmarinho rmarinho removed this from Proposal in Triage Feb 14, 2018
@jassmith
Copy link

retriggering builds

@jassmith
Copy link

Can you please update the docs? There is a script to do it. Without the update we wont get CI results

@mikescandy
Copy link
Contributor Author

mikescandy commented Feb 16, 2018 via email

{
public enum TabsPlacement
{
/// <summary>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please dont use inline docs, they wont get properly updated. Use the mdoc files instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copy/pasted from the WindowsSpecific\ToolbarPlacement.cs file. Anyway, should I just delete the comments? Also, I couldn't find the mdoc files :(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikescandy yea just delete the comments.. Since you rebased with master you won't need to run the docs file that generates docs anymore. Just make sure the API Changes part of the PR has everything


int currentColor = _tabLayout.TabTextColors.DefaultColor;
if (!_defaultColor.HasValue)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces => Tabs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in next commit.

}

base.OnLayout(changed, l, t, r, b);
base.OnLayout(changed, l, t, r, b);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spaces => Tabs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in next commit.

viewPagerParams.AddRule(AWidget.LayoutRules.Above, _bottomNavigationView.Id);

FormsViewPager pager =
_viewPager =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this code be moved to a general CreateFormsViewPager method? Then both the bottom and top navbar would just call that? The way these new up looks to be the same between the two

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in next commit.

@@ -0,0 +1,17 @@
namespace Xamarin.Forms.PlatformConfiguration.AndroidSpecific
{
public enum TabsPlacement
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Default here as well just in case Android changes what Default means in the future

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in next commit. I don't think this has an Android equivalent really, but agree it's right to put a Default here.


public static void SetTabsPlacement(BindableObject element, TabsPlacement value)
{
element.SetValue(TabsPlacementProperty, value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we throw an explicit exception here if the user tries to toggle between Top <-> Bottom after the page has already been rendered? This way it's clear to the user that changing the placement after it's been realized isn't supported

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Help here: as this is an enum, my understanding is that this will always have a value (Default), so I probably should not throw on the first value change. Where should I track the value change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you rebase to master you'll see a property on BindableObject now that lets you test if something has been changed from the default
https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/BindableObject.cs#L55

Which should be sufficient

My other thought would be to add a Changing event to the bindable property. Then inside the Tab Renderer once the layout call happens it could set a local value indicating this is how it is on the layout. The changing event could then check that... But that's probably overkill and just checking IsSet should be enough

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try the rebase. I'm not the best at using GIT, let's see what happens...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be done. Please check if the exception message makes sense.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikescandy Hey mike, looks like your pretty much done with this, when can we mortals expect this as a nuget-package? I suppose its going into pre-release.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error message seems to work just remember to set Default as the defaultValue

		public static readonly BindableProperty TabsPlacementProperty = 
			BindableProperty.Create("TabsPlacement", typeof(TabsPlacement), 
			typeof(TabbedPage), TabsPlacement.Top);

And then at this point Default and Top will just cause the same behavior

@@ -170,6 +170,69 @@ public void TestReorderTabs ()
#endif
}

[Preserve(AllMembers = true)]
[Issue(IssueTracker.Github, 1675, "Android: TabbedPage: Bottom Tab Bar", PlatformAffected.Android)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some Android specific UITests as well modeled after

public class TabbedPageTests : TestTabbedPage

So that we can generate some screen shots of the bottom nav bar and just have some automated ui tests for this

bottomNavigationView.Menu.Add(0, i, i, child.Title).SetEnabled(child.IsEnabled);
}

if (Element.Children.Count > 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of how this is implemented if I'm not on the first tab then when I remove a page it'll just navigate me back to the first page. Can this be setup to just remain on the Current Page if that page wasn't removed? Something like

for (var i = 0; i < Element.Children.Count; i++)
			{
				Page child = Element.Children[i];
				var menuItem = bottomNavigationView.Menu.Add(0, i, i, child.Title).SetEnabled(child.IsEnabled);

				if(Element.CurrentPage == child)
				{
					bottomNavigationView.SelectedItemId = menuItem.ItemId;
				}
			}

			if (Element.Children.Count > 0)
			{
				if(Element.CurrentPage == null || !Element.Children.Contains(Element.CurrentPage))
					Element.CurrentPage = Element.Children[0];			
					
			}

Also is it necessary to clear out and rebuild the whole menu each time an item is removed/added?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will do. Not sure how not to rebuild the menu. I should keep some kind of dictionary linking menu item ids and child pages... not really sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the method is called by OnChildrenCollectionChanged could the NotifyCollectionChangedEventArgs be used for the index of the removed page?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

int newTextColorArgb = newTextColor.ToAndroid().ToArgb();
var color = newTextColor.ToAndroid();
color.A = 128;
int newTextColorArgbFade = color.ToArgb();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do? It doesn't seem like this is used later for anything

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Garbage. Also, renaming color -> semiTransparentColor as it's more meaningful and explains what I'm doing there

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikescandy sounds good

I'm also thinking we should either remove setting the ItemIconTintList or add a platform property BarIconTintColor that can be applied to either AppBar. If setting that Tint Color on a top app bar is tricky though maybe just have BottomBarIconTintColor

if (!newTextColor.IsDefault && currentColor != newTextColorArgb)
{
_bottomNavigationView.ItemTextColor = new ColorStateList(new[] { sChecked, sDefault }, new[] { newTextColorArgb, color });
_bottomNavigationView.ItemIconTintList = new ColorStateList(new[] { sChecked, sDefault }, new[] { newTextColorArgb, color });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want the BarTextColor to also set the Tint? Can we make the Tint Color a different settable property? Then we could also try applying said tint setting to the top bar as well

@rmarinho
Copy link
Member

hey @mikescandy you need to rebase and remove docs, it's not needed anymore. thanks

@PureWeen PureWeen changed the base branch from master to 3.1.0 May 3, 2018 21:12
@xamarin-release-manager xamarin-release-manager added the API-change Heads-up to reviewers that this PR may contain an API change label May 7, 2018
mikescandy and others added 8 commits May 7, 2018 15:54
* Adding bottom navigation support to tabbed page
* Adding bottom navigation support to tabbed page

* Adding a bottom navigation page to the gallery

* Set the default behavior to false to avoid introducing a breaking change

* Fixing onlayout, handling collection change and improving color management
Renaming semi transparent color variable, throw when changing tabs placement after setting it
@samhouts samhouts moved this from In Progress to In Review in v3.1.0 May 7, 2018
@xamarin-release-manager xamarin-release-manager added the API-change Heads-up to reviewers that this PR may contain an API change label May 8, 2018
@PureWeen
Copy link
Contributor

PureWeen commented May 8, 2018

build --uitests

@PureWeen
Copy link
Contributor

PureWeen commented May 9, 2018

Test failure is being misreported. All tests ran fine in appcenter

@PureWeen PureWeen merged commit 3cc4232 into xamarin:3.1.0 May 9, 2018
v3.1.0 automation moved this from In Review to Done May 9, 2018
Enhancements automation moved this from In Progress to Done May 9, 2018
@samhouts samhouts removed this from Done in Enhancements Jun 12, 2018
@Fabone248
Copy link

Hello,

I am using the tool bar placement function to set tabs at the bottom of the screen, but I noticed that if we have more than 3 tabs, then we don't see the titles anymore and the selected icon kind of pushes the other icons away. I'd like to know if there is a way to deactivate this behavior and have all titles showing up and the unselected icons not moving at all?

Thank you for your help.

@hunii
Copy link

hunii commented Jul 30, 2018

I like to know the above question too...

@Roshek
Copy link

Roshek commented Jul 31, 2018

@hunii @Fabone248 AFAIK you can currently only do this by adding a custom renderer. See gist here: https://gist.github.com/LynoDesu/64904b6d143892cf14a60a32798a36bb

If you have more questions you can also read through #1675

@Fabone248
Copy link

@Roshek Thank you for your reply, I will have a look :)

@samhouts samhouts added this to the 3.2.0 milestone Sep 11, 2018
@samhouts samhouts removed this from Done in v3.1.0 Sep 11, 2018
@samhouts samhouts added this to Done in v3.2.0 Sep 11, 2018
@samhouts samhouts removed this from the 3.2.0 milestone Sep 12, 2018
@samhouts samhouts added this to Done in v3.1.0 Sep 12, 2018
@samhouts samhouts removed this from Done in v3.2.0 Sep 12, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
API-change Heads-up to reviewers that this PR may contain an API change community-sprint F100 p/Android t/enhancement ➕
Projects
No open projects
v3.1.0
  
Done
Development

Successfully merging this pull request may close these issues.

None yet