Evenly spaced menu items on Toolbar
Asked Answered
T

8

20

So I've been trying to implement android.support.v7.widget.Toolbar in my Activity and to make it look similar to the previously supported split ActionBar.

Here's the XML for my Toolbar:

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

Here's the style for the Toolbar I'm using:

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

The style for the Toolbar menu buttons, my initial plan was to calculate the minWidth based on the screen size and then set it for each menu button.

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

And finally, here is what I'm calling in my activity.

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

The problem is that the menu items in the bottom Toolbar are right aligned like this: Right aligned menu items

However I want them to be evenly spaced like this:

Evenly spaced menu items

Tawannatawdry answered 21/10, 2014 at 14:22 Comment(4)
I know that with the ActionBar there is a setCustomView() call that can be used to potentially get the design pattern that would be wanted. And it seems as though the ToolBar class itself also supports multiple custom views on quick glance. Then you should be able to use a LinearLayout with weights.Shiny
MrEngineer13 - have you had any luck with a solution to this problem? Just encountered it tonight, and trying to figure out a way to make it work like the splitActionBar. In theory, the toolbar layout could contain a Button view for each icon, but that obviously defeats the purpose of the "menu" capability. I've used that approach in my app for a custom menu icon (drawer slider), but I don't want that much overhead for general menu items...Dermatoid
The usual way to have subviews of equal width would be to make their widths match_parent and to have an equal layout_weight. Have you tried that?Primatology
I'm looking for ways to center the existing menu items. I would like to not have to add new ones and just let the system handle the menu stuff.Tawannatawdry
T
11

Here's what worked* for me:

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

Layout.xml

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

When I say worked I mean that it centered EVERYTHING in my menu, text and images alike. If you only use icons for your menu then it will look great. I'm still looking for a way to center them and have the text to be right next to the icons.

Tawannatawdry answered 7/1, 2015 at 14:11 Comment(8)
This works great, with one minor caveat (other than the aforementioned) - the horizontal alignment is off. The left-most icon has a wider margin than the right-most. I've got 4 in my "split toolbar" and they don't appear to line up as you would expect. It is more noticeable with 5 - the center icon is shifted to the right by 10dp~. Not a huge issue, but enough for OCD to kick in ;-) Otherwise, thank you for the effort on this - it is much better than the default from Google. With the general rule to only have 1 FAB, I don't know how they expect things to work without the split actionbar...Dermatoid
the offset can be countered by calling toolbar.setContentInsetsAbsolute(0,0);Ton
@Ton - very nice. Thank you. I had removed the contentInsert from the XML definition, but didn't think to check to do in the code. Thanks - this works perfectly, OCD can rest easy!Dermatoid
It's not working if my menu item have icon not only text, Please help me!Wenzel
nice solution. have you noticed that with a single menu item, the button captures the available screen width (ripple effect long press), but as soon as you add another item, the buttons only take 48dpUrata
i have implemented your code but have the issue import android.support.v7.internal.view.menu.MenuItemImpl; is show an error like cannot reolved. i dont know how to solve it.Leptospirosis
Used this, Helped a lot.Bahia
Brilliant solution @MrEngineer13. Just one change needed. Import statement should be import android.support.v7.view.menu.MenuItemImpl; instead of import android.support.v7.internal.view.menu.MenuItemImpl;Disperse
P
10

UPDATE

Google now has very similar functionality inflated the normal ways menus inflate using a new Widget call BottomNavigationView

-- Original Answer --

Guys this took me some time to figure out and here you go its a little bit of a heavy operation but it works.

I use this on a Toolbar to display across the bottom of the screen like the old SplitActionBar...

BEHOLD the evenly distributed MenuItems across your Toolbar

I would not recommend using more than 5 or 6 items, it may get a little crowded...

/**
 * 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);
                }
            }
        }
    }
}
Propertied answered 4/2, 2015 at 4:40 Comment(6)
Thanks. I found this the most convenient way; I could declare and use the toolbar as normal, just had to make a call to this method. No need for custom toolbars, inflaters, etc.Ezzell
This works great except for low resolution screens.I have a toolbar at the bottom with 5 visible items and the rest in an overflow menu. On screens with a width of more than 320 dp everything looks fine (C). On low resolutions (width: 320 px ldpi) the last button overlaps the overflow menu (B). If you remove the line childView.setLayoutParams(toolbarParams); there is no overlapping but the buttons are not centered vertically (A). ScreenshotsTaciturnity
It doesn't work for me. Just does nothing :( I use the latest version of the Android Studio, API 21-27.Opening
@Opening look at developer.android.com/reference/android/support/design/widget/… it accomplishes the same thing and is supported out of the boxPropertied
@Opening no it does not. You are looking for design support library and tabs for that behavior.Propertied
Please show how to do it for BottomNavigationView. It has plenty of issues when I try to use it...Lavinia
H
3

Check this out.

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

Replace ImageView with whatever you want.

Horseshoes answered 7/1, 2015 at 11:39 Comment(1)
Wait, what's the point in having a Toolbar here? It won't put the items in the overflow menu item if there isn't enough space...Lavinia
C
1

Here is a solution I have posted for another similar question, since on my bottom toolbar I wanted equally spaced buttons: android add two toolbars in the same activity

Cayuga answered 3/5, 2016 at 13:6 Comment(0)
I
1

This solution take the best from each of above solutions , Thanks to inner_class7 ,Kuffs & MrEngineer13.
This solution evenly distribute the menu items and shows the text .

public class EvenlyDistributedToolbar extends 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" />
Implausibility answered 18/12, 2016 at 14:1 Comment(1)
Could you please explain, why it's better than other solutions?Opening
T
0

If you create your menu programmatically rather than by inflating from resources, you can do this:

Use the SplitToolbar as mentioned in another answer. Get a reference to the toolbar using FindViewById as normal. If the toolbar does not exist in the layout, the menu functions as a normal non-split version.

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

Then in your menu creation code do the following.

@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; 
}
Ton answered 14/1, 2015 at 9:41 Comment(0)
P
0

My recommendation is to follow the design guidelines. If you are using a toolbar then leave the menu items where they are designed to go.

enter image description here

However, if you want equal spacing then consider using Tabs

enter image description here

or a Bottom Navigation Bar

enter image description here

This answer tells how to set up a Bottom Navigation Bar.

Petiole answered 16/9, 2017 at 8:20 Comment(2)
These guidelines are terrible.Opening
Whether they are terribly or not I suppose is a matter of taste. However, by using the official guidelines, your app will match the overall system design and users will naturally understand the navigation. There are reasons to ignore these guidelines at times, but I'm saying that most developers should not.Petiole
A
0

The solution proposed by public void setupEvenlyDistributedToolbar(){} by kandroidj works perfectly. However to make the solution a bit more complete you need a custom OnclickListener:

attached is my implementation

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);
            }
        });
    }
}
Adore answered 26/9, 2018 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.