Here is a solution that will left-justify the menu items while keeping the overflow menu icon to the right. This solution uses the standard implementation of the toolbar/action bar but anticipates how action views will be laid out so they will be positioned as we wish in the toolbar.
Most of the code below is what you have presented. I have moved the for
loop that creates the menu items into onCreateOptionsMenu()
so I could make use of the ActionMenuView
that is already part of the toolbar's menu structure instead of adding another one.
In onCreateOptionsMenu()
a running tally of space consumed by menu items is maintained as menu items are laid into the menu. As long as there is space, menu items will be flagged as "shown" (MenuItem.SHOW_AS_ACTION_ALWAYS). If the item will encroach on the area reserved for the overflow menu icon, the item is laid in but is targeted for the overflow menu (MenuItem.SHOW_AS_ACTION_NEVER).
After all views are laid into the menu, the slack space is computed. This is the area on the screen between the last visible menu item and the overflow icon (if overflow is in used) or between the last visible item and the end of the tool bar (if overflow is not in use.)
Once the slack space is computed, a Space
widget is created and laid into the menu. This widget forces all other items to be left-justified.
Most of the changes have been made to MainActivity.java
, but I may have changed a thing or two in the XML files. I include them here for completeness.
Here are some screen captures of the results.



MainActivity.java
public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = findViewById(R.id.toolbar);
mToolbar.setTitle("");
setSupportActionBar(mToolbar); // Ensures that onCreateOptionsMenu is called
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final float density = getResources().getDisplayMetrics().density;
final int overflowCellSize = (int) (OVERFLOW_CELL_WIDTH * density);
// Other than the overflow icon, this is how much real estate we have to fill.
int widthLeftToFill = mToolbar.getWidth() - overflowCellSize;
// slackWidth is what is left over after we are done adding our action views.
int slackWidth = -1;
for (int i = 0; i < 10; ++i) {
final View menuItemView =
LayoutInflater.from(this).inflate(R.layout.action_item, mToolbar, false);
ImageView imageView = menuItemView.findViewById(android.R.id.icon);
final int itemIconResId = R.drawable.ic_launcher_background;
imageView.setImageResource(itemIconResId);
final String text = "item" + i;
((TextView) menuItemView.findViewById(android.R.id.text1)).setText(text);
final View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(final View view) {
Toast.makeText(MainActivity.this, text,
Toast.LENGTH_SHORT).show();
}
};
menuItemView.setOnClickListener(onClickListener);
final MenuItem menuItem = menu
.add(text).setActionView(menuItemView).setIcon(itemIconResId)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@SuppressLint("MissingPermission")
@Override
public boolean onMenuItemClick(final MenuItem menuItem) {
onClickListener.onClick(menuItemView);
return true;
}
});
// How wide is this ActionView?
menuItemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
widthLeftToFill -= menuItemView.getMeasuredWidth();
if (widthLeftToFill >= 0) {
// The item will fit on the screen.
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
} else {
// The item will not fit. Force it to overflow.
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
if (slackWidth < 0) {
// Just crossed over the limit of space to fill - capture the slack space.
slackWidth = widthLeftToFill + menuItemView.getMeasuredWidth();
}
}
}
if (slackWidth < 0) {
// Didn't have enough action views to fill the width.
slackWidth = widthLeftToFill + overflowCellSize;
}
if (slackWidth > 0) {
// Create a space widget to consume the slack. This slack space widget makes sure
// that the action views are left-justified with the overflow on the right.
// As an alternative, this space could also be distributed among the action views.
Space space = new Space(this);
space.setMinimumWidth(slackWidth);
final MenuItem menuItem = menu.add("").setActionView(space);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
return true;
}
private static final int OVERFLOW_CELL_WIDTH = 40; // dips
}
activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layoutDirection="ltr"
android:padding="0px"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:contentInsetEnd="0px"
app:contentInsetEndWithActions="0px"
app:contentInsetLeft="0px"
app:contentInsetRight="0px"
app:contentInsetStart="0px"
app:contentInsetStartWithNavigation="0px"
app:logo="@null"
app:title="@null"
app:titleMargin="0px"
app:titleTextColor="#757575"
tools:ignore="UnusedAttribute"
tools:title="toolbar">
</android.support.v7.widget.Toolbar>
</FrameLayout>
action_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="false"
android:gravity="center"
android:orientation="horizontal"
android:paddingLeft="8dp">
<ImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="?attr/actionBarSize"
android:scaleType="center"
tools:src="@android:drawable/sym_def_app_icon" />
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="?attr/actionBarSize"
android:layout_marginLeft="6dp"
android:layout_marginStart="6dp"
android:gravity="center"
android:textColor="#c2555555"
android:textSize="15sp"
tools:text="text" />
</LinearLayout>
Update: To use the tool bar without setting it up as an action bar, add a global layout listener to wait until the tool bar is setup.
MainActivity.java - using a global layout listener instead of an action bar
public class MainActivity extends AppCompatActivity {
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = findViewById(R.id.toolbar);
mToolbar.setTitle("");
// setSupportActionBar(mToolbar); // Ensures that onCreateOptionsMenu is called
mToolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mToolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
setupMenu(mToolbar.getMenu());
}
});
}
public boolean setupMenu(Menu menu) {
final float density = getResources().getDisplayMetrics().density;
int mOverflowCellSize = (int) (OVERFLOW_CELL_WIDTH * density);
// Other than the overflow icon, this is how much real estate we have to fill.
int widthLeftToFill = mToolbar.getWidth() - mOverflowCellSize;
// slackWidth is what is left over after we are done adding our action views.
int slackWidth = -1;
for (int i = 0; i < 10; ++i) {
final View menuItemView =
LayoutInflater.from(this).inflate(R.layout.action_item, mToolbar, false);
ImageView imageView = menuItemView.findViewById(android.R.id.icon);
final int itemIconResId = R.drawable.ic_launcher_background;
imageView.setImageResource(itemIconResId);
String text = "item" + i;
((TextView) menuItemView.findViewById(android.R.id.text1)).setText(text);
final View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(final View view) {
Toast.makeText(MainActivity.this, text ,
Toast.LENGTH_SHORT).show();
}
};
menuItemView.setOnClickListener(onClickListener);
final MenuItem menuItem = menu
.add(text).setActionView(menuItemView).setIcon(itemIconResId)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@SuppressLint("MissingPermission")
@Override
public boolean onMenuItemClick(final MenuItem menuItem) {
onClickListener.onClick(menuItemView);
return true;
}
});
// How wide is this ActionView?
menuItemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
widthLeftToFill -= menuItemView.getMeasuredWidth();
if (widthLeftToFill >= 0) {
// The item will fit on the screen.
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
} else {
// The item will not fit. Force it to overflow.
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
if (slackWidth < 0) {
// Just crossed over the limit of space to fill - capture the slack space.
slackWidth = widthLeftToFill + menuItemView.getMeasuredWidth();
}
}
}
if (slackWidth < 0) {
// Didn't have enough action views to fill the width.
slackWidth = widthLeftToFill + mOverflowCellSize;
}
if (slackWidth > 0) {
// Create a space widget to consume the slack. This slack space widget makes sure
// that the action views are left-justified with the overflow on the right.
// As an alternative, this space could also be distributed among the action views.
Space space = new Space(this);
space.setMinimumWidth(slackWidth);
final MenuItem menuItem = menu.add("").setActionView(space);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
return true;
}
private static final int OVERFLOW_CELL_WIDTH = 40; // dips
}
The following sample app separates out menu creation from the left justification of the menu by introducing the method notifyMenuItemsChanged
. In the app, click on the button to remove the menu item at position 1.
This code is basically the same as above, but the Space
widget needs an id so it can be removed to be re-added when the menu changes.
MainActivity.Java: Sample app
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("");
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Menu menu = toolbar.getMenu();
// Remove item at position 1 on click of button.
if (menu.size() > 1) {
menu.removeItem(menu.getItem(1).getItemId());
notifyMenuItemsChanged(toolbar);
}
}
});
toolbar.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
toolbar.getViewTreeObserver().removeOnGlobalLayoutListener(this);
setupMenu(toolbar);
}
});
}
private void setupMenu(Toolbar toolbar) {
Menu menu = toolbar.getMenu();
// Since we are resetting the menu, get rid of what may have been placed there before.
menu.clear();
for (int i = 0; i < 10; ++i) {
final View menuItemView =
LayoutInflater.from(this).inflate(R.layout.action_item, toolbar, false);
ImageView imageView = menuItemView.findViewById(android.R.id.icon);
final int itemIconResId = R.drawable.ic_launcher_background;
imageView.setImageResource(itemIconResId);
String text = "item" + i;
((TextView) menuItemView.findViewById(android.R.id.text1)).setText(text);
final View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(final View view) {
Toast.makeText(MainActivity.this, text ,
Toast.LENGTH_SHORT).show();
}
};
menuItemView.setOnClickListener(onClickListener);
menu.add(Menu.NONE, View.generateViewId(), Menu.NONE, text)
.setActionView(menuItemView)
.setIcon(itemIconResId)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@SuppressLint("MissingPermission")
@Override
public boolean onMenuItemClick(final MenuItem menuItem) {
onClickListener.onClick(menuItemView);
return true;
}
});
}
// Now take the menu and left-justify it.
notifyMenuItemsChanged(toolbar);
}
/**
* Call this routine whenever the Toolbar menu changes. Take all action views and
* left-justify those that fit on the screen. Force to overflow those that don't.
*
* @param toolbar The Toolbar that holds the menu.
*/
private void notifyMenuItemsChanged(Toolbar toolbar) {
final int OVERFLOW_CELL_WIDTH = 40; // dips
final Menu menu = toolbar.getMenu();
final float density = getResources().getDisplayMetrics().density;
final int mOverflowCellSize = (int) (OVERFLOW_CELL_WIDTH * density);
// Other than the overflow icon, this is how much real estate we have to fill.
int widthLeftToFill = toolbar.getWidth() - mOverflowCellSize;
// slackWidth is what is left over after we are done adding our action views.
int slackWidth = -1;
MenuItem menuItem;
// Index of the spacer that will be removed/replaced.
int spaceIndex = View.NO_ID;
if (menu.size() == 0) {
return;
}
// Examine each MenuItemView to determine if it will fit on the screen. If it can,
// set its MenuItem to always show; otherwise, set the MenuItem to never show.
for (int i = 0; i < menu.size(); i++) {
menuItem = menu.getItem(i);
View menuItemView = menuItem.getActionView();
if (menuItemView instanceof Space) {
spaceIndex = menuItem.getItemId();
continue;
}
if (!menuItem.isVisible()) {
continue;
}
// How wide is this ActionView?
menuItemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
widthLeftToFill -= menuItemView.getMeasuredWidth();
if (widthLeftToFill >= 0) {
// The item will fit on the screen.
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
} else {
// The item will not fit. Force it to overflow.
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
if (slackWidth < 0) {
// Just crossed over the limit of space to fill - capture the slack space.
slackWidth = widthLeftToFill + menuItemView.getMeasuredWidth();
}
}
}
if (spaceIndex != View.NO_ID) {
// Assume that this is our spacer. It may need to change size, so eliminate it for now.
menu.removeItem(spaceIndex);
}
if (slackWidth < 0) {
// Didn't have enough action views to fill the width, so there is no overflow.
slackWidth = widthLeftToFill + mOverflowCellSize;
}
if (slackWidth > 0) {
// Create a space widget to consume the slack. This slack space widget makes sure
// that the action views are left-justified with the overflow on the right.
// As an alternative, this space could also be distributed among the action views.
Space space = new Space(this);
space.setMinimumWidth(slackWidth);
// Need an if for the spacer so it can be deleted later if the menu is modified.
// Need API 17+ for generateViewId().
menuItem = menu.add(Menu.NONE, View.generateViewId(), Menu.NONE, "")
.setActionView(space);
menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
}
}
activity_main.xml: Sample app
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Click the button to add/remove item #1 from the menu."/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Click to modify menu" />
</LinearLayout>