Android menus

In this chapter of the Android development tutorial we will work with menus. Menus group available commands of an application. In traditional desktop applications menus are part of a menubar which is located mostly in the top area of the application. Context or popup menus are special cases of menus.

There are three kinds of menus in Android: options menu, context menu, and popup menu. The options menu is the primary collection of menu items for an activity. In the options menu we should have commands that have global impact on the applications. For example a Settings menu. It is displayed either at the top or at the bottom of the activity. The context menu shows menu items in a specific context. For example for ListView items. It is shown when the user performs a long-click on an element. A popup menu displays a list of items in a vertical list that's anchored to the view that invoked the menu. It appears below the anchor view if there is room, or above the view otherwise. It should relate to regions of content in the activity.

Menus can be created manually by coding or they can be defined in an XML file. If we define our menus in an XML file we use the MenuInflater object to create menus from the XML file.

Options menu

Our options menu will have two menu items. When we select a menu item a Toast window is shown with the name of the selected menu item. The options menu is displayed after we click on the menu button.

The manifest file is not modified in this example.

Menu button
Figure: Menu button

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
  <TextView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:text="@string/message" />
    
</LinearLayout>

In the main.xml layout file, we have one TextView widget. It will display a welcome message.

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">OptionsMenu</string>
    <string name="message">Demonstrating Options Menu</string>
    <string name="om1">Settings</string>
    <string name="om2">Tools</string>
</resources>

This is strings.xml file.

options_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/settings"
    android:title="@string/om1" />
  <item android:id="@+id/tools"
    android:title="@string/om2" />
</menu>

This is options_menu.xml file. It defines two menu items. The file is located in the res/menu/ subdirectory.

MainActivity.java
package com.zetcode.opmenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) 
    {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.options_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) 
    {
        switch (item.getItemId()) 
        {
            case R.id.settings:
                Toast.makeText(MainActivity.this, "Settings menu selected.", 
                    Toast.LENGTH_SHORT).show();
                return true;

            case R.id.tools:
                Toast.makeText(MainActivity.this, "Tools menu selected.", 
                    Toast.LENGTH_SHORT).show();
                return true;

            default:
                return super.onOptionsItemSelected(item);
        }
    }
}

To enable an options menu in an activity, we need to override the onCreateOptionsMenu() and the onOptionsItemSelected() methods.

@Override
public boolean onCreateOptionsMenu(Menu menu) 
{
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.options_menu, menu);
    return true;
}

Inside the onCreateOptionsMenu() method, we build the options menu from the options_menu.xml file. We use the MenuInflater class to do the job.

@Override
public boolean onOptionsItemSelected(MenuItem item) 
{
...
}

The onOptionsItemSelected() method handles the click events on the menu items.

case R.id.settings:
    Toast.makeText(MainActivity.this, "Settings menu selected.", 
        Toast.LENGTH_SHORT).show();
    return true;

In case of the selection of the Settings menu item we show a Toast window with "Settings menu selected" message.

Options menu at the bottom of the activity
Figure: Options menu at the bottom of the activity

Context menu

We have a ListView with the names of our planets. A long-click on an element will show a context menu with tree options: Delete, Uppercase, and Lowercase.

The manifest file is not modified.

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
    <ListView 
      android:id="@+id/lvId"
      android:layout_width="fill_parent"   
      android:layout_height="fill_parent" />  
      
</LinearLayout>

This is main.xml file. It contains a ListView widget.

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">ContextMenu</string>
    <string-array name="planets">
        <item>Mercury</item>
        <item>Venus</item>
        <item>Earth</item>
        <item>Mars</item>
        <item>Jupiter</item>
        <item>Saturn</item>
        <item>Uranus</item>
        <item>Neptune</item>
        <item>Pluto</item>
    </string-array>     
</resources>

This is the strings.xml resource file.

row.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp"
    android:textSize="20sp">
</TextView>

This is the row.xml resource file. Each row of a ListView consists of a single TextView.

MainActivity.java
package com.zetcode.conmenu;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;  
import android.widget.ListView;  
import android.view.View;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView.AdapterContextMenuInfo;

import java.util.Arrays;
import java.util.ArrayList;


public class MainActivity extends Activity
{
    private ListView lv;  
    private ArrayAdapter<String> la; 

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        setupUI();
    }

    public void setupUI()
    { 
        lv = (ListView) findViewById(R.id.lvId);  
        String[] planets = getResources().getStringArray(R.array.planets); 
        ArrayList<String> lst = new ArrayList<String>();
        lst.addAll(Arrays.asList(planets));

        la = new ArrayAdapter<String>(this, R.layout.row, lst);
        lv.setAdapter(la);
        registerForContextMenu(lv);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, 
        ContextMenuInfo menuInfo) 
    {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.context_menu, menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) 
    {
        AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
        
        int pos = info.position;
        String i = la.getItem(pos);

        switch (item.getItemId()) 
        {
            case R.id.delId:
                la.remove(i);
                return true;

            case R.id.upId:               
                String upln = i.toUpperCase();
                la.remove(i);
                la.insert(upln, pos); 
                return true;

            case R.id.loId:
                String lpln = i.toLowerCase();
                la.remove(i);
                la.insert(lpln, pos);          
                return true;

            default:
                return super.onContextItemSelected(item);
        }
    }
}

In order to implement the context menu, we have to override the onCreateContextMenu() and the onContextItemSelected() methods. We also need to call the registerForContextMenu() method for a specific view.

String[] planets = getResources().getStringArray(R.array.planets); 
ArrayList<String> lst = new ArrayList<String>();
lst.addAll(Arrays.asList(planets));

We will be deleting items of the ListView. Therefore, we need to use an ArrayList. Otherwise the list would be read-only.

registerForContextMenu(lv);

The context menu is registered for the ListView widget.

@Override
public void onCreateContextMenu(ContextMenu menu, View v, 
    ContextMenuInfo menuInfo) 
{
    super.onCreateContextMenu(menu, v, menuInfo);
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.context_menu, menu);
}

In the onCreateContextMenu() method we build the context menu from the context_menu.xml file.

@Override
public boolean onContextItemSelected(MenuItem item) 
{
...
}

The onContextItemSelected() reacts to list item selection events.

AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();

int pos = info.position;
String i = la.getItem(pos);

To find out more about the selected item, we use the AdapterContextMenuInfo class. We get the position and the text of the selected item.

case R.id.delId:
    la.remove(i);
    return true;

If we select the Delete context menu option, we remove the item from the ArrayAdapter.

case R.id.upId:               
    String upln = i.toUpperCase();
    la.remove(i);
    la.insert(upln, pos); 
    return true;

For the Uppercase option, we modify the string, remove the original one and insert a new one.

Context menu with three options
Figure: Context menu with three options

Popup menu

The example shows a PopupMenu after clicking on a button.

The manifest file is not modified.

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    
  <Button
      android:id="@+id/btnId"
      android:layout_height="wrap_content"
      android:layout_width="wrap_content"
      android:layout_marginTop="10dip"
      android:text="@string/btn_label"
      android:onClick="onClick" />
      
  <TextView
      android:id="@+id/tvId"
      android:layout_width="fill_parent"
      android:layout_height="wrap_content" 
      android:layout_marginTop="10dip" />
      
</LinearLayout>

This is main.xml file. We have a Button widget and a TextView widget. The button will show a PopupMenu.

strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">PopupMenu</string>
    <string name="btn_label">Show menu</string>
    <string name="pm1">Item 1</string>
    <string name="pm2">Item 2</string>
</resources>

This is the strings.xml resource file.

popup_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
  <item android:id="@+id/settings"
    android:title="@string/pm1" />
  <item android:id="@+id/tools"
    android:title="@string/pm2" />
</menu>

This is popup_menu.xml file. It defines two menu items. The file is located in the res/menu/ subdirectory.

MainActivity.java
package com.zetcode.popmenu;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;

public class MainActivity extends Activity
    implements OnMenuItemClickListener
{   
    private TextView tv;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        tv = (TextView) findViewById(R.id.tvId);        
    }

   public void onClick(View v) 
   {   
       showPopupMenu(v);
   }

   public void showPopupMenu(View v)
   {
       PopupMenu pm = new PopupMenu(MainActivity.this, v);
       pm.getMenuInflater().inflate(R.menu.popup_menu, pm.getMenu()); 
       pm.setOnMenuItemClickListener(this);          
       pm.show();
   }

   @Override
   public boolean onMenuItemClick(MenuItem item) 
   {           
      tv.setText(item.toString() + " selected");
      return true;  
   }
}

A PopupMenu is displayed after clicking on the button widget.

public void onClick(View v) 
{   
    showPopupMenu(v);
}

This method is a callback to the button click. The relation is set in the main.xml file via an attribute. The method calls the showPopupMenu() method.

public void showPopupMenu(View v)
{
    PopupMenu pm = new PopupMenu(MainActivity.this, v);
    pm.getMenuInflater().inflate(R.menu.popup_menu, pm.getMenu()); 
    pm.setOnMenuItemClickListener(this);          
    pm.show();
}

We create an instance of the PopupMenu class. It builds the menu, sets the OnMenuItemClickListener and shows the PopupMenu.

@Override
public boolean onMenuItemClick(MenuItem item) 
{           
    tv.setText(item.toString() + " selected");
    return true;  
}

After selecting a menu item the onMenuItemClick() method is called. It sets the item's title to the TextView widget.

In this chapter of the Android development tutorial, we have worked with menus.