Changing the Android Overflow menu icon programmatically
You actually can programmatically change the overflow icon using a little trick. Here's an example:
Create a style for the overflow menu and pass in a content description
<style name="Widget.ActionButton.Overflow" parent="@android:style/Widget.Holo.ActionButton.Overflow">
<item name="android:contentDescription">@string/accessibility_overflow</item>
</style>
<style name="Your.Theme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
<item name="android:actionOverflowButtonStyle">@style/Widget.ActionButton.Overflow</item>
</style>
Now call ViewGroup.findViewsWithText
and pass in your content description. So, something like:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// The content description used to locate the overflow button
final String overflowDesc = getString(R.string.accessibility_overflow);
// The top-level window
final ViewGroup decor = (ViewGroup) getWindow().getDecorView();
// Wait a moment to ensure the overflow button can be located
decor.postDelayed(new Runnable() {
@Override
public void run() {
// The List that contains the matching views
final ArrayList<View> outViews = new ArrayList<>();
// Traverse the view-hierarchy and locate the overflow button
decor.findViewsWithText(outViews, overflowDesc,
View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
// Guard against any errors
if (outViews.isEmpty()) {
return;
}
// Do something with the view
final ImageButton overflow = (ImageButton) outViews.get(0);
overflow.setImageResource(R.drawable.ic_action_overflow_round_red);
}
}, 1000);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Add a dummy item to the overflow menu
menu.add("Overflow");
return super.onCreateOptionsMenu(menu);
}
View.findViewsWithText
was added in API level 14, so you'll have to use your own compatibility method:
static void findViewsWithText(List<View> outViews, ViewGroup parent, String targetDescription) {
if (parent == null || TextUtils.isEmpty(targetDescription)) {
return;
}
final int count = parent.getChildCount();
for (int i = 0; i < count; i++) {
final View child = parent.getChildAt(i);
final CharSequence desc = child.getContentDescription();
if (!TextUtils.isEmpty(desc) && targetDescription.equals(desc.toString())) {
outViews.add(child);
} else if (child instanceof ViewGroup && child.getVisibility() == View.VISIBLE) {
findViewsWithText(outViews, (ViewGroup) child, targetDescription);
}
}
}
Results
Adneal's answer is great and I was using it until recently. But then I wanted my app to make use of material design and thus Theme.AppCompat.*
style and android.support.v7.widget.Toolbar
.
Yes, it stopped working and I was trying to fix it by setting Your.Theme
's parent to @style/Widget.AppCompat.ActionButton.Overflow
. It worked by propertly setting contentDescription
but then it failed when casting to ImageButton
. It turned out in latest (version 23) android.support.v7
class OverflowMenuButton
extends from AppCompatImageView
. Changing casting class was enought to make it work with Toolbar on Nexus 5 running Lollipop.
Then I ran it on Galaxy S4 with KitKat and no matter what I tried I couldn't set overflow's contentDescription
to my custom value. But in AppCompat styles I found it already has default value:
<item name="android:contentDescription">@string/abc_action_menu_overflow_description</item>
So why not use it? Also by Hannes idea (in comments) I implemented listener, to get rid of some random time for delay in postDelayed
. And as overflow icon is already in AppCompat library, then I would use it as well - I am applying color filter, so I don't need any icon resource on my own.
My code based on Adneal's work with Android Lollipop improvements:
public static void setOverflowButtonColor(final Activity activity) {
final String overflowDescription = activity.getString(R.string.abc_action_menu_overflow_description);
final ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
final ViewTreeObserver viewTreeObserver = decorView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
final ArrayList<View> outViews = new ArrayList<View>();
decorView.findViewsWithText(outViews, overflowDescription,
View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION);
if (outViews.isEmpty()) {
return;
}
AppCompatImageView overflow=(AppCompatImageView) outViews.get(0);
overflow.setColorFilter(Color.CYAN);
removeOnGlobalLayoutListener(decorView,this);
}
});
}
and as per another StackOverflow answer:
public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
}
else {
v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
}
}
of course instead of Color.CYAN
you can use your own color - activity.getResources().getColor(R.color.black);
EDIT: Added support for latest AppCompat library (23), which uses AppCompatImageView For AppCompat 22 you should cast overflow button to TintImageView