Click here to Skip to main content
15,867,967 members
Articles / Mobile Apps / Android
Article

Android Menus My Way

Rate me:
Please Sign up or sign in to vote.
4.87/5 (39 votes)
25 Mar 2011CPOL7 min read 216.6K   10.6K   69   44
Explore a set of helper classes for customizing the default

Introduction

For the last year now, I have found myself concentrating on primarily Android development. Like any platform, Android has its plusses and minuses. In my opinion, falling into that latter category is the system generated menu bar. The Android SDK gives an application developer very little room to customize this widget. While I appreciate the idea of UI consistency throughout the platform, the white menus with orange highlights Google chose for this widget are likely to clash with just about any color scheme an app developer might come up with. Besides being tough on the eyes, I take some other issues with the built-in menu widget. First, I don’t get to explicitly control how many menu items are displayed on a single line. The control limits me to two total lines of menu options, after which I have to switch to the dreaded “more” menu. And finally, I’m not a big fan of how menu items align themselves with one another when the second row on the menu has fewer options than the first. The image below shows my app running on Android 1.6 using the default menu implementation. As you can see, it’s not very pretty.

I suspected being an open platform, it wouldn’t take me long to implement my own menu system that overrides the default Google provided one. I was wrong. I saw plenty of people asking the same questions I had in numerous forums throughout the internet, but there were very few if any responses besides: “learn to love orange”. Eventually, I did manage to cobble something together using the popup window class. Thanks to CodeProject, I am sharing that implementation with you. I realize there is plenty of room for improvement for my menu helper classes, but I believe the code is at least a good starting point, and will save a resourceful developer a good deal of time. The image below shows my menu against the same application window. While you may not care for the color scheme in general, at least there is a unified feel. More importantly, using my helper classes, you can tweak the menu look until your heart is content.

Using the Code

The key to creating my menu class turned out to lay largely in me stumbling across the PopupWindow class in the SDK, then subsequently wading through the sometimes confusing documentation and examples. All of the code handling the PopupWindow is found in my CustomMenu class. If you choose to layout your menu using a table, as I have done, you shouldn’t really need to modify this class. What you will need to do is, tweak a couple resource files unless you just happen to be looking for a grey menu bar. Let’s first look closer at the layouts.

custom_menu.xml

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"
   	android:background="#000000"
   	android:padding="0dip"
   	android:focusable="true"
    android:focusableInTouchMode="true"
    android:clickable="true">
    <ImageView
       	android:layout_width="fill_parent"
       	android:layout_height="wrap_content"
       	android:scaleType="fitXY"
       	android:gravity="center"
       	android:src="@drawable/custom_menu_header"/>
   	<TableLayout
        android:layout_width="fill_parent"
        android:id="@+id/custom_menu_table"
        android:layout_height="wrap_content"
        android:stretchColumns="*"
        android:focusable="true"
        android:focusableInTouchMode="true">
    </TableLayout>
</LinearLayout>

This file is the linear layout responsible for placement of the gray bar that separates the menu from the rest of your app, the menu background color (or image if you like), and finally the table which will hold the view for individual menu items.

custom_menu_item.xml

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="wrap_content"
    android:gravity="center"
    android:layout_gravity="center"
    android:padding="4dip"
    android:clickable="true"
    android:background="@drawable/custom_menu_selector">
    <ImageView
    	android:id="@+id/custom_menu_item_icon"
       	android:layout_width="fill_parent"
       	android:layout_height="wrap_content"
       	android:gravity="center"
       	android:paddingBottom="2dip"
       	android:paddingTop="2dip"/>
	<TextView
	   	android:id="@+id/custom_menu_item_caption"
	   	android:layout_width="wrap_content"
	   	android:layout_height="wrap_content"
	   	android:textColor="#ffffff"
	   	android:textSize="12sp"
	   	android:gravity="center"/>
</LinearLayout>

The custom menu item layout is responsible for defining the view that will get dropped inside of each cell in the table that contains the menu. Currently, I am displaying an image centered over a line of text. One thing to note here is the background attribute. This I am assigning to a custom selector so that I can define how the item looks when the user taps it. The other layout in the layout folder is the main.xml file. This has little to do with the actual custom menu classes but rather is the layout for the demo itself. The remainder of the interesting resources for customizing the menus can be found in the drawable folder.

custom_menu_selector.xml

XML
<selector xmlns:android="http://schemas.android.com/apk/res/android">
	<item	android:state_pressed="true"
			android:drawable="@drawable/menu_item_pressed" />
	<item 	android:drawable="@drawable/menu_item_bg" />
</selector>

Here is where I am defining the behavior for the background of individual menu items. At rest, I simply use an image with a black back drop. However, when the item is tapped, I replace the image with a nice gray gradient.

Now that we have our resources in place, let’s take a look at how to actually use the helper classes in your application.

The Demo.java file serves as an example on how to use the helper classes in your own code. The steps are pretty straight forward. First, you need to create a collection of individual menu items. The process as it exists now is a bit tedious but did the trick.

Java
ArrayList<CustomMenuItem> menuItems = new ArrayList<CustomMenuItem>();
CustomMenuItem cmi = new CustomMenuItem();
cmi.setCaption("First");
cmi.setImageResourceId(R.drawable.icon1);
cmi.setId(1);
menuItems.add(cmi);
cmi = new CustomMenuItem();
cmi.setCaption("Second");
cmi.setImageResourceId(R.drawable.icon2);
cmi.setId(2);
menuItems.add(cmi);

etc…

Once you have an array list of your individual menu items, you need to instantiate a new custom menu. The constructor requires a context, a listener, and a layout inflater. If you’ve done much Android development, you know that generally you can pass context simply by sending the keyword “this” as a parameter. The listener is a standard part of Java, and in this case is defined as an interface for the CustomMenu class. So you need to add an “implements” clause to your activity’s constructor:

Java
public class Demo extends Activity implements OnMenuItemSelectedListener {

And you will also have to add a callback for the listener:

Java
@Override
public void MenuItemSelectedEvent(CustomMenuItem selection) {
}

We can come back later and fill in the body for the message handler. Once our activity implements the OnMenuItemSelectedEvent, we are now missing just one piece before we can instantiate our CustomMenu class. This is a layout inflater.

Requiring the CustomMenu class to receive a layout inflater from the parent is a bit odd, I confess. The problem was I couldn’t figure out how to get a layout inflater from within the context of the CustomMenu class and PopupWindow. Luckily, getting the layout inflater from inside the main activity takes zero effort. A call to getLayoutInflater() and we are ready to begin.

Java
CustomMenu mMenu = new CustomMenu(this, this, getLayoutInflater()); 

Once the menu has been created, we have to assign our menu items to it. If you aren’t sure, check the isShowing property prior to assigning or reassigning the menu item list as it will throw an exception if you try to change the content of a visible menu.

Java
if (!mMenu.isShowing()){
       try {
       mMenu.setMenuItems(menuItems);
} catch (Exception e) {
       AlertDialog.Builder alert = new AlertDialog.Builder(this);
	alert.setTitle("Egads!");
	alert.setMessage(e.getMessage());
	alert.show();
}
}

At this point, we could display our menu by calling the show method, but there are a couple other items we can use in the helper class to further customize the menu.

Java
mMenu.setHideOnSelect(true);

This method controls whether or not the menu is automatically closed when the user makes a selection.

Java
mMenu.setItemsPerLineInPortraitOrientation(4);

This is the preferred number of menu items to display on a single line when the phone is in portrait orientation.

Java
mMenu.setItemsPerLineInLandscapeOrientation(8);

Finally, here is the preferred number of menu items to display on a single line with the phone is in landscape orientation.

To invoke our custom menu when someone hits the menu key, we just override the onKeyDown handler in our main activity.

Java
public boolean onKeyDown(int keyCode, KeyEvent event) {
       if (keyCode == KeyEvent.KEYCODE_MENU) {
	    	if (mMenu.isShowing()) {
			mMenu.hide();
		} else {
			mMenu.show(findViewById(R.id.any_old_widget));
		}
	    	return true; //always eat it!
	}
	return super.onKeyDown(keyCode, event);
}

The override is pretty straight forward. We use the menu key as a toggle. So if you press the key and the menu is currently showing, it will be hidden, and vice-versa. Currently the menu does not go away when pressing the back key. This can be handled though simply as another override. A little more trick would be to hide the menu when the user touches the screen outside the menu itself. This is accomplishable through the PopupWindow class onTouchInterceptor and the setOutSideTouchable property.

Notice that when calling the show function of the menu, we must send it a view. This is so the popup window can borrow a window token from the parent. It really doesn’t matter what view on the parent you pass in as long as you give it one.

Once the menu displays, the only thing that remains is to handle the menu item selection events in our main activity. For the demo, it is sufficient to just toast the user selection to the display.

Java
@Override
public void MenuItemSelectedEvent(CustomMenuItem selection) {
	Toast t = Toast.makeText(this,
"You selected item #"+Integer.toString(selection.getId()), Toast.LENGTH_SHORT);
	t.setGravity(Gravity.CENTER, 0, 0);
	t.show();
}

And there you have it, a replacement for those standard Android menus.

Points of Interest

The helper classes provided here are more than usable. However, they are not as simple as a drop in replacement for the built-in menu widgets. I suspect they could be, given time and refinement, but for now they are meeting my needs. One thing experienced Android users might notice these custom menus lack is the ability to give focus to, and thus select or highlight a menu item without actually clicking the item and generating the select event. I started to implement this and found it was tricky because of how focus is handled and subsequent key presses once focus leaves the main activity. It’s not really critical to my application, but if anyone reading this post comes up with a solution and doesn’t mind sharing, I’d be happy to update my class and repost.

History

  • (v)01.00 Initial release 03/24/2011

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I began coding at the ripe old age of eleven (yep, I've always been a nerd). Back then every 8-bit computer on the market had its own flavor of BASIC burned into the EPROM and I was adept at most of them. Somewhere in my bedroom there was an actual bed, but you'd be hard pressed to find it surrounded as it was. My collection included a C64, VIC20, TRS80, APPLE II+, TI-99/4A, and even one of those silver Timex Sinclair "computers" with the chicklet keys.

Eventually I taught myself 6502 assembler, and later Pascal and C. While I spent the majority of my professional career doing a mixture of C++, C#, and dabbling in ARM Assembler, for the last year I've been focusing on JAVA and the Android Platform. While I am a Windows guy at heart lately I'm finding some love for UBUNTU as well.

When I am not at the computer I am hanging out with my 12 year old son. He just finished coding a javascript implementation of Conway's Game of Life. Oh yeah, I guess that means we were in front of the computer. Go figure!

Comments and Discussions

 
QuestionYou made my day! Pin
Alexander Knopov30-Aug-14 16:02
Alexander Knopov30-Aug-14 16:02 
QuestionGreat article Pin
Mike Hankey24-Mar-14 14:28
mveMike Hankey24-Mar-14 14:28 
QuestionHow to make two columns? Pin
furkanDamar8-Nov-13 3:24
furkanDamar8-Nov-13 3:24 
QuestionThanks Pin
Member 1020892222-Oct-13 3:36
Member 1020892222-Oct-13 3:36 
QuestionNeed help urgently! Pin
Member 985156920-Feb-13 16:10
Member 985156920-Feb-13 16:10 
Hi! I want to use the CustomMenu in my TabActivity. There's nothing showing when I press the Menu button. Here's my code:

package org.kordit.openemis;

import java.util.ArrayList;

import org.kordit.commons.customoptionsmenu.CustomMenu;
import org.kordit.commons.customoptionsmenu.CustomMenuItem;
import org.kordit.commons.customoptionsmenu.CustomMenu.OnMenuItemSelectedListener;
import org.kordit.openemis.about.AboutTabGroupActivity;

import android.os.Bundle;
import android.app.AlertDialog;
import android.app.TabActivity;
import android.content.Context;
import android.content.Intent;
import android.view.Gravity;
import android.view.KeyEvent;
import android.widget.TabHost;
import android.widget.Toast;
import android.widget.TabHost.TabSpec;

@SuppressWarnings("deprecation")
public class HomeParentTabActivity extends TabActivity implements OnMenuItemSelectedListener {
/**
* Some global variables.
*/
private CustomMenu mMenu;
public static final int MENU_ITEM_1 = 1;
public static final int MENU_ITEM_2 = 2;
public static final int MENU_ITEM_3 = 3;
public static final int MENU_ITEM_4 = 4;

private Context context;
private TabHost tabHost;
private TabSpec homeSpec, settingsSpec, aboutSpec;

private Intent homeIntent, settingsIntent, aboutIntent;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.openemis_parenttab);

//initialize the menu
mMenu = new CustomMenu(this, this, getLayoutInflater());
mMenu.setHideOnSelect(true);
mMenu.setItemsPerLineInPortraitOrientation(4);
mMenu.setItemsPerLineInLandscapeOrientation(8);

//load the menu items
loadMenuItems();

context = this;
init();
}

private void init() {
tabHost = getTabHost();

homeIntent = new Intent(context, HomeTabGroupActivity.class);
settingsIntent = new Intent(context, SettingsActivity.class);
aboutIntent = new Intent(context, AboutTabGroupActivity.class);

homeSpec = tabHost.newTabSpec("Home");
homeSpec.setIndicator("Home",
getResources().getDrawable(R.drawable.icon_home_tab));
homeSpec.setContent(homeIntent);

settingsSpec = tabHost.newTabSpec("Settings");
settingsSpec.setIndicator("Settings",
getResources().getDrawable(R.drawable.icon_settings_tab));
settingsSpec.setContent(settingsIntent);

aboutSpec = tabHost.newTabSpec("About");
aboutSpec.setIndicator("About",
getResources().getDrawable(R.drawable.icon_about_tab));
aboutSpec.setContent(aboutIntent);

tabHost.addTab(homeSpec);
tabHost.addTab(settingsSpec);
tabHost.addTab(aboutSpec);

tabHost.setCurrentTab(0);
}

/**
* Snarf the menu key.
*/
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
doMenu();
return true; //always eat it!
}
return super.onKeyDown(keyCode, event);
}

/**
* Load up our menu.
*/
private void loadMenuItems() {
//This is kind of a tedious way to load up the menu items.
//Am sure there is room for improvement.
java.util.ArrayList<custommenuitem> menuItems = new ArrayList<custommenuitem>();
CustomMenuItem cmi = new CustomMenuItem();
cmi.setCaption("First");
cmi.setImageResourceId(R.drawable.icon1);
cmi.setId(MENU_ITEM_1);
menuItems.add(cmi);
cmi = new CustomMenuItem();
cmi.setCaption("Second");
cmi.setImageResourceId(R.drawable.icon2);
cmi.setId(MENU_ITEM_2);
menuItems.add(cmi);
cmi = new CustomMenuItem();
cmi.setCaption("Third");
cmi.setImageResourceId(R.drawable.icon3);
cmi.setId(MENU_ITEM_3);
menuItems.add(cmi);
cmi = new CustomMenuItem();
cmi.setCaption("Fourth");
cmi.setImageResourceId(R.drawable.icon4);
cmi.setId(MENU_ITEM_4);
menuItems.add(cmi);
if (!mMenu.isShowing())
try {
mMenu.setMenuItems(menuItems);
} catch (Exception e) {
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Egads!");
alert.setMessage(e.getMessage());
alert.show();
}
}

/**
* Toggle our menu on user pressing the menu key.
*/
private void doMenu() {
if (mMenu.isShowing()) {
mMenu.hide();
} else {
//Note it doesn't matter what widget you send the menu as long as it gets view.
mMenu.show(findViewById(R.id.tv_header));
}
}

/**
* For the demo just toast the item selected.
*/
@Override
public void MenuItemSelectedEvent(CustomMenuItem selection) {
Toast t = Toast.makeText(this, "You selected item #"+Integer.toString(selection.getId()), Toast.LENGTH_SHORT);
t.setGravity(Gravity.CENTER, 0, 0);
t.show();
}
}
QuestionAdding 5 items Pin
FroggerDee12-Dec-12 5:33
FroggerDee12-Dec-12 5:33 
GeneralMy vote of 5 Pin
opiatefuchs16-Nov-12 21:06
opiatefuchs16-Nov-12 21:06 
QuestionMenu hides softkeyboard view Pin
basavarajbj17-Sep-12 21:47
basavarajbj17-Sep-12 21:47 
Question5* the only way to solve this problem Pin
megatokio3-Sep-12 22:50
megatokio3-Sep-12 22:50 
QuestionPut the code as a project Pin
kasympo27-Aug-12 0:40
kasympo27-Aug-12 0:40 
AnswerRe: Put the code as a project Pin
kasympo27-Aug-12 1:20
kasympo27-Aug-12 1:20 
Question+5 Pin
Mast Avalons25-Jul-12 4:06
Mast Avalons25-Jul-12 4:06 
GeneralThanks Pin
hasanghaforian2-Jul-12 1:24
hasanghaforian2-Jul-12 1:24 
QuestionPlease help: error call Show() on start; Pin
sasae2-Jun-12 22:06
sasae2-Jun-12 22:06 
QuestionMy vote is 5 Pin
mohamad yousef10-May-12 0:35
mohamad yousef10-May-12 0:35 
Questionnew functionality Pin
harycoding2-May-12 0:18
harycoding2-May-12 0:18 
GeneralMy vote of 5 Pin
PrateekshaPH12-Apr-12 23:49
PrateekshaPH12-Apr-12 23:49 
QuestionNeed Options menu transition from left to right Pin
Murali511514-Feb-12 0:06
Murali511514-Feb-12 0:06 
GeneralMy vote of 5 Pin
Member 42993969-Feb-12 4:52
Member 42993969-Feb-12 4:52 
Questionnative look & feel Pin
julialecat2-Jan-12 0:24
julialecat2-Jan-12 0:24 
GeneralMy vote of 5 Pin
lgw1508-Nov-11 1:39
lgw1508-Nov-11 1:39 
GeneralMy vote of 5 Pin
Shailendra Singh Rajput7-Nov-11 1:40
Shailendra Singh Rajput7-Nov-11 1:40 
GeneralNeed a sugestion asap Pin
Trung.nguyennam2-Nov-11 4:20
Trung.nguyennam2-Nov-11 4:20 
GeneralMy vote of 5 Pin
nmbappy00416-Oct-11 20:44
nmbappy00416-Oct-11 20:44 
QuestionSubMenu Pin
Buhlo10-Oct-11 5:24
Buhlo10-Oct-11 5:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.