8
votes

I am attempting to match a design like this..

enter image description here

Notice that the "selected tab color tint" is a blue, but the center tab's icon should always be the green circle with the white clock in the middle.

I've tried a number of things. First trying to do it programmatically by using a layer-list XML resource that had the green circle and clock PNG resource, which didn't work at all. Then I just got the designer to give me the full icon(clock and green circle), but now I'm running into this issue..

(Unselected)

enter image description here

(Selected)

enter image description here

I'm failing at finding the correct terms to search for on Google to fix this.

In the end, I need the selected tab color to be blue, but I need the center tab icon to always be the actual icon with no additional coloring(essentially it needs to look exactly like the .png).

PS: I am using Xamarin.Forms, FreshMvvm, and the FreshTabbedFONavigationContainer. However, through the Renderer, I have direct access to the BottomNavigationView and all of the other native Android components. So the solution does not have to be a Xamarin solution. A java/kotlin solution would work also and I can just convert it to Xamarin.

======================

EDITED:

======================

So I have gotten a lot further using Andres Castro code below, but I'm still having the same issue as before. Using Andres' code below, I switched back to using FontAwesome for the icons(which works great), but in doing so means I needed to use a LayerDrawable to create the circle/icon center tab icon.

So this is what I have so far..

Unselected center icon

enter image description here

Selected center icon

enter image description here

As you can see, the center icon is still gray when unselected and blue when selected(the proper selected/unselected colors of the other 4 icons).

Here is the code I have so far pertaining to the center icon..

UpdateTabbedIcons

private void UpdateTabbedIcons()
{
    for (var i = 0; i < Element.Children.Count; i++) {
        var tab = _bottomNavigationView.Menu.GetItem(i);

        var element = Element.Children[i];
        if (element is NavigationPage navigationPage) {
            //if the child page is a navigation page get its root page
            element = navigationPage.RootPage;
        }

        UpdateTabIcon(tab, element);
    }
}

UpdateTabIcon

public void UpdateTabIcon(IMenuItem menuItem, Page page)
{
    var icon = page?.Icon;
    if (icon == null) return;

    var drawable = new IconDrawable(Context, icon, "fa-regular-pro-400.ttf");

    var element = Element.CurrentPage;
    if (element is NavigationPage navigationPage) {
        //if the child page is a navigation page get its root page
        element = navigationPage.RootPage;
    }

    if (page is DoNowTabPage) { //Page for center icon
        drawable.Color(Helpers.Resources.White.ToAndroid());
        var finalDrawable = GetCombinedDrawable(drawable);
        menuItem.SetIcon(finalDrawable);
        return;
    } else if (element == page) {
        drawable.Color(BarSelectedItemColor.ToAndroid());
    } else {
        drawable.Color(BarItemColor.ToAndroid());
    }

    menuItem.SetIcon(drawable);
}

GetCombinedDrawable

private Drawable GetCombinedDrawable(IconDrawable iconDrawable)
{
    var displayMetrics = Resources.DisplayMetrics;

    GradientDrawable circleDrawable = new GradientDrawable();
    circleDrawable.SetColor(Helpers.Resources.Green.ToAndroid());
    circleDrawable.SetShape(ShapeType.Oval);
    circleDrawable.SetSize((int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 500, displayMetrics), (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 500, displayMetrics));
    circleDrawable.Alpha = 1;

    var inset = (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 140, displayMetrics);
    var bottomInset = (int)TypedValue.ApplyDimension(ComplexUnitType.Dip, 40, displayMetrics);
    LayerDrawable finalDrawable = new LayerDrawable(new Drawable[] { circleDrawable, iconDrawable });
    finalDrawable.SetLayerHeight(1, iconDrawable.IntrinsicHeight);
    finalDrawable.SetLayerWidth(1, iconDrawable.IntrinsicWidth);
    finalDrawable.SetLayerInset(1, inset, inset, inset, inset + bottomInset);
    finalDrawable.SetLayerInsetBottom(0, bottomInset);
    finalDrawable.ClearColorFilter();

    return finalDrawable;
}

As you can see in the GradientDrawable that I'm creating for the circle, I am setting it's color to my Green color(I have a custom class called Resources..that's not the Android Resources class).

So that's where I'm stuck. I am setting the circle drawable to having a green color, but once in the BottomNavigationView, it's color always matches the unselected/selected colors of the other icons.

Hoping to get past this final issue. Thanks for any help.

6
One simple solution is to use one blank item in between and place a separate imageview over the BottomNavigationView with the green image.Shivam Yadav
Why don't you programmatically change the selected tint/color depending on the index of the bottom navigation view?Saamer
@Saamer You comment seemed so simple haha. Can you post it as an answer so I can award the bounty? This is the code I used... _bottomNavigationView.ItemIconTintList = null;Ryan Alford
It worked for you? BottomNavigationView is painfully more difficult than its iOS implementation. I did some research to see if what you were asking was possible, and then when I saw it in Android native, I started thinking of ways to make it happen.Saamer
Yeah, it worked. I'm not sure but it would seem like my change would reset ALL item icon tints, but it doesn't. All of the other tabs still work because I'm manually setting them each time, and the center is green when it's selected and unselected.Ryan Alford

6 Answers

2
votes

Since you have access to the bottom navigation view, you can just "redraw" your bottom toolbar every time you switch pages. We did something similar and didn't notice any performance issues.

You will first want to monitor page changes by subscribing to page change inside OnElementChanged

Element.CurrentPageChanged += ElementOnCurrentPageChanged;

Then inside ElementOnCurrentPageChanged you can traverse each page and get the menu item for that page

private void UpdateTabbedIcons()
{
    for (var i = 0; i < Element.Children.Count; i++)
    {
        var tab = _bottomNavigationView.Menu.GetItem(i);

        var element = Element.Children[i];
        if (element is NavigationPage navigationPage)
        {
            //if the child page is a navigation page get its root page
            element = navigationPage.RootPage;
        }

        UpdateTabIcon(tab, element);
    }
}

In our case we used font icons so we drew the icons and set the colors every time.

public void UpdateTabIcon(IMenuItem menuItem, Page page)
{
    var icon = page?.Icon?.File;
    if (icon == null) return;

    var drawable = new IconDrawable(Context, "FontAwesome.ttf", icon).SizeDp(20);

    var element = Element.CurrentPage;
    if (element is NavigationPage navigationPage)
    {
        //if the child page is a navigation page get its root page
        element = navigationPage.RootPage;
    }

    if (element == page)
    {
        drawable.Color(BarSelectedItemColor.ToAndroid());
    }
    else
    {
        drawable.Color(BarItemColor.ToAndroid());
    }

    menuItem.SetIcon(drawable);
}

We also had to override OnAttachedToWindow to make sure the icons were redraw when returning to the app from different states.

protected override void OnAttachedToWindow()
{
    UpdateTabbedIcons();

    base.OnAttachedToWindow();
}

You will have to modify this some to fit your use case but I think this method should accomplish what you are looking for.

1
votes

BottomNavigationView is painfully more difficult than its iOS implementation. I did some research to see if what you were asking was possible, and then when I saw it in Android native, I started thinking of ways to make it happen.

To implement your last challenge, you would have to programmatically change the selected tint/color every time depending on the index of the bottom navigation view.

1
votes

you can use SVG images & make your own color attribute and make a drawable selector for center icon as well as other bottom navigation view icon some how like below:

in colors.xml file

    <color name="color_bottom_selected">#44C8F5</color>
 <color name="color_bottom_unselected">#c0c0c0</color>

in attrs.xml file

   <attr name="color_bottom_unselected" format="color" />
    <attr name="color_bottom_selected" format="color" />

in SVG image file

replace hardcoded color value with your attribute

android:fillColor="#fff" to android:fillColor="?attr/color_bottom_unselected"

and don't forget to make selector drawable

1
votes

Try to use tint mode DST, which will simply ignore the tint.

Add this line into your method GetCombinedDrawable()

circleDrawable.setTintMode(PorterDuff.Mode.DST);

If this won't work, try this:

finalDrawable.ClearColorFilter();
finalDrawable.setTintMode(PorterDuff.Mode.DST);
0
votes

I can help you with this :

public class HomeMenuTabLayout extends TabLayout {

public static final int HOME_MENU_TABLAYOUT_COUNT = 5;

public static final int HOME_MENU_LIVE_INDEX = 0;
public static final int HOME_MENU_TEAM_INDEX = 1;
public static final int HOME_MENU_ADS_INDEX = 2;
public static final int HOME_MENU_WALLET_INDEX = 3;
public static final int HOME_MENU_POST_INDEX = 4;

public int selectedIndex = 0;
private TextView unread;

public HomeMenuTabLayout(Context context) {
    super(context);
    initItems(context);
}

public HomeMenuTabLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    initItems(context);
}

public HomeMenuTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initItems(context);
}

public void initItems(Context context) {
    for (int i = 0; i < HOME_MENU_TABLAYOUT_COUNT; i++) {
        addTab(newTab());
    }

    for (int i = 0; i < HOME_MENU_TABLAYOUT_COUNT; i++) {
        TabLayout.Tab tab = this.getTabAt(i);
        tab.setCustomView(getTabView(context, i, false));
    }
}

public void setTagIndex(Context context, int index) {
    getTabView(context, selectedIndex, false);
    selectedIndex = index;
    getTabView(context, selectedIndex, true);
}

private View getTabView(Context context, int index, boolean selected) {
    View v = null;
    TabLayout.Tab tab = this.getTabAt(index);
    if (tab != null) {
        v = tab.getCustomView();
        if (v == null) {
            v = LayoutInflater.from(context).inflate(R.layout.activity_main_tab_layout, null);
        }
    }

    if (v == null) {
        return null;
    }

    ImageView img = v.findViewById(R.id.tablayout_image);
    int color = 0;
    int color2 = 0;
    if (selected) {
        color = getResources().getColor(R.color.corn_flower_blue);
        color2 = getResources().getColor(R.color.dark_sky_blue_three);
        TmlyUtils.displayViewWithZoom(img);
    } else {
        color = getResources().getColor(R.color.battleship_grey_dark);
        color2 = getResources().getColor(R.color.battleship_grey_dark);
    }

    switch (index) {
        case HOME_MENU_ADS_INDEX:
            Bitmap offers = StyleKit.imageOfTabbarSearchActive(color, color2);
            img.setImageBitmap(offers);
            break;
        case HOME_MENU_LIVE_INDEX:

            Bitmap live = StyleKit.imageOfTabbarHomeActive(color, color2);
            img.setImageBitmap(live);
            unread = v.findViewById(R.id.tablayout_unread);
            break;
        case HOME_MENU_TEAM_INDEX:
            Bitmap team = StyleKit.imageOfTabbarSocialActive(color, color2);
            img.setImageBitmap(team);
            break;
        case HOME_MENU_WALLET_INDEX:
            Bitmap wallet = StyleKit.imageOfTabbarCaddyActive(color, color2);
            img.setImageBitmap(wallet);
            break;
        case HOME_MENU_POST_INDEX:
            Bitmap chat = StyleKit.imageOfTabbarPlusActive(getResources().getColor(R.color.white), getResources().getColor(R.color.white));
            img.setImageBitmap(chat);
            View cirle = v.findViewById(R.id.tablayout_circle);
            cirle.setVisibility(View.VISIBLE);
            break;
        default:
            break;
    }
    return v;
}
}

This is a custom TabLayout you can see I'm extends TabLayout class, when the TabLayout is created I'm calling the initItems method who addTab and set a custom view for it.

The getTabView return the inflated view as you can see with this

 LayoutInflater.from(context).inflate(R.layout.activity_main_tab_layout, null);

If you need it

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="@dimen/tab_bar_height">

    <ImageView
        android:id="@+id/tablayout_circle"
        android:layout_width="@dimen/tab_bar_height"
        android:layout_height="@dimen/tab_bar_height"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:background="@drawable/circle_blue_gradient"
        android:visibility="gone"
        tools:visibility="visible" />

    <ImageView
        android:id="@+id/tablayout_image"
        android:layout_width="@dimen/tab_bar_icon_favorite_height"
        android:layout_height="@dimen/tab_bar_icon_favorite_height"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true" />

</RelativeLayout>

after inflating the view you can get your view element with

  ImageView img = v.findViewById(R.id.tablayout_image);

You can check if the view is selected with the boolean selected, for your case you need to ignore the color switch when the index is the central one.

Just one thing when you click on a TabLayout icon don't forget to call the

setTagIndex();

If you don't do it the image will not be redraw

0
votes

Solution

Gradle:

implementation 'com.github.armcha:SpaceNavigationView:1.6.0'

Layout:

<com.luseen.spacenavigation.SpaceNavigationView
        android:id="@+id/space"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentBottom="true"
        app:active_item_color="@color/color_trans"
        app:centre_button_color="@color/black"
        app:inactive_item_color="@color/color_trans"
        app:space_background_color="@color/white"
        app:space_item_icon_only_size="24dp"
        app:space_item_icon_size="@dimen/space_item_icon_default_size"
        app:space_item_text_size="@dimen/space_item_text_default_size" />

Add Space Navigation items.

 spaceNavigationView = (SpaceNavigationView) findViewById(R.id.space);
 spaceNavigationView.initWithSaveInstanceState(savedInstanceState);

       /*Space navigation View*/
        spaceNavigationView.initWithSaveInstanceState(savedInstanceState);

        spaceNavigationView.addSpaceItem(new SpaceItem(0, "Templates", R.drawable.ic_color_template_tab, getResources().getColor(R.color.color_yellow)));
        spaceNavigationView.addSpaceItem(new SpaceItem(1, "Explore", R.drawable.ic_category_tab, getResources().getColor(R.color.color_bg)));
        spaceNavigationView.addSpaceItem(new SpaceItem(2, "Tools", R.drawable.ic_tools_tab, getResources().getColor(R.color.color_bg)));
        spaceNavigationView.addSpaceItem(new SpaceItem(3, "My Work", R.drawable.ic_my_work_tab, getResources().getColor(R.color.color_bg)));


        spaceNavigationView.setCentreButtonIconColorFilterEnabled(true);
        spaceNavigationView.setCentreButtonIcon(R.drawable.ic_create2_tab);
        spaceNavigationView.setInActiveCentreButtonIconColor(ContextCompat.getColor(this, R.color.white));
        spaceNavigationView.setActiveCentreButtonBackgroundColor(ContextCompat.getColor(this, R.color.color_yellow));
        spaceNavigationView.setCentreButtonColor(ContextCompat.getColor(this, R.color.black));
        spaceNavigationView.setCentreButtonRippleColor(ContextCompat.getColor(this, R.color.color_yellow));
        spaceNavigationView.setCentreButtonSelectable(true);

        spaceNavigationView.setSpaceBackgroundColor(ContextCompat.getColor(this, R.color.obaudiopicker_white));
    //  spaceNavigationView.setCentreButtonSelected(2, R.drawable.ic_color_template_tab, getResources().getColor(R.color.color_yellow));
        spaceNavigationView.setInActiveSpaceItemColor(ContextCompat.getColor(this, R.color.color_bg));

Use initWithSaveInstanceState(savedInstanceState) and override onSaveInstanceState if you want to keep selected item position and badge on device rotation

@Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        spaceNavigationView.onSaveInstanceState(outState);
        Log.i(TAG, "onSaveInstanceState: ");
    }

Set onClick listener

spaceNavigationView.setSpaceOnClickListener(new SpaceOnClickListener() {
            @Override
            public void onCentreButtonClick() {
                setDefaultIcon();
                new Handler().post(new Runnable() {
                    @Override
                    public void run() {
                        spaceNavigationView.setActiveCentreButtonIconColor(ContextCompat.getColor(NEWBusinessCardMainActivity.this, R.color.black));

                    }
                });
                changeCurrentFragmentTo(Constants.ITEM_CREATE);
            }

            @Override
            public void onItemClick(final int itemIndex, String itemName) {
                Log.d("onItemClick ", "" + itemIndex + " " + itemName);
                switch (itemIndex) {
                    case 0:
                        setDefaultIcon();
                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                spaceNavigationView.changeItemIconAtPosition(itemIndex, R.drawable.ic_color_template_tab);
                                spaceNavigationView.changeItemTextColorAtPosition(itemIndex, getResources().getColor(R.color.color_yellow));
                            }
                        });
                        changeCurrentFragmentTo(Constants.ITEM_TEMPLATE);
                        break;
                    case 1:
                        setDefaultIcon();
                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                spaceNavigationView.changeItemIconAtPosition(itemIndex, R.drawable.ic_color_category_tab);
                                spaceNavigationView.changeItemTextColorAtPosition(itemIndex, getResources().getColor(R.color.color_category));

                            }
                        });
                        changeCurrentFragmentTo(Constants.ITEM_CATEGORY);
                        break;
                    case 2:
                        setDefaultIcon();
                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                spaceNavigationView.changeItemIconAtPosition(itemIndex, R.drawable.ic_color_tool_tab);
                                spaceNavigationView.changeItemTextColorAtPosition(itemIndex, getResources().getColor(R.color.color_tools));

                            }
                        });
                        changeCurrentFragmentTo(Constants.ITEM_TOOLS);
                        break;
                    case 3:
                        setDefaultIcon();
                        new Handler().post(new Runnable() {
                            @Override
                            public void run() {
                                spaceNavigationView.changeItemIconAtPosition(itemIndex, R.drawable.ic_color_my_work_tab);
                                spaceNavigationView.changeItemTextColorAtPosition(itemIndex, getResources().getColor(R.color.color_mywork));

                            }
                        });
                        changeCurrentFragmentTo(Constants.ITEM_MY_WORK);
                        break;
                }
            }

            @Override
            public void onItemReselected(int itemIndex, String itemName) {
   //        Toast.makeText(MainActivity.this, itemIndex + " " + itemName, Toast.LENGTH_SHORT).show();

            }
        });

setDefaultIcon()

private void setDefaultIcon() {
        spaceNavigationView.changeItemIconAtPosition(0, R.drawable.ic_template_tab, getResources().getColor(R.color.color_bg));
        spaceNavigationView.changeItemIconAtPosition(1, R.drawable.ic_category_tab, getResources().getColor(R.color.color_bg));
        spaceNavigationView.changeItemIconAtPosition(2, R.drawable.ic_tools_tab, getResources().getColor(R.color.color_bg));
        spaceNavigationView.changeItemIconAtPosition(3, R.drawable.ic_my_work_tab, getResources().getColor(R.color.color_bg));


    }

Example:

enter image description here