Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change TabbedPage Icon when the tab is reselected in Android?

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?

like image 436
Samantha J T Star Avatar asked Mar 31 '18 07:03

Samantha J T Star


Video Answer


3 Answers

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.

like image 190
pinedax Avatar answered Oct 25 '22 14:10

pinedax


Custom Renderer is no needed , you can change the Title and Icon directly in Shared code.

Just implement CurrentPageChanged event in TabbedPage

Complete code

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";
            }
        };
    }
}

Result

enter image description here

PS: Make the image files access from a different platform.

iOS - Resources

Android - Resources->drawable

like image 32
ColeX - MSFT Avatar answered Oct 25 '22 14:10

ColeX - MSFT


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.

Sample App

https://github.com/brminnick/ChangeTabbedPageIconSample/tree/master

Android Custom Renderer

[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;
        }
    }
}

enter image description here

like image 24
Brandon Minnick Avatar answered Oct 25 '22 12:10

Brandon Minnick