74

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

Falko
  • 15,326
  • 12
  • 50
  • 91
Mihir
  • 2,014
  • 2
  • 17
  • 28
  • 26
    Doesn't matter I want to implement for my app. – Mihir Aug 22 '13 at 10:07
  • Fine, but if you have another app doing it then it's possible to have a play around and maybe figure out how their implementation works. A good place to start may be the ActionBarSherlock lib, as that creates a similar view for older (pre 4.0) devices. It shouldn't be too hard to tweak that implementation. – Alex MDC Aug 22 '13 at 10:13
  • It also uses native classes at the inside so it wont work out. – Mihir Aug 22 '13 at 11:44

16 Answers16

86

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);
}
Simon
  • 9,933
  • 44
  • 46
  • 5
    This should be the accepted answer, the other one is more a quick trick. – Yoann Hercouet Apr 03 '14 at 12:19
  • 1
    Nice solution. Since it relies on an internal Android class (`com.android.internal.view.MenuBuilder`), I wonder how reliable this is, particularly as new API levels are released. Hopefully Google will someday expose this functionality in a public API (although [this response by Google's Roman Nurik](http://stackoverflow.com/a/5216625/535871) isn't encouraging). – Ted Hopp Jul 15 '14 at 04:10
  • 2
    2.3.7 doesn't have an ActionBar and by default it has icons in the Options Menu. – Simon Jul 23 '14 at 22:59
  • @TedHopp you can think of it as best effort, most of the time you'll see the icons, but in the worst case it doesn't do anything: it's not like you'll lose core functionality :) Although building this "feature" and not setting `android:title` must be a big no-no. – TWiStErRob Nov 30 '14 at 13:49
  • This solution works great in debug builds, but icons do not appear in a release build with proguard enabled. – Jose Gómez Mar 15 '15 at 05:08
  • The check "menu.getClass().getSimpleName()" above returns "i". – Jose Gómez Mar 15 '15 at 05:17
  • 4
    This is because you are using the AppCompat/SupportLibrary. You need to add the full class name for the menu to your ProGuard script. In this case its "android.support.v7.internal.view.menu.MenuBuilder" – Simon Mar 15 '15 at 05:42
  • Just discovered it, was going to post the response :) Works great now, thanks. – Jose Gómez Mar 15 '15 at 05:53
  • 14
    **Warning**: `onMenuOpened(FEATURE_ACTION_BAR)` is not called any more in `appcompat-v7:22.x`, this may or may not be intentional, see http://b.android.com/171440 . It should be ok to move the code to `onPrepareOptionsMenu`. – TWiStErRob May 02 '15 at 23:43
  • 4
    This approach stopped working on the latest versions of the support library...override the onPrepareOptionsPanel() method instead..see this other answer: http://stackoverflow.com/a/30337653/684582 – Alécio Carvalho Jun 01 '15 at 14:38
  • How can I show icons of the Overflow menu for Contextual Action Bar? – Aritra Roy Jun 27 '15 at 04:09
  • 4
    `featureId == Window.FEATURE_ACTION_BAR ` not work, and I fix it with `(featureId & Window.FEATURE_ACTION_BAR) == Window.FEATURE_ACTION_BAR`, It works! – Ninja Jan 12 '16 at 08:36
  • This method is no longer hidden, but now public, anc calling it on `onCreateOptionsMenu(...)` works – Louis CAD Aug 16 '16 at 07:38
  • This doesn't work if your class extends AppCompatActivity. Solution for AppCompatActivity is here: http://stackoverflow.com/a/35499253/555762 – Uroš Podkrižnik Aug 16 '16 at 07:43
  • 1
    don't forget to add something to your proguard rules or this will probably fail w/ release mode; `-keepclassmembers class **.MenuBuilder { void setOptionalIconsVisible(boolean); }` – trooper Aug 30 '16 at 16:59
  • Use solution with nested menu - see below: [In your menu xml, use the...](https://stackoverflow.com/a/20318161/1953000) Main reason: `Accessing internal APIs via reflection is not supported and may not work on all devices or in the future less... Using reflection to access hidden/private Android APIs is not safe; it will often not work on devices from other vendors, and it may suddenly stop working (if the API is removed) or crash spectacularly (if the API behavior changes, since there are no guarantees for compatibility). ` – miguelt Jul 20 '18 at 02:34
  • Use solution "[In your menu xml...](https://stackoverflow.com/a/20318161/1953000)": - If using reflection: `Using reflection to access hidden/private Android APIs is not safe; it will often not work on devices from other vendors, and it may suddenly stop working (if the API is removed) or crash spectacularly (if the API behavior changes, since there are no guarantees for compatibility). ` ; If using casting: `MenuBuilder.setOptionalIconsVisible can only be called from within the same library group (groupId=androidx.appcompat) less...` - `MenuBuilder` tagged as `@RestrictTo(LIBRARY_GROUP)` – miguelt Jul 20 '18 at 02:40
  • Well after seeing all of these hacks, I think my users can live without icons. – lasec0203 May 01 '21 at 04:21
61

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>

iBabur
  • 934
  • 12
  • 13
  • 2
    I didn't think it would be that easy! – Mohammad Ersan Dec 01 '13 at 22:42
  • after trying alot I was also thinking the same, I make this changed for some other task but it also started showing icons. – iBabur Dec 01 '13 at 22:49
  • @drawable/ic_action_overflow image is bydefault in android or have to place it in the drawable – jyomin Dec 04 '13 at 11:44
  • its private so it will not directly accessible, you need to place it in your resources. you can get those from following link http://developer.android.com/design/downloads/index.html – iBabur Dec 04 '13 at 15:02
  • there is bit extra space next to the icons, which needs to be trimmed. – techtinkerer Nov 23 '15 at 19:14
  • 1
    @techtinkerer any way you accomplished that? – sanevys Dec 02 '15 at 07:55
  • i didn't accomplish this yet. Someone suggested trying an actionLayout. I am trying that one soon. It was easy to do this with a spannable string but that might be a silly way to accomplish it. – techtinkerer Dec 05 '15 at 05:08
  • This should be marked as the correct solution. Reflection and casting may fail in the future (androidx libs have tagged `MenuBuilder` as `@RestrictTo(LIBRARY_GROUP)`) – miguelt Jul 20 '18 at 02:42
57

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;
}
lbarbosa
  • 1,656
  • 14
  • 19
  • 2
    Thanks, reflection skeers me. :) It would be nice to have this become the accepted answer. – William T. Mallard Apr 16 '17 at 17:06
  • 1
    Works with beautiful simplicity. Thanks. – dazed Aug 07 '17 at 20:42
  • 7
    Using Android Support 25.3.1 `MenuBuilder.setOptionalIconsVisible` can only be called from within the same library group (groupId=com.android.support) – Francesco Vadicamo Aug 13 '17 at 08:52
  • 3
    @FrancescoVadicamo `@SuppressLint("RestrictedApi")` – tim4dev Jul 19 '18 at 19:23
  • This does not work here, possibly because I have defined another icon on the ToolBar (that is when the overflow icon disappeared). Also, the call to m.setOptionalIconsVisible is restricted to the package from which it is called, see also tim4dev comment. – carl Jul 22 '19 at 08:50
24

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);
}
Desmond Lua
  • 5,153
  • 4
  • 34
  • 44
14

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);
}
user2366975
  • 3,548
  • 7
  • 39
  • 76
  • 2
    Since the "setOptionalIconsVisible" method is package-local, I ended up creating a package in my project with the same name and created a helper class that doesn't have to use reflection. `package android.support.v7.view.menu; import android.view.Menu; public class Menus { public static void setOptionalIconsVisible(Menu menu) { if (menu instanceof MenuBuilder) { MenuBuilder menuBuilder = (MenuBuilder) menu; menuBuilder.setOptionalIconsVisible(true); } } }` – Makotosan Mar 03 '16 at 14:22
  • 1
    @Makotosan This method is no longer package private! ;) – Louis CAD Aug 16 '16 at 07:37
7

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;
}
Community
  • 1
  • 1
Kevin Grant
  • 2,131
  • 21
  • 16
  • I was just wondering, Android expert, how to fix/adjust the vertical alignment of icon text?! is it simple? cheers! – Fattie Aug 21 '16 at 14:20
  • Not sure off the top of my head, but my gut instinct would just be to add visual padding on the icon itself (e.g. in photoshop) to get it exactly where you want it. Or maybe theres something to this LineHeightSpan, check out this other answer on it http://stackoverflow.com/a/11120208/409695 – Kevin Grant Aug 31 '16 at 21:56
  • Yes, fantastic! Works great!! Superb! Only watch out that setTint() should verify the version of Android to be used. – Beppi's Nov 30 '16 at 16:42
  • add builder.setSpan(new ForegroundColorSpan(color), 1, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); if you want the same color for the text – Hatzegopteryx Jan 03 '21 at 13:22
5

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);
}
Community
  • 1
  • 1
Menion Asamm
  • 938
  • 11
  • 19
  • I'm not sure how you broke Simon's solution, but the [method didn't change between Aug 2014-May 2015](https://github.com/android/platform_frameworks_support/blob/7fa6a00a4600aac591402398c23fea97721adf26/v7/appcompat/src/android/support/v7/internal/view/menu/MenuBuilder.java#L1296), it also [sits happily on Android 5.1.1](https://github.com/android/platform_frameworks_base/blob/android-5.1.1_r1/core/java/com/android/internal/view/menu/MenuBuilder.java#L1248) – TWiStErRob May 02 '15 at 23:52
  • 3
    Is it possible you forgot to tell ProGuard about it? `-keepclassmembers **.MenuBuilder { void setOptionalIconsVisible(boolean); }` – TWiStErRob May 02 '15 at 23:55
  • Hmm, it is possible. I'm usually testing on unobfuscated code, but it is possible. If you have no troubles on 5.x devices, take it as an another alternative solution, nothing more. – Menion Asamm May 03 '15 at 16:55
5

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;
}        `
Paras Singh
  • 59
  • 1
  • 2
3

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);
    }
Vishal Kumar
  • 3,811
  • 1
  • 21
  • 30
2

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;
    }
Kaamel
  • 1,782
  • 1
  • 18
  • 21
2

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> 
MashukKhan
  • 1,516
  • 1
  • 23
  • 37
2

kotlin:

@SuppressLint("RestrictedApi")
fun Menu.showOptionalIcons() {
    this as MenuBuilder
    setOptionalIconsVisible(true)
}
LLL
  • 31
  • 2
1

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>
Bryan
  • 13,244
  • 9
  • 62
  • 114
Dashrath Rathod
  • 378
  • 4
  • 13
1

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);
}
MilapTank
  • 9,383
  • 7
  • 35
  • 52
0
 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();
        }
    }
}
chry
  • 424
  • 7
  • 5
0

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.

Chuck
  • 159
  • 1
  • 7