In the last chapter, you learned about MIDP's simpler screen classes. Now we're getting into deeper waters, with screens that show lists and screens with mixed types of controls.
After TextBox and Alert, the next simplest Screen is List, which allows the user to select items (called elements) from a list of choices. A text string or an image is used to represent each element in the list. List supports the selection of a single element or of multiple elements.
There are two main types of List, denoted by constants in the Choice interface:
MULTIPLE designates a list where multiple elements may be selected simultaneously.
EXCLUSIVE specifies a list where only one element may be selected. It is akin to a group of radio buttons.
For both MULTIPLE and EXCLUSIVE lists, selection and confirmation are separate steps. In fact, List does not handle confirmation for these types of lists—your MIDlet will need to provide some other mechanism (probably a Command) that allows users to confirm their choices. MULTIPLE lists allow users to select and deselect various elements before confirming the selection. EXCLUSIVE lists permit users to change their minds several times before confirming the selection.
Figure 6-1a shows an EXCLUSIVE list. The user navigates through the list using the arrow up and down keys. An element is selected by pressing the select button on the device. Figure 6-1b shows a MULTIPLE list. It works basically the same way as an EXCLUSIVE list, but multiple elements can be selected simultaneously. As before, the user moves through the list with the up and down arrow keys. The select key toggles the selection of a particular element.
A further refinement of EXCLUSIVE also exists: IMPLICIT lists combine the steps of selection and confirmation. The IMPLICIT list acts just like a menu. Figure 6-2 shows an IMPLICIT list with images and text for each element. When the user hits the select key, the list immediately fires off an event, just like a Command. An IMPLICIT list is just like an EXCLUSIVE list in that the user can only select one of the list elements. But with IMPLICIT lists, there's no opportunity for the user to change his or her mind before confirming the selection.
When the user makes a selection in an IMPLICIT List, the commandAction() method of the List's CommandListener is invoked. A special value is passed to commandAction() as the Command parameter:
public static final Command SELECT_COMMAND
For example, you can test the source of command events like this:
public void commandAction(Command c, Displayable s) {
if (c == nextCommand)
// ...
else if (c == List.SELECT_COMMAND)
// ...
}
There's an example at the end of this chapter that demonstrates an IMPLICIT List.
In MIDP 2.0, the setSelectCommand() offers you the opportunity to specify your own Command to be used for selections instead of having to use the SELECT_COMMAND.
To create a List, specify a title and a list type. If you have the element names and images available ahead of time, you can pass them in the constructor:
public List(String title, int type)
public List(String title, int type,
String[] stringElements, Image[] imageElements)
The stringElements parameter cannot be null; however, stringElements or imageElements may contain null array elements. If both the string and image for a given list element are null, the element is displayed blank. If both the string and the image are defined, the element will display using the image and the string.
Some Lists will have more elements than can be displayed on the screen. Indeed, the actual number of elements that will fit varies from device to device. But don't worry: List implementations automatically handle scrolling up and down to show the full contents of the List.
Our romp through the List class yields a first look at images. Instances of the javax.microedition.lcdui.Image class represent images in MIDP. The specification dictates that implementations be able to load images files in PNG format.[1] This format supports both a transparent color and lossless compression.
Image has no constructors, but the Image class offers a handful of createImage() factory methods for obtaining Image instances. The first are for loading images from PNG data.
public static Image createImage(String name)
public static Image createImage(byte[] imagedata, int imageoffset,
int imagelength)
The first method attempts to create an Image from the named file, which should be packaged inside the JAR that contains your MIDlet. You should use an absolute pathname or the image file may not be found. The second method creates an Image using data in the supplied array. The data starts at the given array offset, imageoffset, and is imagelength bytes long. In MIDP 2.0, you can also create an Image from an InputStream:
public static Image createImage(InputStream stream)
Images may be mutable or immutable. Mutable Images can be modified by calling getGraphics() and using the returned Graphics object to draw on the image. (For full details on Graphics, see Chapter 10.) If you try to call getGraphics() on an immutable Image, an IllegalStateException will be thrown.
The createImage() methods described above return immutable Images. To create a mutable Image, use the following method:
public static Image createImage(int width, int height)
Typically you would create a mutable Image for off-screen drawing, perhaps for an animation or to reduce flicker if the device's display is not double buffered.
Any Image you pass to Alert, ChoiceGroup, ImageItem, or List should be immutable. To create an immutable Image from a mutable one, use the following method:
public static Image createImage(Image image)
In MIDP 2.0, you can also create an Image from a portion of another Image using the following method:
public static Image createImage(Image image,
int x, int y, int width, int height, int transform)
This method takes the part of the original image described by x, y, width, and height, applies the specified transformation, and returns the result as an immutable Image. The possible transformations are described by constants in the javax.microedition.lcdui.game.Sprite class and include things like mirroring and 90-degree rotation.
Image also includes methods that handle image data as an int array. I'll talk about these methods later in Chapter 10.
How do you figure out what size Images you need? In MIDP 2.0, Display provides methods that return information about the optimal width and height for various types of images:
public int getBestImageHeight(int imageType); public int getBestImageWidth(int imageType);
The imageType parameter should be one of Display's constants LIST_ELEMENT, ALERT, or CHOICE_GROUP_ELEMENT. (You'll learn all about ChoiceGroup later in this chapter.) If you were building a List, you could query Display to find the best size for element images. Assuming you had packaged icons of various sizes in your application, you could select the best-sized images at runtime.
List provides methods for adding items, removing elements, and examining elements. Each element in the List has an index. The first element is at index 0, the next at index 1, and so forth. You can replace an element with set() or add an element to the end of the list with append(). The insert() method adds a new element to the list at the given index; this bumps all elements at that position and higher up by one.
public void set(int elementNum, String stringPart, Image imagePart) public void insert(int elementNum, String stringPart, Image imagePart) public int append(String stringPart, Image imagePart)
You can examine the string or image for a given element by supplying its index. Similarly, you can use delete() to remove an element from the List.
public String getString(int elementNum) public Image getImage(int elementNum) public void delete(int elementNum)
MIDP 2.0 also features a deleteAll() method that removes every element from the List.
Finally, the size() method returns the number of elements in the List.
Although you usually give the MIDP implementation the responsibility of displaying your List, new methods in MIDP 2.0 give you some control over the appearance of a List. The first method, setFitPolicy(), tells the List how it should handle elements whose text is wider than the screen. The possible values (from the Choice interface) are
TEXT_WRAP_ON denotes that long elements will be wrapped to multiple lines.
TEXT_WRAP_OFF denotes that long elements will be truncated at the edge of the screen.
TEXT_WRAP_DEFAULT indicates that the implementation should use its default fit policy.
Another new method is setFont(), which allows you to specify the font that will be used for a specific List element. (Fonts will be fully discussed in Chapter 10.) The current Font for an element can be retrieved by calling getFont(). Calls to setFitPolicy() and setFont() only serve as hints; it's up to the implementation to decide how to display the List and whether the requested fit policy or font can be honored.
You can find out whether a particular element in a List is selected by supplying the element's index to the following method:
For EXCLUSIVE and IMPLICIT lists, the index of the single selected element is returned from the following method:
public int getSelectedIndex()
If you call getSelectedIndex() on a MULTIPLE list, it will return -1.
To change the current selection programmatically, use setSelectedIndex().
public void setSelectedIndex(int index, boolean selected)
Finally, List allows you to set or get the selection state en masse with the following methods. The supplied arrays must have as many array elements as there are list elements.
public int getSelectedFlags(boolean[] selectedArray_return) public void setSelectedFlags(boolean[] selectedArray)
The example in Listing 6-1 shows a simple MIDlet that could be part of a travel reservation application. The user chooses what type of reservation to make. This example uses an IMPLICIT list, which is essentially a menu.
import java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class TravelList
extends MIDlet
implements CommandListener {
private List mList;
private Command mExitCommand, mNextCommand;
public TravelList() {
String[] stringElements = { "Airplane", "Car", "Hotel" };
Image[] imageElements = { loadImage("/airplane.png"),
loadImage("/car.png"), loadImage("/hotel.png") };
mList = new List("Reservation type", List.IMPLICIT,
stringElements, imageElements);
mNextCommand = new Command("Next", Command.SCREEN, 0);
mExitCommand = new Command("Exit", Command.EXIT, 0);
mList.addCommand(mNextCommand);
mList.addCommand(mExitCommand);
mList.setCommandListener(this);
}
public void startApp() {
Display.getDisplay(this).setCurrent(mList);
}
public void commandAction(Command c, Displayable s) {
if (c == mNextCommand || c == List.SELECT_COMMAND) {
int index = mList.getSelectedIndex();
Alert alert = new Alert("Your selection",
"You chose " + mList.getString(index) + ".",
null, AlertType.INFO);
Display.getDisplay(this).setCurrent(alert, mList);
}
else if (c == mExitCommand)
notifyDestroyed();
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
private Image loadImage(String name) {
Image image = null;
try {
image = Image.createImage(name);
}
catch (IOException ioe) {
System.out.println(ioe);
}
return image;
}
}
To see images in this example, you'll need to either download the examples from the book's Web site or supply your own images. With the J2ME Wireless Toolkit, image files should go in the res directory of your toolkit project directory. TravelList expects to find three images named airplane.png, car.png, and hotel.png.
Construction of the List itself is very straightforward. Our application also includes a Next command and an Exit command, which are both added to the List. The TravelList instance is registered as the CommandListener for the List. If the Next command or the List's IMPLICIT command is fired off, we simply retrieve the selected item from the List and show it in an Alert.
The Next command, in fact, is not strictly necessary in this example since you can achieve the same result by clicking the select button on one of the elements in the List. Nevertheless, it might be a good idea to leave it there. Maybe all of the other screens in your application have a Next command, so you could keep it for user interface consistency. It never hurts to provide the user with more than one way of doing things.
The difference between EXCLUSIVE and IMPLICIT lists can be subtle. Try changing the List in this example to EXCLUSIVE to see how the user experience is different.
[1]MIDP implementations are not required to recognize all varieties of PNG files. The documentation for the Image class has the specifics.