I have an application using Xamarin Forms
TabbedPage
which has a feature that would allow the user to pause and play a page. Please see the code below.
Shared Code
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
var homePage = new NavigationPage(new HomePage())
{
Title = "Home",
Icon = "ionicons_2_0_1_home_outline_25.png"
};
var phrasesPage = new NavigationPage(new PhrasesPage())
{
Title = "Play",
Icon = "ionicons_2_0_1_play_outline_25.png"
};
Children.Add(homePage);
Children.Add(phrasesPage);
}
}
In iOS renderer:
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var tabbarController = (UITabBarController)this.ViewController;
if (null != tabbarController)
{
tabbarController.ViewControllerSelected += OnTabBarReselected;
}
}
void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
if (TabBar.SelectedItem.Title == "Play") {
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else {
if (tabs != null) {
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
Android Renderer
public class MyTabbedPageRenderer: TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
if (e.PropertyName == "Renderer")
{
viewPager = (ViewPager)ViewGroup.GetChildAt(0);
tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
setup = true;
ColorStateList colors = null;
if ((int)Build.VERSION.SdkInt >= 23)
{
colors = Resources.GetColorStateList(Resource.Color.icon_tab, Forms.Context.Theme);
}
else
{
colors = Resources.GetColorStateList(Resource.Color.icon_tab);
}
for (int i = 0; i < tabLayout.TabCount; i++)
{
var tab = tabLayout.GetTabAt(i);
var icon = tab.Icon;
if (icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
var selectedPosition = tab.Position;
if(selectedPosition == 4)
{
if (playTab.Title == "Play")
{
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else
{
if (tabs != null)
{
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
}
}
This is perfectly working in iOS
. But somehow in Android
only the Title
would change but not the Icon
. Anyone knows what Im missing or how it should be done? Also, is this possible to be done in the shared code instead of repeating almost exactly the same lines on code in each platform?
You can do it by using the tab that is being passe to you in the OnTabReselected
parameters in the TabRenderer
.
You can move your whole logic with this object.
This is my whole renderer file (Android):
[assembly: ExportRenderer(typeof(SWTabSelection.MainPage), typeof(SWTabSelection.Droid.MyTabbedPageRenderer))]
namespace SWTabSelection.Droid
{
public class MyTabbedPageRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
private ViewPager viewPager;
private TabLayout tabLayout;
private bool setup;
public MyTabbedPageRenderer() { }
public MyTabbedPageRenderer(Context context) : base(context)
{
//Use this constructor for newest versions of XF saving the context parameter
// in a field so it can be used later replacing the Xamarin.Forms.Forms.Context which is deprecated.
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "Renderer")
{
viewPager = (ViewPager)ViewGroup.GetChildAt(0);
tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
setup = true;
ColorStateList colors = GetTabColor();
for (int i = 0; i < tabLayout.TabCount; i++)
{
var tab = tabLayout.GetTabAt(i);
SetTintColor(tab, colors);
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
// To have the logic only on he tab on position 1
if(tab == null || tab.Position != 1)
{
return;
}
if(tab.Text == "Play")
{
tab.SetText("Pause");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_25);
App.pauseCard = false;
}
else
{
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
SetTintColor(tab, GetTabColor());
}
void SetTintColor(TabLayout.Tab tab, ColorStateList colors)
{
var icon = tab?.Icon;
if(icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
ColorStateList GetTabColor()
{
return ((int)Build.VERSION.SdkInt >= 23)
? Resources.GetColorStateList(Resource.Color.icon_tab, Forms.Context.Theme)
: Resources.GetColorStateList(Resource.Color.icon_tab);
}
}
}
The only thing that I had with the code above is that the icon was not taking the Tint color so created a function with the same logic you had to set the Tint and I am using it on the Tab Reselection. If you have only one tab in your app you can set a global tint in the Android Theme/Style xml.
Hope this helps.
Custom Renderer
is no needed , you can change the Title
and Icon
directly in Shared code.
Just implement CurrentPageChanged
event in TabbedPage
public partial class TabbedPage1 : TabbedPage
{
NavigationPage homePage;
NavigationPage phrasesPage;
public TabbedPage1 ()
{
InitializeComponent();
var homePage = new NavigationPage(new Page1())
{
Title = "Home",
Icon = "1.png"
};
var phrasesPage = new NavigationPage (new Page2())
{
Title = "Play",
Icon = "1.png"
};
Children.Add(homePage);
Children.Add(phrasesPage);
this.CurrentPageChanged += (object sender, EventArgs e) => {
var i = this.Children.IndexOf(this.CurrentPage);
if (i == 0)
{
homePage.Title = "HomeChanged";
homePage.Icon = "2.png";
}
else {
phrasesPage.Title = "PlayChanged";
phrasesPage.Icon = "2.png";
}
};
}
}
PS: Make the image files access from a different platform.
iOS - Resources
Android - Resources->drawable
There isn't a way to detect when a tab is reselected in Xamarin.Forms, so we'll have to use custom rederers to detect the logic.
For Android, we'll have to handle two cases: Current Tab Page Changed, and Current Tab Page Reselected.
We'll subscribe to to CurrentPageChanged
and in its EventHandler, we'll check to see if the tab selected is PhrasesPage
. If it is, we'll update the Icon/Text.
In OnTabReselected
, we can check which page is currently selected, and if it's the PhrasesPage
, we can update PhrasesPage.Icon
and PhrasesPage.Text
.
https://github.com/brminnick/ChangeTabbedPageIconSample/tree/master
[assembly: ExportRenderer(typeof(MainPage), typeof(TabbedPageRenderer))]
namespace YourNameSpace
{
public class TabbedPageRenderer : TabbedRenderer, TabLayout.IOnTabSelectedListener
{
//Overloaded Constructor required for Xamarin.Forms v2.5+
public TabbedPageRenderer(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
Element.CurrentPageChanged += HandleCurrentPageChanged;
}
void HandleCurrentPageChanged(object sender, EventArgs e)
{
var currentNavigationPage = Element.CurrentPage as NavigationPage;
if (!(currentNavigationPage.RootPage is PhrasesPage))
return;
var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
for (int i = 0; i < tabLayout.TabCount; i++)
{
var currentTab = tabLayout.GetTabAt(i);
var currentTabText = currentTab.Text;
if (currentTabText.Equals("Play") || currentTabText.Equals("Pause"))
{
Device.BeginInvokeOnMainThread(() => UpdateTab(currentTabText, currentTab, currentNavigationPage));
break;
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
System.Diagnostics.Debug.WriteLine("Tab Reselected");
var mainPage = Application.Current.MainPage as MainPage;
var currentNavigationPage = mainPage.CurrentPage as NavigationPage;
if(currentNavigationPage.RootPage is PhrasesPage)
Device.BeginInvokeOnMainThread(() => UpdateTab(currentNavigationPage.Title, tab, currentNavigationPage));
}
void UpdateTab(string currentTabText, TabLayout.Tab tab, NavigationPage currentNavigationPage)
{
if (currentTabText.Equals("Puzzle"))
{
tab.SetIcon(IdFromTitle("Settings", ResourceManager.DrawableClass));
currentNavigationPage.Title = "Settings";
}
else
{
tab.SetIcon(IdFromTitle("Puzzle", ResourceManager.DrawableClass));
currentNavigationPage.Title = "Puzzle";
}
}
int IdFromTitle(string title, Type type)
{
string name = System.IO.Path.GetFileNameWithoutExtension(title);
int id = GetId(type, name);
return id;
}
int GetId(Type type, string memberName)
{
object value = type.GetFields().FirstOrDefault(p => p.Name == memberName)?.GetValue(type)
?? type.GetProperties().FirstOrDefault(p => p.Name == memberName)?.GetValue(type);
if (value is int)
return (int)value;
return 0;
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With