Smartphones

A context enabled grid view layout for Android

Android developer William Francis presents a step-by-step tutorial on his context aware grid view layout.

One of the things I most appreciate about working on an open source platform like Android is all the community support available for developers. Google has its official Android documentation, but if that resource falls short of expectations I can usually type whatever the issue is into my favorite search engine and voila, I've got more answers than I can shake a stick at. Of course there are exceptions.

Last week I ran into one of those exceptions while working on a project that required me to make use of a context menu from within a grid view. (The context menu is the menu Android occasionally pops up when you press and hold your finger on an item.) There were lots of examples of grid views and plenty of samples of the context menu, yet marrying the two had some unique aspects, and I failed to find a reliable tutorial demonstrating the powerful combination.

Like a dog with a bone once I get my teeth into something, I'm not likely to let it go. And I wouldn't be much of an engineer if I didn't eventually manage to get my grid view and context menu to play nice. Now in the spirit of sharing that's so prevalent among Android developers I present to you my context aware grid view layout. Feel free to follow along with the step-by-step tutorial, or download the entire Eclipse project here.

1. Start a new project in Eclipse targeting SDK 1.6 or higher.

2. In the /res/layout folder create a simple layout consisting of one text and one grid view.

main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Grid Demo" />
<GridView
android:id="@+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp"
android:verticalSpacing="5dp"
android:horizontalSpacing="10dp"
android:numColumns="auto_fit"
android:columnWidth="60dp"
android:stretchMode="columnWidth"
android:gravity="center_horizontal"/>
</LinearLayout>

3. In the /res/layout folder, we will also need to create the layout for our grid view items -- this time a text view and an image view.

grid_item.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/GridItem"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView android:id="@+id/grid_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"/>
<ImageView android:id="@+id/grid_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>

4. In the /res/drawable folder I've included a series of images. The images are simple PNGs that represent the numbers 0 through 9, named cleverly zero.png, one.png, etc.

5. In our /src folder we will want to start by defining a custom adapter for the grid view. With the exception of the case statement responsible for selecting which graphic to display, I think you will find I am just implementing default versions of the required methods one must override to derive from the base adapter class.

CustomAdapter.java
 
package com.authorwjf;
import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class CustomAdapter extends BaseAdapter {
private Context mContext;
private ArrayList<String> mItems;
public CustomAdapter(Context c, ArrayList<String> items) {
mContext = c;
mItems = items;
}
@Override
public int getCount() {
return mItems.size();
}
@Override
public Object getItem(int position) {
return mItems.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater li = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = li.inflate(R.layout.grid_item, null);
TextView tv = (TextView)v.findViewById(R.id.grid_item_text);
tv.setText("Item #"+mItems.get(position));
ImageView iv = (ImageView)v.findViewById(R.id.grid_item_image);
switch (position) {
case 0:
iv.setImageResource(R.drawable.zero);
break;
case 1:
iv.setImageResource(R.drawable.one);
break;
case 2:
iv.setImageResource(R.drawable.two);
break;
case 3:
iv.setImageResource(R.drawable.three);
break;
case 4:
iv.setImageResource(R.drawable.four);
break;
case 5:
iv.setImageResource(R.drawable.five);
break;
case 6:
iv.setImageResource(R.drawable.six);
break;
case 7:
iv.setImageResource(R.drawable.seven);
break;
case 8:
iv.setImageResource(R.drawable.eight);
break;
case 9:
iv.setImageResource(R.drawable.nine);
break;
}
}
return v;
}
}

6. The Main.java file is where we will register for and respond to context menu related events. Let's start by setting up a few class level variables, and overriding the onCreate() method of the activity. Pay close attention to the registerForContextMenu() call; this call inherently registers each view contained in the grid view for context menu detection. In other words, you don't have to register the children individually when you populate the grid. It's one of the items that was not immediately clear to me when I read the documentation for the grid view class.

Main.java
 
package com.authorwjf;
import android.app.Activity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.GridView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import java.util.ArrayList;
public class Main extends Activity {
private CustomAdapter mAdapter;
private ArrayList<String> mItems = new ArrayList<String>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
for (int i = 0; i<10; i++) {
mItems.add(Integer.toString(i));
}
mAdapter = new CustomAdapter(this, mItems);
GridView g = (GridView) findViewById(R.id.gridview);
g.setAdapter(mAdapter);
registerForContextMenu(g);
}
}

7. Next we will add the function that populates the context menu. Note the first parameter of the .add method --this is the group identifier, and we will use this later to decide which action the user has requested (i.e., action one or action two).

Main.java
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.setHeaderTitle("Context Menu");
AdapterContextMenuInfo cmi = (AdapterContextMenuInfo) menuInfo;
menu.add(1, cmi.position, 0, "Action 1");
menu.add(2, cmi.position, 0, "Action 2");
}

8. The final step is to write the menu handler. Using the menu item passed into the method by the framework, we can get at the ID of the selected item in the grid view adapter. In our case, the id and position are synonymous, so it's a no brainer to get at the item the user selected. If your ids and position are not the same, you may have to loop through the adapter with the id looking for the position via the getItemIdAtPosition method.

Main.java
@Override
public boolean onContextItemSelected(MenuItem item) {
GridView g = (GridView) findViewById(R.id.gridview);
String s = (String) g.getItemAtPosition(item.getItemId());
switch (item.getGroupId()) {
case 1:
Toast.makeText(this, "Action 1, Item "+s, Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(this, "Action 2, Item "+s, Toast.LENGTH_SHORT).show();
break;
}
return true;
}

That completes the tutorial. Running it will give you a grid populated with the digits 0 - 9, and selecting one will simply present a toast message informing you which action needs to be performed on which item.

As you can see, once it's laid out, combining a grid view with a context menu isn't really all that complex - it's just one of those coding situations where getting started can be intimidating. Many times I've incorporated the tutorials and experiences of other developers into my Android projects. It's nice to have the opportunity to give back now and then.

Figure A

Figure B

Figure C

About

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

0 comments

Editor's Picks