Mihir
Mihir

Reputation: 2074

How To show icons in Overflow menu in ActionBar

I know it's not possible using the native API. Is there a workaround to implement that kind of view?

Upvotes: 79

Views: 72087

Answers (18)

Tech Bro
Tech Bro

Reputation: 29

Everyone with their alternatives.

Sadly, onCreateOptionsMenu is deprecated and there's now a better method for it nowadays. It's the addMenuProvider method

public class MainActivity extends AppCompatActivity {
    private MenuProvider menuProvider;
    //Please, use  this addMenuProvider method in the onResume() lifecycle of your activity.
    @Override
    public void onResume() {
        super.onResume();
        if(menuProvider == null) menuProvider = new MenuProvider() {
            @SuppressLint("RestrictedApi")
            @Override
            public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
                menuInflater.inflate(R.menu.toolbar_menu, menu);
                //If you want to add menu items later, then below code is for you.
                //Else, skip the 4 lines of codes below and continue from the if statement
                menu.add(0, 1, 0, "First item in overflow menu");
                menu.findItem(1).setIcon(R.drawable.my_drawable1);
                menu.add(0, 2, 0, "Seconditem in overflow menu");
                menu.findItem(2).setIcon(R.drawable.my_drawable2);
                //For Java version 17 & above
                if (menu instanceof MenuBuilder menuBuilder) {
                    menuBuilder.setOptionalIconsVisible(true);
                }
            }

            @Override
            public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
                return true;
            }
        };

        MainActivity.this.addMenuProvider(menuProvider, MainActivity.this, Lifecycle.State.RESUMED);

   @Override
   public void onPause() {
       //To avoid duplicates, please remove the menu items in the onPause method
       if (menuProvider != null) {
            MainActivity.this.removeMenuProvider(menuProvider);
            menuProvider = null;

        }
   }

}

I used this coding technique because Android Operaing System can decide to destroy your activity without calling the onDestroy method

Upvotes: 0

Trasd
Trasd

Reputation: 108

Enabling icons on the overflow menu can be distilled to just a couple lines of code.

Day late and dollar short, but refining it a little more yields (tested on Android 5, 10, 11, and 12 - debug build):

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_X_activity, menu);
    
    // All other menus show icons, why leave the overflow menu out? It should match.
    if(menu instanceof MenuBuilder) {
        ((MenuBuilder) menu).setOptionalIconsVisible(true);
    }
    
    return super.onCreateOptionsMenu(menu);
}

Upvotes: 0

LLL
LLL

Reputation: 111

kotlin:

@SuppressLint("RestrictedApi")
fun Menu.showOptionalIcons() {
    this as MenuBuilder
    setOptionalIconsVisible(true)
}

Upvotes: 3

Chuck
Chuck

Reputation: 349

I used MashukKhan's suggestion but I showed the holder item as action always and used the vertical more dots from the vector assets as the icon.

<item
    android:orderInCategory="10"
    android:title=""
    android:icon="@drawable/ic_more_vert"
    app:showAsAction="always" >

    <menu>
        <item
            android:id="@+id/action_tst1"
            ...and so on

   </menu>
</item>

This produces the "overflow" menu effect and displays the icons in the drop down.
One can even put items ahead of it if they don't conflict with it showing.
I find MashukKhan's solution elegant and it fits in the solution rather than the work around category because it is just an implementation of a sub menu.

Upvotes: 0

Paras Singh
Paras Singh

Reputation: 69

Easiest Way I found is this :

public boolean onCreateOptionsMenu(Menu menu){
     MenuInflater inflater = getMenuInflater();
     inflater.inflate(R.menu.toolbar_menu,menu);
     if(menu instanceof MenuBuilder) {  //To display icon on overflow menu

          MenuBuilder m = (MenuBuilder) menu; 
          m.setOptionalIconsVisible(true);

     }
   return true;
}        `

Upvotes: 6

lbarbosa
lbarbosa

Reputation: 2152

Tried this based on the previous answers and it works fine, at least with more recent versions of the support library (25.1):

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);

    if(menu instanceof MenuBuilder){
        MenuBuilder m = (MenuBuilder) menu;
        //noinspection RestrictedApi
        m.setOptionalIconsVisible(true);
    }

    return true;
}

Upvotes: 69

chry
chry

Reputation: 434

 public void showContextMenuIconVisible(Menu menu){
    if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
        try {
            Field field = menu.getClass().getDeclaredField("mOptionalIconsVisible");
            field.setAccessible(true);
            field.setBoolean(menu, true);
        } catch (Exception ignored) {
            ignored.printStackTrace();
        }
    }
}

Upvotes: 0

MashukKhan
MashukKhan

Reputation: 1954

According to me this is only possible by creating a custom toolbar. Because default ActionBar doesn't gives you that feature. But you can put icons by taking sub menu as a child of an item. And if you have better solution than me.. just inform me.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">
<item
    android:id="@+id/action_settings"
    android:icon="@drawable/ic_menu_camera"
    android:showAsAction="never"
    android:title="@string/action_settings" />

<item
    android:id="@+id/action_1"
    android:icon="@drawable/ic_menu_gallery"
    android:showAsAction="never"
    android:title="Hello" />

<item
    android:id="@+id/action_search"
    android:icon="@android:drawable/ic_search_category_default"
    android:showAsAction="never"
    android:title="action_search">

    <menu>
        <item
            android:id="@+id/version1"
            android:icon="@android:drawable/ic_dialog_alert"
            android:showAsAction="never"
            android:title="Cup cake" />

        <item
            android:id="@+id/version2"
            android:icon="@drawable/ic_menu_camera"
            android:showAsAction="never"
            android:title="Donut" />


        <item
            android:id="@+id/version3"
            android:icon="@drawable/ic_menu_send"
            android:showAsAction="never"
            android:title="Eclair" />

        <item
            android:id="@+id/version4"
            android:icon="@drawable/ic_menu_gallery"
            android:showAsAction="never"
            android:title="Froyo" />
    </menu>
</item>
</menu> 

Upvotes: 2

Dashrath Rathod
Dashrath Rathod

Reputation: 538

Add this in style:

<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/action_settings"
        app:showAsAction="always"
        android:icon="@drawable/ic_more_vert_white"
        android:orderInCategory="100"
        android:title="">
        <menu>

            <item
                android:id="@+id/Login"
                android:icon="@drawable/ic_menu_user_icon"
                android:showAsAction="collapseActionView|withText"
                android:title="@string/str_Login" />

            <item
                android:id="@+id/str_WishList"
                android:icon="@drawable/ic_menu_wish_list_icon"
                android:showAsAction="collapseActionView"
                android:title="@string/str_WishList" />

            <item
                android:id="@+id/TrackOrder"
                android:icon="@drawable/ic_menu_my_order_icon"
                android:showAsAction="collapseActionView"
                android:title="@string/str_TrackOrder" />

            <item
                android:id="@+id/Ratetheapp"
                android:icon="@drawable/ic_menu_rate_the_apps"
                android:showAsAction="collapseActionView"
                android:title="@string/str_Ratetheapp" />

            <item
                android:id="@+id/Sharetheapp"
                android:icon="@drawable/ic_menu_shar_the_apps"
                android:showAsAction="collapseActionView"
                android:title="@string/str_Sharetheapp" />

            <item
                android:id="@+id/Contactus"
                android:icon="@drawable/ic_menu_contact"
                android:showAsAction="collapseActionView"
                android:title="@string/str_Contactus" />

            <item
                android:id="@+id/Policies"
                android:icon="@drawable/ic_menu_policy_icon"
                android:showAsAction="collapseActionView"
                android:title="@string/str_Policies" />
        </menu>
    </item>
</menu>

Upvotes: 1

Kevin Grant
Kevin Grant

Reputation: 2351

Building on @Desmond Lua's answer from above, I made a static method for using the drawable declared in XML in the dropdown, and ensuring that it's tinted color does not affect the Constant Drawable state.

    /**
 * Updates a menu item in the dropdown to show it's icon that was declared in XML.
 *
 * @param item
 *         the item to update
 * @param color
 *         the color to tint with
 */
private static void updateMenuWithIcon(@NonNull final MenuItem item, final int color) {
    SpannableStringBuilder builder = new SpannableStringBuilder()
            .append("*") // the * will be replaced with the icon via ImageSpan
            .append("    ") // This extra space acts as padding. Adjust as you wish
            .append(item.getTitle());

    // Retrieve the icon that was declared in XML and assigned during inflation
    if (item.getIcon() != null && item.getIcon().getConstantState() != null) {
        Drawable drawable = item.getIcon().getConstantState().newDrawable();

        // Mutate this drawable so the tint only applies here
        drawable.mutate().setTint(color);

        // Needs bounds, or else it won't show up (doesn't know how big to be)
        drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        ImageSpan imageSpan = new ImageSpan(drawable);
        builder.setSpan(imageSpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        item.setTitle(builder);
    }
}

And using it would look something like this when used in an activity. This could probably be even more elegant depending on your individual needs.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_activity_provider_connect, menu);
    int color = ContextCompat.getColor(this, R.color.accent_dark_grey);
    updateMenuWithIcon(menu.findItem(R.id.email), color);
    updateMenuWithIcon(menu.findItem(R.id.sms), color);
    updateMenuWithIcon(menu.findItem(R.id.call), color);
    return true;
}

Upvotes: 8

Vishal Kumar
Vishal Kumar

Reputation: 4617

The answer by @Simon really works well... but of you are using AppCompat Activity... you will need to use this code instead...Because onMenuOpened() is not called any more in appcompat-v7:22.x

@Override
    protected boolean onPrepareOptionsPanel(View view, Menu menu) {
        if(menu != null){
            if(menu.getClass().getSimpleName().equals("MenuBuilder")){
                try{
                    Method m = menu.getClass().getDeclaredMethod(
                            "setOptionalIconsVisible", Boolean.TYPE);
                    m.setAccessible(true);
                    m.invoke(menu, true);
                }
                catch(NoSuchMethodException e){
                    Log.e(Constants.DEBUG_LOG, "onMenuOpened", e);
                }
                catch(Exception e){
                    throw new RuntimeException(e);
                }
            }
        }
        return super.onPrepareOptionsPanel(view, menu);
    }

Upvotes: 3

MilapTank
MilapTank

Reputation: 10076

This is too late but some one may help my try i got help from @Desmond Lua answer that helps for who uses menu.xml

My answer is for dynamic menu creation here is my code:

  int ACTION_MENU_ID =1;
  SpannableStringBuilder builder;
   @Override
  public boolean onCreateOptionsMenu(Menu menu) {

  //this space for icon 
    builder = new SpannableStringBuilder("  " + getString(R.string.your_menu_title));
    builder.setSpan(new ImageSpan(this,  R.drawable.ic_your_menu_icon), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
  //dynamic menu added 
    menu.add(Menu.NONE,ACTION_MENU_ID, Menu.NONE,
             getString(R.string.your_menu_title))
            .setShowAsAction(MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT | MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
  //set icon in overflow menu      
        menu.findItem(ACTION_MENU_ID).setTitle(builder);
}

Upvotes: 1

Kaamel
Kaamel

Reputation: 1852

My simple mod to Simon's excellent solution to use with ActionMode:

 @Override
    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
        if(menu != null){
            if(menu.getClass().getSimpleName().equals("MenuBuilder")){
                try{
                    Method m = menu.getClass().getDeclaredMethod(
                            "setOptionalIconsVisible", Boolean.TYPE);
                    m.setAccessible(true);
                    m.invoke(menu, true);
                }
                catch(NoSuchMethodException e){
                    Log.e(TAG, "onPrepareActionMode", e);
                }
                catch(Exception e){
                    throw new RuntimeException(e);
                }
            }
        }
        return true;
    }

Upvotes: 2

Desmond Lua
Desmond Lua

Reputation: 6290

You can make use of SpannableString

public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_tab, menu);

    MenuItem item = menu.findItem(R.id.action_login);
    SpannableStringBuilder builder = new SpannableStringBuilder("* Login");
    // replace "*" with icon
    builder.setSpan(new ImageSpan(this, R.drawable.login_icon), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    item.setTitle(builder);
}

Upvotes: 26

user2366975
user2366975

Reputation: 4700

The answer from Simon was very useful to me, so I want to share how I implemented it into the onCreateOptionsMenu-method as suggested:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu items for use in the action bar
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.main_action_bar, menu);

    // To show icons in the actionbar's overflow menu:
    // http://stackoverflow.com/questions/18374183/how-to-show-icons-in-overflow-menu-in-actionbar
    //if(featureId == Window.FEATURE_ACTION_BAR && menu != null){
        if(menu.getClass().getSimpleName().equals("MenuBuilder")){
            try{
                Method m = menu.getClass().getDeclaredMethod(
                        "setOptionalIconsVisible", Boolean.TYPE);
                m.setAccessible(true);
                m.invoke(menu, true);
            }
            catch(NoSuchMethodException e){
                Log.e(TAG, "onMenuOpened", e);
            }
            catch(Exception e){
                throw new RuntimeException(e);
            }
        }
    //}

    return super.onCreateOptionsMenu(menu);
}

Upvotes: 18

Menion Asamm
Menion Asamm

Reputation: 1004

Current best, but not accepted solution probably works on older platforms. Anyway in new AppCompat21+, required method not exists and method getDeclaredMethod returns exception NoSuchMethodException.

So workaround for me (tested and working on 4.x, 5.x devices) is based on direct change background parameter. So just place this code into your Activity class.

@Override
public boolean onMenuOpened(int featureId, Menu menu) {
    // enable visible icons in action bar
    if (featureId == Window.FEATURE_ACTION_BAR && menu != null) {
        if (menu.getClass().getSimpleName().equals("MenuBuilder")) {
            try {
                Field field = menu.getClass().
                        getDeclaredField("mOptionalIconsVisible");
                field.setAccessible(true);
                field.setBoolean(menu, true);
            } catch (IllegalAccessException | NoSuchFieldException e) {
                Logger.w(TAG, "onMenuOpened(" + featureId + ", " + menu + ")", e);
            }
        }
    }
    return super.onMenuOpened(featureId, menu);
}

Upvotes: 5

Simon
Simon

Reputation: 11190

The previously posted answer is OK, generally speaking. But it basically removes the default behaviour of the Overflow menu. Things like how many icons can be displayed on different screen-sizes and then they dropped off into the overflow menu when they can't be displayed. By doing the above you remove a lot of important functionality.

A better method would be to tell the overflow menu to display the icons directly. You can do this by adding the following code to your Activity.

@Override
public boolean onMenuOpened(int featureId, Menu menu)
{
    if(featureId == Window.FEATURE_ACTION_BAR && menu != null){
        if(menu.getClass().getSimpleName().equals("MenuBuilder")){
            try{
                Method m = menu.getClass().getDeclaredMethod(
                    "setOptionalIconsVisible", Boolean.TYPE);
                m.setAccessible(true);
                m.invoke(menu, true);
            }
            catch(NoSuchMethodException e){
                Log.e(TAG, "onMenuOpened", e);
            }
            catch(Exception e){
                throw new RuntimeException(e);
            }
        }
    }
    return super.onMenuOpened(featureId, menu);
}

Upvotes: 91

iBabur
iBabur

Reputation: 1004

In your menu xml, use the following syntax to nest menu, you will start getting the menu with icons

<item
    android:id="@+id/empty"
    android:icon="@drawable/ic_action_overflow"
    android:orderInCategory="101"
    android:showAsAction="always">
    <menu>
        <item
            android:id="@+id/action_show_ir_list"
            android:icon="@drawable/ic_menu_friendslist"
            android:showAsAction="always|withText"
            android:title="List"/>
    </menu>
</item>

Upvotes: 66

Related Questions