How to Align Android Toolbar Menu/Icons to the Left Like in Google Maps App

How can I align Android Toolbar menu/icons to the left like in Google Maps app?

After some struggling and digging in Android Toolbar code I managed to make it work. Basically, the idea is to add a new android.support.v7.widget.ActionMenuView as child of the Toolbar, set its gravity to top|start, and then add the menu to that action menu view in your Activity. Here is the code:

my_toolbar.xml

<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/tToolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
android:background="?attr/colorPrimary"
android:gravity="center_vertical|start"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

<android.support.v7.widget.ActionMenuView
android:id="@+id/amvMenu"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
</android.support.v7.widget.Toolbar>

my_activity.xml

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

<!--Toolbar-->
<include
android:id="@+id/tToolbar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
layout="@layout/my_toolbar" />
</RelativeLayout>

MyActivity.java

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public final class MyActivity extends ActionBarActivity {
private ActionMenuView amvMenu;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// this layout includes the custom toolbar my_toolbar.xml
setContentView(R.layout.my_activity);

Toolbar t = (Toolbar) findViewById(R.id.tToolbar);
amvMenu = (ActionMenuView) t.findViewById(R.id.amvMenu);
amvMenu.setOnMenuItemClickListener(new ActionMenuView.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
return onOptionsItemSelected(menuItem);
}
});

setSupportActionBar(t);
getSupportActionBar().setTitle(null);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
// use amvMenu here
inflater.inflate(R.menu.my_activity_menu, amvMenu.getMenu());
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Do your actions here
return true;
}
}

Aligning an overflow menu item's icon with the Toolbar Title

70dp is a non-standard height for a toolbar which is the underlying issue with your layout. If you stay with the 70dp toolbar height, you will need to make adjustments to the toolbar layout.

Here is one way that just modifies the XML layout. The 7dp padding in the toolbar will shift the icon down. Corresponding changes to the text view keep it aligned.

<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="70dp"
android:background="@android:color/holo_blue_light"
android:elevation="4dp"
android:paddingTop="7dp"
app:layout_scrollFlags="scroll|enterAlways"
app:titleTextColor="@android:color/white">

<TextView
android:id="@+id/toolbar_title"
android:layout_width="wrap_content"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="center_horizontal|top"
android:gravity="center"
android:text="@string/toolbar_title_placeholder"
android:textColor="@android:color/white"
android:textSize="20sp" />
</androidx.appcompat.widget.Toolbar>

To understand what is happening, make the changes and run the app. In Android Studio, go to Tools->Layout Inspector and choose your device and the app. What you will see is a dump of the layout that you can examine.

This method works with what you have posted. It may have side effects with the placement of other views so be on the lookout.

An alternatative involves code to retrieve the view that corresponds to the settings icon. If I recall, it's not pretty.

btw, you don't see the settings icon in Android Studio because menu items are added at run time.

How to put all action items on the left, taking as much space as possible, and yet have overflow on the right?

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.

Sample Image

Sample Image

Sample Image

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>


Related Topics



Leave a reply



Submit