Равномерно расположенные элементы меню на панели инструментов

Итак, я пытался внедрить android.support.v7.widget.Toolbar в свою активность и сделать ее похожей на ранее поддерживаемую разделенную панель действий.

Вот XML для моей панели инструментов:

<android.support.v7.widget.Toolbar
    android:id="@+id/toolbar_btm"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:minHeight="?attr/actionBarSize"
    android:background="@color/toolbar_bkgnd"
    android:layout_alignParentBottom="true"
    app:theme="@style/ToolBarTheme" />

Вот стиль панели инструментов, который я использую:

<style name="ToolBarTheme" parent="Theme.AppCompat">
    <item name="actionButtonStyle">@style/ActionButtonStyle</item>
    <item name="android:actionButtonStyle">@style/ActionButtonStyle</item>
    <item name="android:textColor">@android:color/white</item>
</style>

Стиль для кнопок меню панели инструментов. Мой первоначальный план состоял в том, чтобы рассчитать minWidth на основе размера экрана, а затем установить его для каждой кнопки меню.

<style name="ActionButtonStyle" parent="@android:style/Widget.Holo.Light.ActionButton">
    <item name="android:minWidth">56dip</item>
    <item name="android:paddingLeft">0dip</item>
    <item name="android:paddingRight">0dip</item>
</style>

И, наконец, вот что я называю в своей деятельности.

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar_btm);
toolbarBtm.inflateMenu(R.id.menu);

Проблема в том, что пункты меню внизу Toolbar выровнены по правому краю следующим образом: Элементы меню с выравниванием по правому краю

Однако я хочу, чтобы они были равномерно распределены следующим образом:

Равномерно расположенные элементы меню


person MrEngineer13    schedule 21.10.2014    source источник
comment
Я знаю, что с ActionBar есть вызов setCustomView(), который можно использовать для потенциального получения желаемого шаблона проектирования. И кажется, что сам класс ToolBar также поддерживает несколько пользовательских представлений при быстром просмотре. Тогда вы сможете использовать LinearLayout с весами.   -  person Jay Snayder    schedule 21.10.2014
comment
MrEngineer13 - Вам повезло с решением этой проблемы? Только что столкнулся с этим сегодня вечером и попытался найти способ заставить его работать как splitActionBar. Теоретически макет панели инструментов может содержать представление Button для каждого значка, но это, очевидно, противоречит цели возможностей меню. Я использовал этот подход в своем приложении для пользовательского значка меню (ползунка ящика), но мне не нужны такие большие накладные расходы для общих пунктов меню...   -  person Kyle    schedule 07.01.2015
comment
Обычный способ иметь подпредставления одинаковой ширины - сделать их ширину match_parent и иметь равную layout_weight. Вы пробовали это?   -  person Craig McMahon    schedule 07.01.2015
comment
Я ищу способы центрировать существующие пункты меню. Я хотел бы не добавлять новые и просто позволить системе обрабатывать меню.   -  person MrEngineer13    schedule 07.01.2015


Ответы (8)


Вот что сработало* у меня:

EnhancedMenuInflater.java

import android.support.v4.internal.view.SupportMenuItem;
import android.support.v7.internal.view.menu.MenuItemImpl;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import here.is.your.R;

public class EnhancedMenuInflater {

    public static void inflate(MenuInflater inflater, Menu menu, boolean forceVisible) {
        inflater.inflate(R.menu.menu, menu);

        if (!forceVisible) {
            return;
        }

        int size = menu.size();
        for (int i = 0; i < size; i++) {
            MenuItem item = menu.getItem(i);
            // check if app:showAsAction = "ifRoom"
            if (((MenuItemImpl) item).requestsActionButton()) {
                item.setShowAsAction(SupportMenuItem.SHOW_AS_ACTION_ALWAYS);
            }
        }
    }
}

MainActivity.java

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    if (toolbar == null) {
        EnhancedMenuInflater.inflate(getMenuInflater(), menu, false);
    }
    return super.onCreateOptionsMenu(menu);
}

// somewhere after views have been set.
if (toolbar != null) {
    EnhancedMenuInflater.inflate(getMenuInflater(), toolbar.getMenu(), true);
    toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
        @Override
        public boolean onMenuItemClick(MenuItem item) {
            return onOptionsItemSelected(item);
        }
    });
}

SplitToolbar.java

import android.content.Context;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class SplitToolbar extends Toolbar {
    public SplitToolbar(Context context) {
        super(context);
    }

    public SplitToolbar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SplitToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (child instanceof ActionMenuView) {
            params.width = LayoutParams.MATCH_PARENT;
        }
        super.addView(child, params);
    }
}

Макет.xml

<here.is.my.SplitToolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"/>

Когда я говорю «сработало», я имею в виду, что в моем меню было сосредоточено ВСЕ, как текст, так и изображения. Если вы используете только значки для своего меню, оно будет выглядеть великолепно. Я все еще ищу способ центрировать их, чтобы текст располагался рядом с иконками.

person MrEngineer13    schedule 07.01.2015
comment
Это отлично работает, с одним небольшим предостережением (кроме вышеупомянутого) - горизонтальное выравнивание отключено. Крайний левый значок имеет более широкое поле, чем крайний правый. У меня есть 4 на моей разделенной панели инструментов, и они, похоже, не выстраиваются в линию, как вы ожидаете. С 5 это более заметно - центральная иконка сдвинута вправо на 10dp~. Не большая проблема, но достаточно, чтобы ОКР сработало ;-) В остальном спасибо за усилия по этому поводу - это намного лучше, чем по умолчанию от Google. С общим правилом иметь только 1 FAB, я не знаю, как они ожидают, что все будет работать без разделенной панели действий... - person Kyle; 09.01.2015
comment
смещение можно отменить, вызвав toolbar.setContentInsetsAbsolute(0,0); - person Kuffs; 14.01.2015
comment
@Kuffs - очень мило. Спасибо. Я удалил contentInsert из определения XML, но не подумал проверить, что делать в коде. Спасибо - это работает отлично, ОКР может спать спокойно! - person Kyle; 16.01.2015
comment
Это не работает, если у моего пункта меню есть значок, а не только текст, пожалуйста, помогите мне! - person Ibrahim Disouki; 27.05.2015
comment
хорошее решение. Вы заметили, что с одним пунктом меню кнопка захватывает доступную ширину экрана (эффект пульсации при длительном нажатии), но как только вы добавляете другой элемент, кнопки занимают только 48 dp - person esskar; 18.09.2015
comment
Я реализовал ваш код, но у меня возникла проблема с импортом android.support.v7.internal.view.menu.MenuItemImpl; показывает ошибку, как не может быть reolved. я не знаю, как это решить. - person MohanRaj S; 29.04.2016
comment
Пользовался этим, очень помогло. - person Relm; 13.05.2016
comment
Блестящее решение @MrEngineer13. Требуется всего одно изменение. Оператор импорта должен быть import android.support.v7.view.menu.MenuItemImpl; вместо импорта android.support.v7.internal.view.menu.MenuItemImpl; - person Siju; 20.06.2016

ОБНОВЛЕНИЕ

Google теперь имеет очень похожую функциональность, расширенную обычными способами расширения меню с помощью нового вызова виджета Вид нижней навигации

-- Оригинальный ответ --

Ребята, мне потребовалось некоторое время, чтобы понять, и вот, это немного тяжелая операция, но она работает.

Я использую это на Toolbar для отображения в нижней части экрана, как старый SplitActionBar...

ВЗГЛЯДИТЕ на равномерно распределенные элементы меню на панели инструментов

Я бы не рекомендовал использовать более 5 или 6 предметов, может быть немного тесно...

/**
 * This method will take however many items you have in your  
 * menu/menu_main.xml and distribute them across your devices screen
 * evenly using a Toolbar. Enjoy!!
 */
public void setupEvenlyDistributedToolbar(){
    // Use Display metrics to get Screen Dimensions
    Display display = getWindowManager().getDefaultDisplay();
    DisplayMetrics metrics = new DisplayMetrics();
    display.getMetrics(metrics);

    // Toolbar
    mToolbar = (Toolbar) findViewById(R.id.navigationToolbar);
    // Inflate your menu
    mToolbar.inflateMenu(R.menu.menu_bottom);

    // Add 10 spacing on either side of the toolbar
    mToolbar.setContentInsetsAbsolute(10, 10);

    // Get the ChildCount of your Toolbar, this should only be 1
    int childCount = mToolbar.getChildCount();
    // Get the Screen Width in pixels
    int screenWidth = metrics.widthPixels;

    // Create the Toolbar Params based on the screenWidth
    Toolbar.LayoutParams toolbarParams = new Toolbar.LayoutParams(screenWidth, LayoutParams.WRAP_CONTENT);

    // Loop through the child Items
    for(int i = 0; i < childCount; i++){
        // Get the item at the current index
        View childView = mToolbar.getChildAt(i);
        // If its a ViewGroup
        if(childView instanceof ViewGroup){
            // Set its layout params
            childView.setLayoutParams(toolbarParams);
            // Get the child count of this view group, and compute the item widths based on this count & screen size
            int innerChildCount = ((ViewGroup) childView).getChildCount();
            int itemWidth  = (screenWidth / innerChildCount);               
            // Create layout params for the ActionMenuView
            ActionMenuView.LayoutParams params = new ActionMenuView.LayoutParams(itemWidth, LayoutParams.WRAP_CONTENT);
            // Loop through the children
            for(int j = 0; j < innerChildCount; j++){
                View grandChild = ((ViewGroup) childView).getChildAt(j);
                if(grandChild instanceof ActionMenuItemView){
                    // set the layout parameters on each View
                    grandChild.setLayoutParams(params);
                }
            }
        }
    }
}
person kandroidj    schedule 04.02.2015
comment
Спасибо. Я нашел это наиболее удобным способом; Я мог объявить и использовать панель инструментов как обычно, просто нужно было вызвать этот метод. Нет необходимости в пользовательских панелях инструментов, инфляторах и т. д. - person Mendhak; 27.02.2015
comment
Это прекрасно работает, за исключением экранов с низким разрешением. У меня есть панель инструментов внизу с 5 видимыми элементами, а остальные в меню переполнения. На экранах шириной более 320 dp все выглядит нормально (С). В низком разрешении (ширина: 320 px ldpi) последняя кнопка перекрывает дополнительное меню (B). Если вы удалите строку childView.setLayoutParams(toolbarParams); перекрытия нет, но кнопки расположены не по центру по вертикали (A). Скриншоты - person floppes; 10.12.2015
comment
Это не работает для меня. Просто ничего не делает :( Я использую последнюю версию Android Studio, API 21-27. - person Dmitry; 13.05.2018
comment
@Dmitry посмотрите на developer.android.com/reference/android/support/ design/widget/ выполняет то же самое и поддерживается "из коробки" - person kandroidj; 14.05.2018
comment
@ Дмитрий, нет. Вы ищете библиотеку поддержки дизайна и вкладки для такого поведения. - person kandroidj; 14.05.2018

Проверь это.

<android.support.v7.widget.Toolbar
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:abc="http://schemas.android.com/apk/res-auto"
        android:id="@+id/toolbar"
        android:layout_height="?attr/actionBarSize"
        android:layout_width="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/action1"
            android:background="@color/red_700"/>

        <ImageView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/action2"
            android:background="@color/red_200"/>

        <ImageView
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:id="@+id/action3"
            android:background="@color/red_100"/>

    </LinearLayout>

</android.support.v7.widget.Toolbar>

Замените ImageView на что хотите.

person natario    schedule 07.01.2015

Вот решение, которое я опубликовал для другого похожего вопроса, так как на моей нижней панели инструментов мне нужны кнопки с одинаковым интервалом: android добавить две панели инструментов в одно действие

person Giorgio Barchiesi    schedule 03.05.2016

Это решение берет лучшее из каждого из вышеперечисленных решений благодаря inner_class7, Kuffs и MrEngineer13.
Это решение равномерно распределяет пункты меню и отображает текст.

открытый класс EvenlyDistributedToolbar расширяет android.support.v7.widget.Toolbar {

    private View actionMenuView;
    public EvenlyDistributedToolbar(Context context) {
        super(context);
        setContentInsetsAbsolute(0, 0);
    }

    public EvenlyDistributedToolbar(Context context, AttributeSet attrs) {
        super(context, attrs);
        setContentInsetsAbsolute(0, 0);
    }

    public EvenlyDistributedToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setContentInsetsAbsolute(0, 0);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (child instanceof ActionMenuView) {
            actionMenuView  = child ;
            params.width = LayoutParams.MATCH_PARENT;

            ((ViewGroup)actionMenuView).setOnHierarchyChangeListener(new OnHierarchyChangeListener() {

                @Override 
                public void onChildViewRemoved(View parent, View child) {

                } 

                @Override 
                public void onChildViewAdded(View parent, View child) {
                    if (child instanceof ActionMenuItemView) {
                        //Show the menu item text as well as the the icon
                        ActionMenuItemView actionMenuItemView = (ActionMenuItemView) child;
                        // set the layout parameters on each View
                        actionMenuItemView.setExpandedFormat(true);
                        Drawable[] arr = actionMenuItemView.getCompoundDrawables();

                        if (arr != null && arr.length == 4 && arr[0] != null) {
                            actionMenuItemView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
                        }
                        else if (arr != null && arr.length == 4 && arr[2] != null) {
                            actionMenuItemView.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
                        }
                        actionMenuItemView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16);
                        actionMenuItemView.setOnLongClickListener(null);

                    }
                } 
            }); 
        }
        super.addView(child, params);
    }


    /**
     * Show All items, call after the menu inflated
     */
    public void showAll() {
        Menu menu = getMenu();

        int size = menu.size();
        for (int i = 0; i < size; i++) {
            MenuItem item = menu.getItem(i);
            // check if app:showAsAction = "ifRoom"
            if (((MenuItemImpl) item).requestsActionButton()) {
                item.setShowAsAction(SupportMenuItem.SHOW_AS_ACTION_ALWAYS);
            }
        }
    }


}

        <com.util.EvenlyDistributedToolbar
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content" />
person avi    schedule 18.12.2016
comment
Не могли бы вы объяснить, почему это лучше, чем другие решения? - person Dmitry; 13.05.2018

Если вы создаете свое меню программно, а не путем раздувания из ресурсов, вы можете сделать это:

Используйте SplitToolbar, как указано в другом ответе. Получите ссылку на панель инструментов, используя FindViewById, как обычно. Если панель инструментов отсутствует в макете, меню функционирует как обычная неразделенная версия.

import android.content.Context;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class SplitToolbar extends Toolbar {
    public SplitToolbar(Context context) {
        super(context);
    }

    public SplitToolbar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SplitToolbar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        if (child instanceof ActionMenuView) {
            params.width = LayoutParams.MATCH_PARENT;
        }
        super.addView(child, params);
    }
}

Затем в коде создания меню сделайте следующее.

@Override
public boolean onPrepareOptionsMenu(Menu menu) {

    if (toolbar != null) {
       toolbar.setContentInsetsAbsolute(0,0);
        menu = toolbar.getMenu();
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem menuItem) {
                // Call back to the original menu code to handle menu clicks
                return onOptionsItemSelected(menuItem);
            }
        });
    }

    // Now build your menu as normal
    menu.clear();

    MenuItem b = menu.add(0, WHATEVER, 0, R.string.WHATEVER);
             b.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
             b.setIcon(R.drawable.ic_menu_encrypt);
    // End of normal menu code

    // Now set the button options.
    if (toolbar != null) {
        int size = menu.size();
        for (int i = 0; i < size; i++) {
            MenuItem item = menu.getItem(i);
            // check if app:showAsAction = "ifRoom"
            if (((MenuItemImpl) item).requestsActionButton()) {
                item.setShowAsAction(SupportMenuItem.SHOW_AS_ACTION_ALWAYS);
            }
        }
    }
    Return true; 
}
person Kuffs    schedule 14.01.2015

Я рекомендую следовать руководству по дизайну. Если вы используете панель инструментов, оставьте элементы меню, для которых они предназначены.

введите здесь описание изображения

Однако, если вы хотите использовать равные интервалы, рассмотрите возможность использования вкладки.

введите здесь описание изображения

или нижняя панель навигации

введите здесь описание изображения

В этом ответе рассказывается, как настроить нижнюю панель навигации.

person Suragch    schedule 16.09.2017
comment
Эти рекомендации ужасны. - person Dmitry; 13.05.2018
comment
Являются ли они ужасно или нет, я полагаю, это дело вкуса. Однако, используя официальные рекомендации, ваше приложение будет соответствовать общему дизайну системы, и пользователи, естественно, поймут навигацию. Есть причины игнорировать эти рекомендации время от времени, но я говорю, что большинство разработчиков не должны этого делать. - person Suragch; 13.05.2018

Решение, предложенное public void setupEvenlyDistributedToolbar(){} автором kandroidj, отлично работает. Однако, чтобы сделать решение немного более полным, вам нужен собственный OnclickListener:

прилагается моя реализация

private void setupOnClickListener4Toolbar(Toolbar toolbar) {
    Menu bottomMenu = toolbar.getMenu();
    for (int i = 0; i < bottomMenu.size(); i++) {
        bottomMenu.getItem(i).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                return onOptionsItemSelected(item);
            }
        });
    }
}
person Pedro Campos    schedule 26.09.2018