I've been looking for a method to programmatically change the color of the overflow menu icon in android.
The only option I have found is to change the icon permanently by adding a custom style. The problem is that in the nearby future we will need to change this during the use of our app.
Our app is an extension to a series of online-platforms and therefore a user can enter their platform's web-url. These have their own styles and will be fetched by an API call towards the app.
These might adress me to change the color of the icon...
Currently I change other icons in the Actionbar like this:
if (ib != null){
Drawable resIcon = getResources().getDrawable(R.drawable.navigation_refresh);
resIcon.mutate().setColorFilter(StyleClass.getColor("color_navigation_icon_overlay"), PorterDuff.Mode.SRC_ATOP);
ib.setIcon(resIcon);
}
For now I'll have to use the styles.
This can be achieved by setting the android:textColorSecondary theme attribute.
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
As of support 23.1 Toolbar now has getOverflowIcon() and setOverflowIcon() methods, so we can do this much more easily:
public static void setOverflowButtonColor(final Toolbar toolbar, final int color) {
Drawable drawable = toolbar.getOverflowIcon();
if(drawable != null) {
drawable = DrawableCompat.wrap(drawable);
DrawableCompat.setTint(drawable.mutate(), color);
toolbar.setOverflowIcon(drawable);
}
}
There is much better solution. You can do it programmatically in the runtime
toolbar.overflowIcon?.setColorFilter(colorInt, PorterDuff.Mode.SRC_ATOP)
Viola!
There is the less-hacky solution for changing the overflow icon. There is an example how to change overflow icon's color, but you can adapt it to change the image:
private void setOverflowIconColor(int color) {
Drawable overflowIcon = toolbar.getOverflowIcon();
if (overflowIcon != null) {
Drawable newIcon = overflowIcon.mutate();
newIcon.setColorFilter(color, PorterDuff.Mode.MULTIPLY);
toolbar.setOverflowIcon(newIcon);
}
}
Based on @michalbrz answer, I used the below to change the icon itself. :)
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;
}
TintImageView overflow = (TintImageView) outViews.get(0);
//overflow.setColorFilter(Color.CYAN); //changes color
overflow.setImageResource(R.drawable.dots);
removeOnGlobalLayoutListener(decorView, this);
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With