2012年10月11日木曜日

Show a context menu for long-clicks in an Android ListView

Source code for this blog post is available as a complete Eclipse project at
http://github.com/mikeplate/ListViewDemo (zip download link in upper right
corner).
An Activity with an expanding ListView and a docked TextView

If you have an activity that will only contain a single ListView control,
you can derive your activity from the ListActivity instead of Activity.
However, I think I might like to show some extra info below my ListView so I
chose to have a separate ListView object. My activity layout looks like
this:
<?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"
>
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="0px"
android:layout_weight="1"
/>
<TextView
android:id="@+id/footer"
android:layout_width="fill_parent"
android:layout_height="60dip"
android:text="@string/footer"
android:padding="4dip"
android:background="#FF666666"
/>
</LinearLayout>
And I need the layout for items in the ListView (listitem.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="wrap_content"
android:textSize="24dip"
android:padding="8dip"
/>
Note a nice trick that I've used to get the TextView to "dock" at the
bottom with a definied height, and have the ListView automatically fill out
the rest of the height. This kind of thinking is important since Android
devices can have different resolutions. The trick is to set the
layout_height to zero pixels and the layout_weight to one (default is zero).
Not sure about the logic behind that, but it works!

In order to have something to put into my ListView, I created a few country
names in a string array as a resource and I sort that array before adding it
to the ListView with the ArrayAdapter object. (Check out source code link
above for this content.)
public class ListViewDemoActivity extends Activity {
private String[] Countries;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Countries = getResources().getStringArray(R.array.countries);
Arrays.sort(Countries);

ListView list = (ListView)findViewById(R.id.list);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
R.layout.listitem, Countries);
list.setAdapter(adapter);
registerForContextMenu(list);
}
}
Creating a ContextMenu in Android

When the user long-clicks, the event onCreateContextMenu is fired for the
control that the user is clicking. For me, that is the ListView control. But
since I don't want to write a custom ListView-derived class, I want to
catch that event in my activity. There does not seem to be any bubbling
going on. Events fired in a child control does not bubble up to the parent
if they are unhandled.
But obviously, the api designers have thought of this since there is a
special method for this situation. Call the registerForContextMenu in your
activity for this! This will actually make sure your overridden methods for
both onCreateContextMenu and onContextItemSelected is called for the
ListView-events as we'll see soon.
Next, we'll provide the implementation of onCreateContextMenu. Here I want
to ensure that the event comes from the ListView and if so, I want to
determine on which item in the ListView the user long-clicked.
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
if (v.getId()==R.id.list) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo)menuInfo;
menu.setHeaderTitle(Countries[info.position]);
String[] menuItems = getResources().getStringArray(R.array.menu);
for (int i = 0; i<menuItems.length; i++) {
menu.add(Menu.NONE, i, i, menuItems[i]);
}
}
}
As you can see, the argument of type ContextMenuInfo can actually change
depending on what type of control is sending the event. For ListViews, the
class you need to type cast into is AdapterView.AdapterContextMenuInfo. From
there I used the position, which in my case corresponds to the index into
the string-array. From the array I retrieve the string for that particular
item and use as title for the menu. Then you can of course add all the menu
commands you like. For the demo, I defined another string array as a
resource with the commands I want to add.
When creating the menu items with the add-call, I specify that I don't want
any grouping of the items (Menu.NONE) and that the order and id of the item
is the same (i). The last argument to add is the text to display for the
item.

Responding to selected MenuItem

If the user dismisses the context menu (for instance, by back button) you
don't need to do anything. But for catching the actual selection of one of
the items, you need to override onContextItemSelected.
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info =
(AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
int menuItemIndex = item.getItemId();
String[] menuItems = getResources().getStringArray(R.array.menu);
String menuItemName = menuItems[menuItemIndex];
String listItemName = Countries[info.position];

TextView text = (TextView)findViewById(R.id.footer);
text.setText(String.format("Selected %s for item %s", menuItemName,
listItemName));
return true;
}
The MenuItem argument holds all information that you need. The
ContextMenuInfo object that got sent to onCreateContextMenu is still there
and still needs type casting. Or I guess you could have saved that info in
the activity between the calls, but I didn't.
The id of the menu item selected is the same as the index into the string
array of menu item texts for me. Instead of just outputting the menu command
name and the list item text in a TextView, you would most likely have a big
switch statement on menuItemIndex.
This was my first blog post and code demo for the Android platform. I hope
it won't be the last! The goal is to build upon this demo and/or other
demos in my investigations of the Android platform. Please let me know in
the comments if you have even better methods or code patterns that solves
problems like this.

0 件のコメント:

コメントを投稿