Home > Software engineering >  How to change TabbedPage partial icon color in Xamarin
How to change TabbedPage partial icon color in Xamarin

Time:12-30

I am dealing with TabbedPage in Xamarin. However there is a problem that is: How can I change the icon color only partially when Tab is selected. This is the example I want:

enter image description here

In Xamarin it has a solution to change part of the image color. Or is it changing the icon when Tab is selected. This is the code I handle

<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
            BackgroundColor="#fff"
            ....>    
    <views:Page1 Title="Page 1" IconImageSource="homeicon" BackgroundColor="#fff"/>
    <views:Page2 Title="Page 2" IconImageSource="feeds" BackgroundColor="#fff"/>    
    <views:Page3 Title="Page 3" IconImageSource="chats" BackgroundColor="#fff" />
    <views:Page4 Title="Page 4" IconImageSource="usericon" BackgroundColor="#fff"/>
</TabbedPage>

MyTabbedPageRenderer iOS

public class MyTabbedPageRenderer : TabbedRenderer
{

    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        base.OnElementChanged(e);
        
    }
    public override void ViewWillAppear(bool animated)
    {
        base.ViewWillAppear(animated);

        if (TabBar?.Items == null)
            return;

        foreach (var item in TabBar.Items)
        {
            item.Image = ScalingImageToSize(item.Image, new CGSize(20, 20)); // set the size here as you want 
        }

        var tabs = Element as TabbedPage;
        if (tabs != null)
        {
            for (int i = 0; i < TabBar.Items.Length; i  )
            {
                UpdateTabBarItem(TabBar.Items[i]);
            }
        }
    }
    private void UpdateTabBarItem(UITabBarItem item)
    {
        if (item == null) return;

        if (UIDevice.CurrentDevice.CheckSystemVersion(13, 0))
        {
            UITabBarAppearance app = new UITabBarAppearance();
            app.ConfigureWithOpaqueBackground();
            app.BackgroundColor = UIColor.Clear;

            app.StackedLayoutAppearance.Normal.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 12), ForegroundColor = Color.FromHex("#808080").ToUIColor() };
            app.StackedLayoutAppearance.Selected.TitleTextAttributes = new UIStringAttributes() { Font = UIFont.FromName("Roboto Medium", 13), ForegroundColor = Color.FromHex("#00AA13").ToUIColor() };
            item.StandardAppearance = app;

            if (UIDevice.CurrentDevice.CheckSystemVersion(15, 0))
            {
                item.ScrollEdgeAppearance = item.StandardAppearance;
            }
        }

    }
    public UIImage ScalingImageToSize(UIImage sourceImage, CGSize newSize)
    {

        if (UIScreen.MainScreen.Scale == 2.0) //@2x iPhone 6 7 8 
        {
            UIGraphics.BeginImageContextWithOptions(newSize, false, 2.0f);
        }


        else if (UIScreen.MainScreen.Scale == 3.0) //@3x iPhone 6p 7p 8p...
        {
            UIGraphics.BeginImageContextWithOptions(newSize, false, 3.0f);
        }

        else
        {
            UIGraphics.BeginImageContext(newSize);
        }

        sourceImage.Draw(new CGRect(0, 0, newSize.Width, newSize.Height));

        UIImage newImage = UIGraphics.GetImageFromCurrentImageContext();

        UIGraphics.EndImageContext();

        return newImage;

    }

}

AppDelegate.cs

UITabBar.Appearance.BackgroundImage = new UIImage();
UITabBar.Appearance.ShadowImage = new UIImage();
UITabBar.Appearance.SelectedImageTintColor = UIColor.FromRGB(0, 170, 19);

And the results it shows:

enter image description here

How can I change the icon color only partially when Tab is selected in android and iOS. Ask for help. Thanks

Update

I updated as per your sample, but it still doesn't seem to work. I have created a new project

MainView.xaml

<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:Appssss.Views"
            x:Class="Appssss.Views.Mainview">
  <!--Pages can be added as references or inline-->
    <views:Page1 Title="Page 1" IconImageSource="homeicon" BackgroundColor="#fff"/>
    <views:Page2 Title="Page 2" IconImageSource="feeds" BackgroundColor="#fff"/>
    <views:Page3 Title="Page 3" IconImageSource="moneys" BackgroundColor="#fbfbfb" />
    <views:Page4 Title="Page 4" IconImageSource="chats" BackgroundColor="#fff" />
    <views:Page5 Title="Page 5" IconImageSource="usericon" BackgroundColor="#fff"/>
</TabbedPage>

MainView.xaml.cs

namespace Appssss.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class Mainview : Xamarin.Forms.TabbedPage
    {
        public Mainview(int index)
        {
            InitializeComponent();
            On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);
            SetPage(index);
        }
        void SetPage(int index)
        {
            CurrentPage = Children[index];
        }
    }
}

CustomTabbedPageRenderer.cs

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Appssss.Droid.Renderers;
using Google.Android.Material.Tabs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android.AppCompat;
using static Google.Android.Material.Tabs.TabLayout;

[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace Appssss.Droid.Renderers
{
    public class CustomTabbedPageRenderer : TabbedPageRenderer, IOnTabSelectedListener
    {
        public CustomTabbedPageRenderer(Context context) : base(context)
        {

        }
        void IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
        {
            if (tab == null)
            {
                return;
            }

            switch (tab.Text)
            {
                case "Page 1":
                    tab.SetIcon(Resource.Drawable.homeiconselect);
                    break;
                case "Page 2":
                    tab.SetIcon(Resource.Drawable.feedsselect);
                    break;
                case "Page 3":
                    tab.SetIcon(Resource.Drawable.moneysselect);
                    break;
                case "Page 4":
                    tab.SetIcon(Resource.Drawable.chatsselect);
                    break;
                case "Page 5":
                    tab.SetIcon(Resource.Drawable.usericonselect);
                    break;
                default:
                    break;

            }
        }
        void IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
        {
            if (tab == null)
            {
                return;
            }

            switch (tab.Text)
            {
                case "Page 1":
                    tab.SetIcon(Resource.Drawable.homeicon);
                    break;
                case "Page 2":
                    tab.SetIcon(Resource.Drawable.feeds);
                    break;
                case "Page 3":
                    tab.SetIcon(Resource.Drawable.moneys);
                    break;
                case "Page 4":
                    tab.SetIcon(Resource.Drawable.chats);
                    break;
                case "Page 5":
                    tab.SetIcon(Resource.Drawable.usericon);
                    break;
                default:
                    break;
            }
        }

    }
}

And here is the result, I noticed it still doesn't change as expected:

enter image description here

enter image description here

I have created different icons, and as noticed it is still not working. I haven't tried it on iOS yet.

Update 2

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Appssss.Droid.Renderers;
using Google.Android.Material.BottomNavigation;
using Google.Android.Material.Tabs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
using static Google.Android.Material.BottomNavigation.BottomNavigationView;
using static Google.Android.Material.Tabs.TabLayout;

[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace Appssss.Droid.Renderers
{
    public class CustomTabbedPageRenderer : TabbedPageRenderer, IOnTabSelectedListener
    {
        public CustomTabbedPageRenderer(Context context) : base(context)
        {

        }
        protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
        {
            base.OnElementChanged(e);

            if (e.NewElement != null)
            {
                IEnumerable<View> children = GetAllChildViews(ViewGroup);
                BottomNavigationView bottomNavBar = (BottomNavigationView)children.SingleOrDefault(view => view is BottomNavigationView);

                if (bottomNavBar != null)
                {
                    bottomNavBar.NavigationItemSelected  = BottomNavBar_NavigationItemSelected;
                    bottomNavBar.Menu.GetItem(0).SetIcon(Resource.Drawable.diamond);
                }

            }
        }
        int previous;
        private void BottomNavBar_NavigationItemSelected(object sender, NavigationItemSelectedEventArgs e)
        {
            var current = e.Item.ItemId;

            switch (e.Item.ToString())
            {
                case "Page 1":
                    e.Item.SetIcon(Resource.Drawable.diamond);
                    break;
                case "Page 2":
                    e.Item.SetIcon(Resource.Drawable.diamond);
                    break;
                default:
                    break;
            }


            var previousView = sender as BottomNavigationView;
            IMenu menu = previousView.Menu;
            var previousItem = menu.GetItem(previous);


            if (previousItem.IsChecked)
            {
                switch (previousItem.ToString())
                {
                    case "Page 1":
                        previousItem.SetIcon(Resource.Drawable.cactus_24px);
                        break;
                    case "Page 2":
                        previousItem.SetIcon(Resource.Drawable.cactus_24px);
                        break;
                    default:
                        break;
                }


            }

            previous = current;
        }

        private IEnumerable<View> GetAllChildViews(View view)
        {
            if (!(view is ViewGroup group))
                return new List<View> { view };

            List<View> result = new List<View>();
            int childCount = group.ChildCount;

            for (int i = 0; i < childCount; i  )
            {
                View child = group.GetChildAt(i);
                List<View> childList = new List<View> { child };
                childList.AddRange(GetAllChildViews(child));
                result.AddRange(childList);
            }

            return result.Distinct();
        }
    }
}

Get error:

enter image description here enter image description here

I tried

enter image description here

Update3

enter image description here

enter image description here

It doesn't seem to use the library: using static Google.Android.Material.BottomNavigation.BottomNavigationView;

CodePudding user response:

A easy way is to change icon when you selected the tab. Use two icons, a black one, a red one.

Normally, if we want to change the icon color, one way is to set the background like the result(the second screenshot) you post. The second way is to use the Material Icons instead of image source.

But all the ways would not change the color to red and left two black lines as the example you want.

A Complex method is to draw a new image when the Tab selected. Xamarin.Forms provided several ways to do that:

Updated:

You could do this in custom renderer.

Android: Android provide a class IOnTabSelectedListener to listener the selected and unseledted status of the tab. A simple code below for your reference.

[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace App18.Droid
{
public class CustomTabbedPageRenderer: TabbedPageRenderer, IOnTabSelectedListener
{
    public CustomTabbedPageRenderer(Context context) : base(context)
    {

    }
    void IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
    {
        if (tab == null)
        {
            return;
        }

        switch (tab.Text)
        {
            case "Page 1":
                tab.SetIcon(Resource.Drawable.icon);
                break;
            case "Page 2":
                tab.SetIcon(Resource.Drawable.icon);
                break;
            default:
                break;
        }
    }
    void IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
    {
        if (tab == null)
        {
            return;
        }

        switch (tab.Text)
        {
            case "Page 1":
                tab.SetIcon(Resource.Drawable.cactus_24px);
                break;
            case "Page 2":
                tab.SetIcon(Resource.Drawable.cactus_24px);
                break;
            default:
                break;
        }
    }
  
}
}

iOS: For the ios, you could use ViewControllerSelected to register the method ViewControllerSelected. For more details, check the link below. how to make the tabbed page navigate when clicked on the same tab text/icon twice?

Updated2:

Android: For the tabs on the bottom, you need to get access to the native BottomNavigationView.

[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedPageRenderer))]
namespace App18.Droid
{
public class CustomTabbedPageRenderer : TabbedPageRenderer, IOnTabSelectedListener
{
    public CustomTabbedPageRenderer(Context context) : base(context)
    {

    }
    protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
    {
        base.OnElementChanged(e);

        if (e.NewElement != null)
        {
            IEnumerable<View> children = GetAllChildViews(ViewGroup);
            BottomNavigationView bottomNavBar = (BottomNavigationView)children.SingleOrDefault(view => view is BottomNavigationView);

            if (bottomNavBar != null)
            {               
                bottomNavBar.NavigationItemSelected  = BottomNavBar_NavigationItemSelected;
                bottomNavBar.Menu.GetItem(0).SetIcon(Resource.Drawable.diamond);
            }

        }
    }
    int previous; 
    private void BottomNavBar_NavigationItemSelected(object sender, NavigationItemSelectedEventArgs e)
    {
         var current = e.Item.ItemId;

        switch (e.Item.ToString())
        {
            case "Page 1":
                e.Item.SetIcon(Resource.Drawable.diamond);
                break;
            case "Page 2":
                e.Item.SetIcon(Resource.Drawable.diamond);
                break;
            default:
                break;
        }       
        

        var previousView = sender as BottomNavigationView;
        IMenu menu = previousView.Menu;
        var previousItem = menu.GetItem(previous);
       

        if (previousItem.IsChecked)
        {
            switch (previousItem.ToString())
            {
                case "Page 1":
                    previousItem.SetIcon(Resource.Drawable.cactus_24px);
                    break;
                case "Page 2":
                    previousItem.SetIcon(Resource.Drawable.cactus_24px);
                    break;
                default:
                    break;
            }
           
            
        }

        previous = current;
    }

    private IEnumerable<View> GetAllChildViews(View view)
    {
        if (!(view is ViewGroup group))
            return new List<View> { view };

        List<View> result = new List<View>();
        int childCount = group.ChildCount;

        for (int i = 0; i < childCount; i  )
        {
            View child = group.GetChildAt(i);
            List<View> childList = new List<View> { child };
            childList.AddRange(GetAllChildViews(child));
            result.AddRange(childList);
        }

        return result.Distinct();
    }
}
}

Update3:

For the errors of View, use Android.Views.View instead of Xamarin.Forms.View.

using View = Android.Views.View;

For the errors of the Item, i added all the code with references which you used, no error with the item. Clean the bin and obj folder to rebuild the project again.

Update4:

Declare a TabbedPage instance variables outside any function. And set the value when bottomNavBar not null.

 TabbedPage tabbedPage;
    protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.TabbedPage> e)
    {
        base.OnElementChanged(e);
        if (e.NewElement != null)
        {
            IEnumerable<View> children = GetAllChildViews(ViewGroup);
            BottomNavigationView bottomNavBar = (BottomNavigationView)children.SingleOrDefault(view => view is BottomNavigationView);
            if (bottomNavBar != null)
            {
                tabbedPage = e.NewElement;
                bottomNavBar.NavigationItemSelected  = BottomNavBar_NavigationItemSelected;
                bottomNavBar.Menu.GetItem(0).SetIcon(Resource.Drawable.diamond_24);
            }
        }
    }

Set the CurrentPage of tabbedPage in BottomNavBar_NavigationItemSelected.

   private void BottomNavBar_NavigationItemSelected(object sender, BottomNavigationView.NavigationItemSelectedEventArgs e)
    {
        var current = e.Item.ItemId;
        switch (e.Item.ToString())
        {
            case "Page 1":
                e.Item.SetIcon(Resource.Drawable.diamond_24);
                break;
            case "Page 2":
                e.Item.SetIcon(Resource.Drawable.diamond_24);
                break;
            case "Page 3":
                e.Item.SetIcon(Resource.Drawable.diamond_24);
                break;
            default:
                break;
        }
        var previousView = sender as BottomNavigationView;
        IMenu menu = previousView.Menu;
        var previousItem = menu.GetItem(previous);
         if (previous != current)
        {
            if (previousItem.IsChecked)
            {
                switch (previousItem.ToString())
                {
                    case "Page 1":
                        previousItem.SetIcon(Resource.Drawable.cactus_24);
                        break;
                    case "Page 2":
                        previousItem.SetIcon(Resource.Drawable.cactus_24);
                        break;
                    case "Page 3":
                        previousItem.SetIcon(Resource.Drawable.cactus_24);
                        break;
                    default:
                        break;
                }

            }

        }
        tabbedPage.CurrentPage = tabbedPage.Children[current];
        previous = current;
    }
  • Related