29

So I have a menu item, that's defined as:

<item
    android:id="@+id/action_live"
    android:title="@string/action_live"
    android:orderInCategory="1"
    app:showAsAction="ifRoom|withText" />

It shows as text, as you can see below:

Screenshot 1

And I want to programmatically change the "LIVE" text color. I've searched for a while and I found a method:

With globally defined:

private Menu mOptionsMenu;

and:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    mOptionsMenu = menu;
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

I do:

MenuItem liveitem = mOptionsMenu.findItem(R.id.action_live);
SpannableString s = new SpannableString(liveitem.getTitle().toString());
s.setSpan(new ForegroundColorSpan(Color.RED), 0, s.length(), 0);
liveitem.setTitle(s);

But nothing happens!

If I do the same for an item of the overflow menu, it works:

Screenshot 2

Is there some limitation for app:showAsAction="ifRoom|withText" items? Is there any workaround?

Thanks in advance.

2
  • Hi @danielnovais92, were you able to resolve this issue? I am having the same issue Commented Aug 8, 2016 at 23:35
  • This is how I resolved my case, my menu items are in a list and when I change the contents of the menu, the list was not being updated. Doing notifyDataSetChanged to the List's adapter fixed it for me Commented Aug 9, 2016 at 17:42

9 Answers 9

20

Bit late to the party with this one, but I spent a while working on this and found a solution, which may be of use to anyone else trying to do the same thing. Some credit goes to Harish Sridharan for steering me in the right direction.

You can use findViewById(R.id.MY_MENU_ITEM_ID) to locate the menu item (provided that the menu had been created and prepared), and cast it to a TextView instance as suggested by Harish, which can then be styled as required.

public class MyAwesomeActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
    super.onCreate(savedInstanceState);

    // Force invalidatation of the menu to cause onPrepareOptionMenu to be called
    invalidateOptionsMenu();
}

private void styleMenuButton() {
    // Find the menu item you want to style
    View view = findViewById(R.id.YOUR_MENU_ITEM_ID_HERE);

    // Cast to a TextView instance if the menu item was found
    if (view != null && view instanceof TextView) {
        ((TextView) view).setTextColor( Color.BLUE ); // Make text colour blue
        ((TextView) view).setTextSize(TypedValue.COMPLEX_UNIT_SP, 24); // Increase font size
    }
}

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    boolean result = super.onPrepareOptionsMenu(menu);
    styleMenuButton();
    return result;
}

}

The trick here is to force the menu to be invalidated in the activity's onCreate event (thereby causing the onPrepareMenuOptions to be called sooner than it would normally). Inside this method, we can locate the menu item and style as required.

Sign up to request clarification or add additional context in comments.

7 Comments

i'm getting view as null
@Zafer Did you remember to change R.id.YOUR_MENU_ITEM_ID_HERE to use the correct ID of your TextView?
findViewById return null, I use toolbar
Could you specify more about R.id.YOUR_MENU_ITEM_ID_HERE? Is this the id you give the menu in the res/menu/menu.xml file for that item, or is it something else?
a lot has changed you might not be able to cast MenuItem to View but you can surely do this. Menu menu = toolbar.getMenu(); MenuItem item = menu.findItem(menuResId); SpannableString s = new SpannableString(item.getTitle()); s.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, colorRes)), 0, s.length(), 0); item.setTitle(s);
|
15

@RRP give me a clue ,but his solution does not work for me. And @Box give a another, but his answer looks a little not so cleaner. Thanks them. So according to them, I have a total solution.

private static void setMenuTextColor(final Context context, final Toolbar toolbar, final int menuResId, final int colorRes) {
    toolbar.post(new Runnable() {
        @Override
        public void run() {
            View settingsMenuItem =  toolbar.findViewById(menuResId);
            if (settingsMenuItem instanceof TextView) {
                if (DEBUG) {
                    Log.i(TAG, "setMenuTextColor textview");
                }
                TextView tv = (TextView) settingsMenuItem;
                tv.setTextColor(ContextCompat.getColor(context, colorRes));
            } else { // you can ignore this branch, because usually there is not the situation
                Menu menu = toolbar.getMenu();
                MenuItem item = menu.findItem(menuResId);
                SpannableString s = new SpannableString(item.getTitle());
                s.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, colorRes)), 0, s.length(), 0);
                item.setTitle(s);
            }

        }
    });
}

1 Comment

Great! This solution don't need to force cast MenuItem to TextView
7

In order to change the colour of menu item you can find that item, extract the title from it, put it in a Spannable String and set the foreground colour to it. Try out this code piece

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu, menu);
    MenuItem mColorFullMenuBtn = menu.findItem(R.id.action_submit); // extract the menu item here

    String title = mColorFullMenuBtn.getTitle().toString();
    if (title != null) {
        SpannableString s = new SpannableString(title);
        s.setSpan(new ForegroundColorSpan(Color.parseColor("#FFFFFF")), 0, s.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE); // provide whatever color you want here.
        mColorFullMenuBtn.setTitle(s);
    }
   return super.onCreateOptionsMenu(menu);
}

Comments

4

It only becomes a text view after inspection, its real class is ActionMenuItemView, on which we can further set the text color like this:

public static void setToolbarMenuItemTextColor(final Toolbar toolbar,
                                               final @ColorRes int color,
                                               @IdRes final int resId) {
    if (toolbar != null) {
        for (int i = 0; i < toolbar.getChildCount(); i++) {
            final View view = toolbar.getChildAt(i);
            if (view instanceof ActionMenuView) {
                final ActionMenuView actionMenuView = (ActionMenuView) view;
                // view children are accessible only after layout-ing
                actionMenuView.post(new Runnable() {
                    @Override
                    public void run() {
                        for (int j = 0; j < actionMenuView.getChildCount(); j++) {
                            final View innerView = actionMenuView.getChildAt(j);
                            if (innerView instanceof ActionMenuItemView) {
                                final ActionMenuItemView itemView = (ActionMenuItemView) innerView;
                                if (resId == itemView.getId()) {
                                    itemView.setTextColor(ContextCompat.getColor(toolbar.getContext(), color));
                                }
                            }
                        }
                    }
                });
            }
        }
    }
}

2 Comments

Thank you for this. Sometimes Android kills me with how complicated they make something as simple as setting a text color...
you can make this shorter with this. Menu menu = toolbar.getMenu(); MenuItem item = menu.findItem(menuResId); SpannableString s = new SpannableString(item.getTitle()); s.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, colorRes)), 0, s.length(), 0); item.setTitle(s);
3

You could put the change of the color in the onPrepareOptionsMenu:

override fun onPrepareOptionsMenu(menu: Menu?): Boolean 
{

    val signInMenuItem = menu?.findItem(R.id.menu_main_sign_in)

    val title = signInMenuItem?.title.toString()

    val spannable = SpannableString(title)

    spannable.setSpan(
       ForegroundColorSpan(Color.GREEN),
       0,
       spannable.length,
       Spannable.SPAN_INCLUSIVE_INCLUSIVE)

    SgnInMenuItem?.title = spannable

    return super.onPrepareOptionsMenu(menu)

}

of course you can make it shorter above...

now you can change the color appearance upon other (ie. viewmodel) values...

RG

Comments

2

I spent a lot of hours on this and finally got it into work. There is easy solusion for Android 6 and 7 but it doesn't work on Android 5. This code works on all of them. So, if you are doing it in Kotlin this is my suggestion:

override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.my_menu, menu)
        setToolbarActionTextColor(menu, R.color.black)
        this.menu = menu
        return true
}

private fun setToolbarActionTextColor(menu: Menu, color: Int) {
        val tb = findViewById<Toolbar>(R.id.toolbar)
        tb?.let { toolbar ->
            toolbar.post {
                val view = findViewById<View>(R.id.my_tag)
                if (view is TextView) {
                    view.setTextColor(ContextCompat.getColor(this, color))
                } else {
                    val mi = menu.findItem(R.id.my_tag)
                    mi?.let {
                        val newTitle: Spannable = SpannableString(it.title.toString())
                        val newColor = ContextCompat.getColor(this, color)
                        newTitle.setSpan(ForegroundColorSpan(newColor),
                                0, newTitle.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                        it.title = newTitle
                    }
                }
            }
        }
}

Comments

1

It's complicated, but you can use the app:actionLayout attribute. For example,

my_menu.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/englishList"
        android:orderInCategory="1"
        app:showAsAction="ifRoom|withText"
        app:actionLayout="@layout/custom_menu_item_english_list"
        android:title=""/>
</menu>

custom_menu_item_english_list.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_vertical">
    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/englishListWhiteText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="#FFFFFF"
        android:lineHeight="16dp"
        android:textSize="16sp"
        android:text="英文"
        android:layout_marginRight="8dp"/>
</LinearLayout>

MainActivity.java:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.my_menu, menu);
    MenuItem item = menu.findItem(R.id.englishList); 
    item.getActionView().findViewById(R.id.englishListWhiteText)
        .setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v){
                //Handle button click. 
            }
        });
    return true;
}

Result:

enter image description here

More Detailed Example= https://medium.com/@info.anikdey003/custom-menu-item-using-action-layout-7a25118b9d5

Comments

1

if you are using popup menu function to show the menu items in the application and trying to change the design or color of your text items in the menu list, first create a style item in your style.xml file:

<style name="PopupMenuStyle" parent="Widget.AppCompat.PopupMenu">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_gravity">center</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:textColor">@color/ColorPrimary</item>
    <item name="android:textSize">@dimen/textsize</item>
    <item name="android:fontFamily">@font/myfonts</item></style>

and use this style in your code as:

val popupWrapper = ContextThemeWrapper(this, R.style.PopupMenuStyle)
val popup = PopupMenu(popupWrapper, your_menu_view)

Comments

0

MenuItem as defined by documentation is an interface. It will definitely be implemented with a view widget before being portrayed as an menu. Most cases these menu items are implemented as TextView. You can use UiAutomatorViewer to see the view hierarchy or even use hierarchyviewer which will be found in [sdk-home]/tools/. Attached one sample uiautomatorviewer screenshot for a MenuItem

So you can always typecast your MenuItem and set the color.

TextView liveitem = (TextView)mOptionsMenu.findItem(R.id.action_live);
liveitem.setTextColor(Color.RED);

EDIT:

Since there was request to see how to use this tool, I'm adding a few more contents.

Make sure you have set environment variable $ANDROID_HOME pointing to your SDK HOME.

In your terminal:

cd $ANDROID_HOME
./tools/uiautomatorviewer

This tool will open up.

The second or third button (refer screenshot) in the menu will capture the screenshot of your attached device or emulator and you can inspect the view and their hierarchy. Clicking on the view will describe the view and their information. It is tool purposely designed for testing and you can inspect any application.

Refer developer site for more info: uiautomatorviewer

uiautomatorviewer

5 Comments

please provide the terminal commands to launch this tool, so everyone can know how to bring this up..
When I tried that I got an error saying "MenuItemImpl cannot be cast to TextView". I also tried TextView livetv = (TextView) liveitem.getActionView(); livetv.setTextColor(Color.RED); but I get an: "Attempt to invoke virtual method 'void android.widget.TextView.setTextColor(int)' on a null object reference"
This doesn't seem to work. I have the same issue as @danielnovais92.
The casting works for me, but the color does not change. :(
Not work. Show error MenuItemImpl cannot be cast to TextView

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.