Как отобразить значки в меню переполнения в панели действий

Я знаю, что это невозможно с использованием собственного API. Есть ли обходной путь для реализации такого представления?


person Mihir    schedule 22.08.2013    source источник
comment
Неважно, что я хочу реализовать для своего приложения.   -  person Mihir    schedule 22.08.2013
comment
Хорошо, но если у вас есть другое приложение, которое делает это, то можно поиграть и, возможно, выяснить, как работает их реализация. Хорошим местом для начала может быть библиотека ActionBarSherlock, так как она создает аналогичное представление для более старых (до 4.0) устройств. Не должно быть слишком сложно настроить эту реализацию.   -  person Alex MDC    schedule 22.08.2013
comment
Он также использует собственные классы внутри, поэтому это не сработает.   -  person Mihir    schedule 22.08.2013


Ответы (15)


Ранее опубликованный ответ в целом в порядке. Но это в основном удаляет поведение по умолчанию меню переполнения. Такие вещи, как количество значков, которые могут отображаться на разных размерах экрана, а затем они выпадают в дополнительное меню, когда не могут отображаться. Делая вышеперечисленное, вы удаляете много важных функций.

Лучшим методом было бы указать дополнительному меню отображать значки напрямую. Вы можете сделать это, добавив следующий код в свой 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);
}
person Simon    schedule 09.03.2014
comment
Это должен быть принятый ответ, другой - более быстрый трюк. - person Yoann Hercouet; 03.04.2014
comment
Хорошее решение. Поскольку он опирается на внутренний класс Android (com.android.internal.view.MenuBuilder), мне интересно, насколько он надежен, особенно когда выпускаются новые уровни API. Надеемся, что Google когда-нибудь представит эту функциональность в общедоступном API (хотя этот ответ Романа Нурика из Google не обнадеживает). - person Ted Hopp; 15.07.2014
comment
2.3.7 не имеет панели действий и по умолчанию имеет значки в меню параметров. - person Simon; 24.07.2014
comment
@TedHopp, вы можете думать об этом как о максимальных усилиях, большую часть времени вы будете видеть значки, но в худшем случае это ничего не делает: вы не потеряете основные функции :) Хотя создание этой функции и не устанавливать android:title должно быть большим нет-нет. - person TWiStErRob; 30.11.2014
comment
Это решение отлично работает в сборках отладки, но значки не отображаются в сборке выпуска с включенным proguard. - person Jose Gómez; 15.03.2015
comment
Вышеупомянутая проверка menu.getClass().getSimpleName() возвращает i. - person Jose Gómez; 15.03.2015
comment
Это связано с тем, что вы используете AppCompat/SupportLibrary. Вам нужно добавить полное имя класса для меню в ваш скрипт ProGuard. В данном случае это android.support.v7.internal.view.menu.MenuBuilder. - person Simon; 15.03.2015
comment
Только что обнаружил, собирался опубликовать ответ :) Теперь отлично работает, спасибо. - person Jose Gómez; 15.03.2015
comment
Предупреждение: onMenuOpened(FEATURE_ACTION_BAR) больше не вызывается в appcompat-v7:22.x, это может быть или не быть преднамеренным, см. b.android.com/171440 . Можно переместить код в onPrepareOptionsMenu. - person TWiStErRob; 03.05.2015
comment
Этот подход перестал работать с последними версиями библиотеки поддержки... вместо этого переопределите метод onPrepareOptionsPanel()... см. этот другой ответ: stackoverflow .com/a/30337653/684582 - person Alécio Carvalho; 01.06.2015
comment
Как я могу отобразить значки меню переполнения для контекстной панели действий? - person Aritra Roy; 27.06.2015
comment
featureId == Window.FEATURE_ACTION_BAR не работает, и я исправляю это с помощью (featureId & Window.FEATURE_ACTION_BAR) == Window.FEATURE_ACTION_BAR, работает! - person Ninja; 12.01.2016
comment
Этот метод больше не скрытый, а общедоступный, и вызов его на onCreateOptionsMenu(...) работает - person Louis CAD; 16.08.2016
comment
Это не работает, если ваш класс расширяет AppCompatActivity. Решение для AppCompatActivity находится здесь: stackoverflow.com/a/35499253/555762 - person Uroš Podkrižnik; 16.08.2016
comment
не забудьте добавить что-нибудь в свои правила proguard, иначе это, вероятно, не удастся с режимом выпуска; -keepclassmembers class **.MenuBuilder { void setOptionalIconsVisible(boolean); } - person trooper; 30.08.2016
comment
Используйте решение с вложенным меню - см. ниже: В вашем меню xml используйте... Основная причина: 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). - person miguelt; 20.07.2018
comment
Используйте решение В вашем меню xml...: - При использовании отражения: 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). ; При использовании кастинга: MenuBuilder.setOptionalIconsVisible can only be called from within the same library group (groupId=androidx.appcompat) less... - MenuBuilder помечены как @RestrictTo(LIBRARY_GROUP) - person miguelt; 20.07.2018
comment
Что ж, увидев все эти хаки, я думаю, что мои пользователи могут жить без иконок. - person lasec0203; 01.05.2021

В вашем меню xml используйте следующий синтаксис для вложения меню, вы начнете получать меню со значками

<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>

person iBabur    schedule 01.12.2013
comment
Я не думал, что это будет так просто! - person Mohammad Ersan; 02.12.2013
comment
после многих попыток я тоже думал о том же, я изменил это для какой-то другой задачи, но он также начал показывать значки. - person iBabur; 02.12.2013
comment
Изображение @drawable/ic_action_overflow по умолчанию используется в Android или должно быть размещено в выпадающем списке. - person jyomin; 04.12.2013
comment
он частный, поэтому он не будет доступен напрямую, вам нужно поместить его в свои ресурсы. вы можете получить их по следующей ссылке developer.android.com/design/downloads/index.html - person iBabur; 04.12.2013
comment
рядом с иконками есть немного лишнего места, которое нужно обрезать. - person techtinkerer; 23.11.2015
comment
@techtinkerer каким образом ты этого добился? - person sanevys; 02.12.2015
comment
я еще не выполнил это. Кто-то предложил попробовать actionLayout. Я попробую это в ближайшее время. Это было легко сделать с помощью растягиваемой строки, но это может быть глупым способом. - person techtinkerer; 05.12.2015
comment
Это должно быть отмечено как правильное решение. Отражение и кастинг могут завершиться ошибкой в ​​будущем (библиотеки androidx пометили MenuBuilder как @RestrictTo(LIBRARY_GROUP)) - person miguelt; 20.07.2018

Попробовал это на основе предыдущих ответов, и он отлично работает, по крайней мере, с более поздними версиями библиотеки поддержки (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;
}
person lbarbosa    schedule 15.02.2017
comment
Спасибо, отражение меня пугает. :) Было бы неплохо, чтобы это стало принятым ответом. - person William T. Mallard; 16.04.2017
comment
Работает с красивой простотой. Спасибо. - person dazed; 07.08.2017
comment
Использование поддержки Android 25.3.1 MenuBuilder.setOptionalIconsVisible можно вызывать только из той же группы библиотек (groupId=com.android.support) - person Francesco Vadicamo; 13.08.2017
comment
@Франческо Вадикамо @SuppressLint("RestrictedApi") - person tim4dev; 19.07.2018
comment
Здесь это не работает, возможно, потому, что я определил другой значок на панели инструментов (то есть, когда значок переполнения исчез). Кроме того, вызов m.setOptionalIconsVisible ограничен пакетом, из которого он вызывается, см. также комментарий tim4dev. - person carl; 22.07.2019

Вы можете использовать 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);
}
person Desmond Lua    schedule 11.09.2015
comment
Это отличное решение, не нужно полагаться на внутренние поля! - person nicopico; 28.09.2015
comment
заявление о возврате отсутствует - person King of Masses; 12.02.2018

Ответ от Саймона был очень полезен для меня, поэтому я хочу поделиться тем, как я реализовал его в методе onCreateOptionsMenu, как было предложено:

@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);
}
person user2366975    schedule 22.08.2015
comment
Поскольку метод setOptionalIconsVisible является локальным для пакета, в итоге я создал пакет в своем проекте с тем же именем и создал вспомогательный класс, который не должен использовать отражение. 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); } } } - person Makotosan; 03.03.2016
comment
@Makotosan Этот метод больше не является частным пакетом! ;) - person Louis CAD; 16.08.2016

Основываясь на ответе @Desmond Lua из выше, я создал статический метод для использования drawable, объявленного в XML в раскрывающемся списке, и гарантируя, что его окрашенный цвет не влияет на состояние Constant Drawable.

    /**
 * 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);
    }
}

И его использование будет выглядеть примерно так при использовании в действии. Это, вероятно, может быть даже более элегантным в зависимости от ваших индивидуальных потребностей.

@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;
}
person Kevin Grant    schedule 10.08.2016
comment
Мне просто интересно, эксперт по Android, как исправить/отрегулировать вертикальное выравнивание текста значка?! это просто? ваше здоровье! - person Fattie; 21.08.2016
comment
Не уверен, что это пришло мне в голову, но мое внутреннее чутье было бы просто добавить визуальное дополнение к самой иконке (например, в фотошопе), чтобы получить ее именно там, где вы хотите. Или, может быть, есть что-то в этом LineHeightSpan, посмотрите этот другой ответ на него stackoverflow.com/a/11120208/409695 - person Kevin Grant; 01.09.2016
comment
Да, фантастика! Работает отлично!! Превосходно! Только следите за тем, чтобы setTint() проверял используемую версию Android. - person Beppi's; 30.11.2016
comment
добавить builder.setSpan (новый ForegroundColorSpan (цвет), 1, builder.length (), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); если вы хотите тот же цвет для текста - person Hatzegopteryx; 03.01.2021

Текущее лучшее, но не принятое решение, вероятно, работает на старых платформах. В любом случае в новом AppCompat21+ требуемый метод не существует, а метод getDeclaredMethod возвращает исключение NoSuchMethodException.

Поэтому обходной путь для меня (протестирован и работает на устройствах 4.x, 5.x) основан на прямом изменении фонового параметра. Так что просто поместите этот код в свой класс Activity.

@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);
}
person Menion Asamm    schedule 15.02.2015
comment
Я не уверен, как вы сломали решение Саймона, но метод не менялся в период с августа 2014 г. по май 2015 г., он также счастливо работает на Android 5.1.1 - person TWiStErRob; 03.05.2015
comment
Возможно ли, что вы забыли сообщить об этом ProGuard? -keepclassmembers **.MenuBuilder { void setOptionalIconsVisible(boolean); } - person TWiStErRob; 03.05.2015
comment
Хм, это возможно. Обычно я тестирую незапутанный код, но это возможно. Если у вас нет проблем на устройствах 5.x, примите это как еще одно альтернативное решение, не более того. - person Menion Asamm; 03.05.2015

Самый простой способ, который я нашел, это:

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;
}        `
person Paras Singh    schedule 04.10.2018

Ответ @Simon действительно работает хорошо... но если вы используете AppCompat Activity... вместо этого вам нужно будет использовать этот код... Потому что onMenuOpened() больше не вызывается в 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);
    }
person Vishal Kumar    schedule 25.05.2016

Мой простой мод для отличного решения Саймона для использования с 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;
    }
person Kaamel    schedule 11.10.2015

По моему мнению, это возможно только путем создания пользовательской панели инструментов. Потому что ActionBar по умолчанию не дает вам этой функции. Но вы можете поместить значки, взяв подменю в качестве дочернего элемента элемента. И если у вас есть лучшее решение, чем у меня... просто сообщите мне.

<?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> 
person MashukKhan    schedule 14.09.2016

котлин:

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

Это слишком поздно, но кто-то может помочь мне попытаться, я получил помощь от ответа @Desmond Lua, который помогает тем, кто использует menu.xml

Мой ответ для создания динамического меню, вот мой код:

  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);
}
person MilapTank    schedule 08.03.2016

Добавьте это в стиле:

<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>
person Dashrath Rathod    schedule 20.12.2015

Я воспользовался предложением MashukKhan, но я всегда показывал элемент держателя как действие и использовал больше точек по вертикали из векторных активов в качестве значка.

<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>

Это создает эффект «переполнения» меню и отображает значки в раскрывающемся списке.
Можно даже поместить элементы перед ним, если они не конфликтуют с его отображением.
Я нахожу решение MashukKhan элегантным и оно подходит для решения, а не для обхода категории, потому что это просто реализация подменю.

person Chuck    schedule 07.09.2019