Exploring the ListField, Part 2

Subclassing for fun and function.

Subclassing

In our last article on the BlackBerry ListField we learned how to display data in a vertically oriented list.

We explored drawing text and even how to search for entries with a progressive text search.

However, the UI was somewhat bland. This article extends the prior code to upgrade the UI a bit. Along the way we demonstrate a technique for subclassing the ListField to bring some more life to the application and importantly to move in the direction of building more custom functionality to help your applications stand apart from the crowd.

Programmers familiar with object oriented languages such as Java, Objective-C, C++, Smalltalk, and others know that being comfortable with subclassing is a key technique to getting your applications to deliver the functionality you want and provide that something “extra” we desire out of our applications. It is kind of like having your kid at the science fair demonstrating along side the others. They all had access to the same materials and were given the same assignment to build a volcano, but then your kid went a step further and made the lava coming out of the volcano not only red and gooey, but hot and so life-like that someone pulled the fire alarm in terror. OK, so maybe that is a little over the top, but that is what we’re after — something in our applications that delivers more than the “out of the box” variety experience.

Some might say, then why are you messing with BlackBerry? Good question — I have faith that RIM is not going to roll over and issue iPhones to their staff. If they do, I’ll be the first to admit I was wrong and purchase yet another device myself. Now, let’s see what we can do to add some more spice to our ListField-based BlackBerry Application.

Some more color

In an earlier article we looked at changing the color of a BlackBerry screen by subclassing the VerticalFieldManager. This article demonstrates adding color to the BlackBerry ListField.

Some of the behavior is modified via a subclassed class named ColorList, while the primary modifications are made within the drawListRow() method of the ListFieldCallback implementation.

Before we get into the specifics of subclassing the ColorField, let’s add some functionality to the application using the stock ListField.

Here is the code for the main UI screen, BBColorScreen:

/*
 * BBColorScreen.java
 */

package com.msi.linuxmag.cl;

import net.rim.device.api.ui.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ButtonField;

import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.component.ListField;
import net.rim.device.api.ui.component.ListFieldCallback;

class BBColorScreen extends MainScreen implements ListFieldCallback,FieldChangeListener{
    ColorList sampleList;
    int currentColorIndex = 0;
    ButtonField changeColorButton;
    String [] listData = {"ALICEBLUE","ANTIQUEWHITE","AQUA","AQUAMARINE","AZURE",
"BEIGE","BISQUE","BLACK","BLANCHEDIAMOND"};
    int [] colorData = {Color.ALICEBLUE,Color.ANTIQUEWHITE,Color.AQUA,Color.AQUAMARINE,
Color.AZURE,Color.BEIGE,Color.BISQUE,Color.BLACK,Color.BLANCHEDALMOND};

    BBColorScreen() {
        super(DEFAULT_MENU);
        changeColorButton = new ButtonField("Change colors",
ButtonField.CONSUME_CLICK | ButtonField.NEVER_DIRTY);
        changeColorButton.setChangeListener(this);
        add(changeColorButton);
        add(new SeparatorField());
        sampleList = new ColorList();
        sampleList.setSize(listData.length);
        sampleList.setCallback(this);
        add(sampleList);
        setTitle(new LabelField("Linux Mag ListField",LabelField.USE_ALL_WIDTH | DrawStyle.HCENTER));
    }
 /*
    protected void applyTheme() {
        System.out.println("applyTheme ... skeaky bugger");
    }
   */
    private int makeRGB(int r,int g,int b) {
        return (r << 16) + (g << 8) + b;
    }

    public void fieldChanged(Field f, int context)
    {
        if (f == changeColorButton) {
            currentColorIndex++;
            if (currentColorIndex >= colorData.length) currentColorIndex = 0;
            sampleList.invalidate();
        }
    }

    /* ListFieldCallback Interface methods    */
    public void drawListRow(ListField listField,Graphics graphics,int index,int y,int width)
    {
        graphics.setFont(Font.getDefault());
        graphics.setColor(colorData[currentColorIndex]);
        graphics.fillRect(0,listField.getRowHeight() * index,Display.getWidth(),listField.getRowHeight());
        graphics.setColor(Color.BLACK);
        graphics.drawText(listData[index],2,y,DrawStyle.TOP,width);
    }

    public int getPreferredWidth(ListField listField)
    {
        return Display.getWidth();
    }
    public Object get(ListField listField,int index)
    {
        return listData[index];
    }

    public int indexOfList(ListField listField,String prefix,int start)
    {
        try {
            int ret = -1;
            for (ret = start;ret<listField.getSize();ret++) {
                if (listData[ret].toUpperCase().startsWith(prefix.toUpperCase())) return ret;
            }
            return 0;
        } catch (Exception e) {
            return 0;
        }
    }
/* End of ListFieldCallback Interface methods */

}

This user interface consists of three fields: a ButtonField (changeColorButton), a SeparatorField, and a ListField (sampleList).

New UI with ButtonField, SeparatorField, and ListField
New UI with ButtonField, SeparatorField, and ListField

This class implements not only the ListFieldCallback but also the FieldChangeListener interface to handle Button presses. In addition to the array of Strings, this class has an array of integers holding Color values. Each time the user presses the changeColorButton the class level variable currentColorIndex is incremented through the range of available color values stored in the colorData array. Then we request the ListField to redraw by calling its invalidate() method.

    public void fieldChanged(Field f, int context)
    {
        if (f == changeColorButton) {
            currentColorIndex++;
            if (currentColorIndex >= colorData.length) currentColorIndex = 0;
            sampleList.invalidate();
        }
    }

Now, with some color!
Now, with some color!

As the user selects the changeColorButton, the display updates with each successive color used as the background. In order to draw this background we have to perform a little bit of work by actually painting it with the graphics.fillRect() method:

    public void drawListRow(ListField listField,Graphics graphics,int index,int y,int width)
    {
        ....
        graphics.setColor(colorData[currentColorIndex]);
        graphics.fillRect(0,listField.getRowHeight() * index,Display.getWidth(),listField.getRowHeight());
        ....
    }

Note how we use the listField’s row height to calculate the dimensions of the rectangle to draw. Now we can cycle through the colors quite happily, however, we’re missing something — the focus rectangle. That’s right, we don’t know which row is selected! How do we fix that?

Uhoh, where's the selection?!
Uhoh, where’s the selection?!

Enter subclassing. We subclass the ListField in order to override the drawFocus() method.

/*
 * ColorList.java
 */

package com.msi.linuxmag.cl;

import net.rim.device.api.ui.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.component.ListField;
import net.rim.device.api.ui.component.ListFieldCallback;

class ColorList extends ListField{
    ColorList() {
        super();
    }

    protected void drawFocus(Graphics g,boolean on) {
        g.invert(g.getClippingRect());
    }
}

We could of course override more methods and anyone doing some serious BlackBerry programming will likely wind up down that road before long. In this example, we demonstrate overriding only the drawFocus() method where we invert the currently “selected” area, otherwise known as the clipping rectangle.

In order to use this new class, revisit the code and replace the instances of ListField with ColorList:

ColorList sampleList;

    BBColorScreen() {
        super(DEFAULT_MENU);
        ...
        sampleList = new ColorList();
        ...
    }

Now, when we search for an entry and it is selected, we can actually see it! What is particularly interesting is that we get a more pleasant experience by asking the display to invert the colors rather than picking a hard-coded color for the focus.

That's better, Inverted row shows selection.
That’s better, Inverted row shows selection.

OK, so that’s cool, we have some color. But what about something a little bit more interesting? Let’s say we want to display an image behind our list’s entries. We can do that too!

We can add a background image incrementally by drawing a “slice” of a Bitmap instead of a background color.

    Bitmap backgroundImage;

    BBColorScreen() {
        ...
        backgroundImage = Bitmap.getBitmapResource("lmlogo.jpg");
        ...
    }

    public void drawListRow(ListField listField,Graphics graphics,int index,int y,int width)
    {
        XYRect rect = new XYRect(0,y,Math.min(Display.getWidth(),this.backgroundImage.getWidth()),Math.min(listField.getRowHeight(),this.backgroundImage.getHeight()));
        graphics.drawBitmap(rect,this.backgroundImage,0,y % this.backgroundImage.getHeight());
        graphics.setFont(Font.getDefault());
    //    graphics.setColor(colorData[currentColorIndex]);
    //    graphics.fillRect(0,listField.getRowHeight() * index,Display.getWidth(),listField.getRowHeight());
        graphics.setColor(Color.BLACK);
        graphics.drawText(listData[index],2,y,DrawStyle.TOP,width);
    }

Now with background image.  But too dark!
Now with background image. But too dark!

We accomplish this by first calculating each list’s rectangle (XYRect in the code) and then painting the Bitmap into that rectangle. Note that in the constructor, we load the Bitmap via the Bitmap.getBitmapResource(filename) method.

So far so good. Oh wait, we cannot see the list entries with the graphic so dark. Let’s have a look at thinning out the background through the use of the alpha channel.

    public void drawListRow(ListField listField,Graphics graphics,int index,int y,int width)
    {
        int saveAlpha = graphics.getGlobalAlpha();
        graphics.setGlobalAlpha(64);

        XYRect rect = new XYRect(0,y,Math.min(Display.getWidth(),this.backgroundImage.getWidth()),Math.min(listField.getRowHeight(),this.backgroundImage.getHeight()));
        graphics.drawBitmap(rect,this.backgroundImage,0,y % this.backgroundImage.getHeight());

        graphics.setGlobalAlpha(saveAlpha);

        graphics.setFont(Font.getDefault());
        graphics.setColor(Color.BLACK);
        graphics.drawText(listData[index],2,y,DrawStyle.TOP,width);
    }

Light backround allows list to show
Light backround allows list to show

And here it is with a selected item.

And finally, with selected entry.
And finally, with selected entry.

There is more to do to take the UI “over the top”, however we’ll have to revisit it another day. For now, hopefully you have a feel for somethings that can be done to dress up the display a bit. Next up, displaying records from a persistent store.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62