Search code examples
androidandroid-actionbar

Changing the Android Overflow menu icon programmatically


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.


Solution

  • 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

    Example