Android UI, Activities & Navigation

Menus, Toolbars & Material Design

18 min Lesson 9 of 12

Menus, Toolbars & Material Design

A polished Android app does more than display data — it presents its actions clearly and follows the visual language users already know. In this lesson you will master the app bar (the horizontal strip at the top of every screen), inflate menu resources into it, respond to item selections, and apply foundational Material Design principles so every screen looks intentional and professional.

The App Bar: Toolbar vs ActionBar

Early Android put a built-in ActionBar at the top of each Activity. The modern replacement is androidx.appcompat.widget.Toolbar — a regular view you place in your layout. This matters because you can position, style, and animate it freely, embed it inside a CoordinatorLayout for scroll-aware behaviour, and keep full control over its height and colour without theme gymnastics.

The standard setup uses a NoActionBar theme so the system bar is gone, and you promote your own Toolbar as the activity's action bar:

<!-- res/values/themes.xml --> <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.NoActionBar"> <item name="colorPrimary">@color/purple_500</item> <item name="colorPrimaryVariant">@color/purple_700</item> <item name="colorOnPrimary">@color/white</item> </style>
<!-- res/layout/activity_main.xml --> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" android:elevation="4dp" /> </com.google.android.material.appbar.AppBarLayout> <!-- main content below the bar --> </androidx.coordinatorlayout.widget.CoordinatorLayout>

In your Activity, promote the toolbar after calling setContentView:

import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // replaces the system ActionBar getSupportActionBar().setTitle("My App"); // set the displayed title } }
Why setSupportActionBar? It wires your Toolbar into the AppCompat menu system, so onCreateOptionsMenu and onOptionsItemSelected callbacks work identically across all API levels. Without this call those callbacks never fire for toolbar items.

Defining a Menu Resource

Menus are declared in XML under res/menu/. Each <item> has an id, a title (shown in the overflow), and an optional icon. The app:showAsAction attribute controls placement:

  • always — always visible as an icon in the toolbar.
  • ifRoom — show as icon if horizontal space allows, otherwise collapse into the overflow.
  • never — always in the overflow (three-dot menu).
<!-- res/menu/menu_main.xml --> <?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_search" android:title="Search" android:icon="@drawable/ic_search" app:showAsAction="ifRoom" /> <item android:id="@+id/action_settings" android:title="Settings" app:showAsAction="never" /> <item android:id="@+id/action_about" android:title="About" app:showAsAction="never" /> </menu>

Inflating and Handling Menu Items

Override onCreateOptionsMenu to inflate the resource, and onOptionsItemSelected to react to taps. Return true from the latter when you handle an item so Android stops propagating the event.

@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); return true; // returning true shows the menu } @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_search) { // open search UI Toast.makeText(this, "Search", Toast.LENGTH_SHORT).show(); return true; } else if (id == R.id.action_settings) { startActivity(new Intent(this, SettingsActivity.class)); return true; } else if (id == R.id.action_about) { showAboutDialog(); return true; } return super.onOptionsItemSelected(item); // delegate unhandled items }
Use item.getItemId() rather than a switch statement with resource IDs. Since Android Gradle Plugin 8.x, resource IDs are no longer guaranteed to be compile-time constants, so switch on them produces a compiler warning or error. Use if / else if chains instead.

Contextual Action Mode

When a user long-presses an item in a list you often want a contextual action bar (CAB) — a temporary bar that replaces the toolbar and shows actions relevant to the selection. Implement ActionMode.Callback:

private ActionMode actionMode; private final ActionMode.Callback actionModeCallback = new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { mode.getMenuInflater().inflate(R.menu.menu_context, menu); mode.setTitle("1 selected"); return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; // return true only if the menu was updated } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { if (item.getItemId() == R.id.action_delete) { deleteSelectedItem(); mode.finish(); // dismisses the CAB return true; } return false; } @Override public void onDestroyActionMode(ActionMode mode) { actionMode = null; clearSelection(); } }; // Activate on long press: someView.setOnLongClickListener(v -> { actionMode = startSupportActionMode(actionModeCallback); return true; });

Material Design Fundamentals

Material Design is Google's design system. It gives Android (and web) apps a consistent visual grammar. The key ideas you need as a developer are:

  • Elevation and shadows — every surface lives at a z-level; higher surfaces cast larger shadows. Set it with android:elevation.
  • Color systemcolorPrimary, colorSecondary, colorSurface, colorOnPrimary (text/icons on primary), etc. Define them once in your theme and reference via ?attr/ so every widget uses consistent colour automatically.
  • Typography scale — headline, title, body, caption. Use TextAppearance.MaterialComponents.* styles instead of raw font sizes.
  • Ripple feedbackMaterialButton, MaterialCardView, and other Material widgets include ripple by default; avoid plain Button or View where a Material counterpart exists.

Using MaterialButton and FloatingActionButton

Swap plain Button for MaterialButton to get correct padding, corner radius, ripple, and colour from your theme automatically:

<com.google.android.material.button.MaterialButton android:id="@+id/btnSave" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Save" style="@style/Widget.MaterialComponents.Button" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:layout_margin="16dp" android:src="@drawable/ic_add" android:contentDescription="Add item" />

Wire the FAB in Java:

FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(v -> openCreateScreen());

Snackbar: the Material Way to Show Feedback

Toast works but is not dismissible and cannot carry an action. Snackbar is the Material replacement. It requires a view to anchor to (use the root CoordinatorLayout so it slides up above the FAB automatically):

import com.google.android.material.snackbar.Snackbar; View rootView = findViewById(R.id.coordinator_root); Snackbar.make(rootView, "Item deleted", Snackbar.LENGTH_LONG) .setAction("UNDO", v -> undoDelete()) .show();
Anchor Snackbar to the CoordinatorLayout, not an inner view. When anchored to a CoordinatorLayout, the framework automatically shifts the FAB upward so the Snackbar does not cover it. Anchoring to any other container breaks this automatic choreography and the Snackbar appears over the FAB.

Navigation Drawer with Material Design

For apps with several top-level destinations, a Navigation Drawer is the standard pattern. Wrap your layout in DrawerLayout and use NavigationView from the Material library:

<androidx.drawerlayout.widget.DrawerLayout android:id="@+id/drawerLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- Main content + toolbar here --> <com.google.android.material.navigation.NavigationView android:id="@+id/navView" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" app:menu="@menu/menu_drawer" app:headerLayout="@layout/nav_header" /> </androidx.drawerlayout.widget.DrawerLayout>

In Java, toggle the drawer from the toolbar's hamburger icon and handle navigation selections:

DrawerLayout drawer = findViewById(R.id.drawerLayout); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.nav_open, R.string.nav_close); drawer.addDrawerListener(toggle); toggle.syncState(); // syncs the hamburger/arrow icon with drawer state NavigationView navView = findViewById(R.id.navView); navView.setNavigationItemSelectedListener(item -> { int id = item.getItemId(); if (id == R.id.nav_home) { // load home fragment } else if (id == R.id.nav_profile) { // load profile fragment } drawer.closeDrawer(GravityCompat.START); return true; });

Summary

You have built a complete app bar using Toolbar + setSupportActionBar, declared and handled both toolbar and overflow menu items, implemented a contextual action bar for long-press selections, and applied Material Design through consistent theming, MaterialButton, FloatingActionButton, Snackbar, and a Navigation Drawer. In the final lesson you will bring everything together in a full List-Detail project.