Tuesday, June 30, 2009

Adding a scrollbar

Sometimes you have more data to show than available space. One way to make this clear to the user is presenting a scroll bar.
Lets update our custom alert to paint a scroll bar when necessary.
Below is the source code of my simple scroll bar component:

import javax.microedition.lcdui.Graphics;

public class Scrollbar {

private int x, y, width, height;
private int total; // how much you have to present
private int position; // what you are presenting
private int backgroundColor = 0xffffff;
private int foregroundColor;
private int indicatorHeight;

/**
* @param part how much can be presented to the user at a time
*/
public Scrollbar(int x, int y, int w, int h,
int part, int total)
{
this.x = x; this.y = y; this.width = w; this.height = h;
this.total = total;
// the indicator height is calculated as a proportion of h
indicatorHeight = (part * h) / total;
// sanity test
if (indicatorHeight >= height) {
indicatorHeight = height / 2;
}
}

public void setPosition(int position) {
this.position = position;
}

public void setBackgroundColor(int backgroundColor) {
this.backgroundColor = backgroundColor;
}

public void setForegroundColor(int foregroundColor) {
this.foregroundColor = foregroundColor;
}

public void paint(Graphics g) {
int indicatorY = (this.position * this.height) / this.total;

// sanity test, can not draw below the height
if (indicatorY + this.indicatorHeight > height) {
indicatorY = height - this.indicatorHeight;
}

g.setColor(this.backgroundColor);
g.fillRect(x, y, width, height);
g.setColor(this.foregroundColor);
g.fillRect(x, indicatorY, width, indicatorHeight);
}
}

Now lets use the scroll bar. Define an Scrollbar attribute and create an instance right after you have initiated your message array:

// how many lines the user can see at a time
int maxLines = this.getHeight() / Font.getDefaultFont().getHeight();

if (maxLines < this.message.length) {
// 10 = scroll bar width. Remember to also subtract this value
// when initiating message array
this.scrollbar = new Scrollbar(getWidth() - 10, 0, 10, getHeight(),
maxLines, this.message.length)
}

At commandAction right after you update messageFirstLineShown:

if (this.scrollbar != null) {
this.scrollbar.setPosition(this.messageFirstLineShown);
}

At paint method:

if (this.scrollbar != null) {
this.scrollbar.paint(g);
}

Now, when the user press Next command he will see the scroll bar indicator move downwards.
This is a simple implementation with a simple usage. Feel free to change any part of it on your project.

Related topics:

Saturday, June 20, 2009

Key Repetition with TimerTask

It is not friendly to force your user to repeat key presses. My CustomImplicitList had this fault. If you wanted to move the selection using up/down keys or 2/8 numbers you had to press the key once for every change. To avoid this I made a simple change, adding the following method:

protected void keyRepeated(int keyCode) {
this.keyPressed(keyCode);
}

Now the user can press and hold a key to keep changing the selection, right? Wrong! Because not all Java ME Virtual Machines call keyRepeated. You can know it by calling Canvas.hasRepeatEvents(). So, how do we grant key repetition to these devices?

I could use a Thread and flag attributes to solve this problem, but I prefer creating an inner class at CustomImplicitList:

class KeyRepeatTask extends TimerTask {
public void run() {
if (isShown() && keyCode != Integer.MIN_VALUE) {
keyPressed(keyCode);
}
}
}

Two new attributes will help me control the key repetition:

private Timer keyRepeatTimer;
private int keyCode = Integer.MIN_VALUE;

To start the key repetition I added the following lines to the very start of keyPressed method:

if (this.hasRepeatEvents() == false && this.keyCode == Integer.MIN_VALUE) {
this.keyCode = keyCode;
this.keyRepeatTimer = new Timer();
// keep repeating the task at each 700 milliseconds
this.keyRepeatTimer.scheduleAtFixedRate(new KeyRepeatTask(), new Date(), 700);
}

To stop the key repetition I added the following method:

protected void keyReleased(int keyCode) {
if (this.keyRepeatTimer != null) {
this.keyCode = Integer.MIN_VALUE;
this.keyRepeatTimer.cancel();
this.keyRepeatTimer = null;
}
}

How did I test this? First I removed the implementation of keyRepeated and added hasRepeatEvents always returning false. Then I executed my use case on emulators and personal handsets. Of course this is not enough to guarantee the same behavior on all Java ME enabled handsets, as this is highly dependendent on the correct implementation of hasRepeatEvents but is a safe start.

If you find out this code does not work on your handset, please let me know.

Related topics:

Sunday, June 14, 2009

MIDP 1.0 long tail

I don't know of any new handset that comes with a MIDP 1.0 Java Virtual Machine. New handsets comes with MIDP 2.0 or 2.1 and will come, eventually, with 3.0.
But I know there is a market for these old handsets and began following the daily downloads of my Chess board. Below is an updated graphic of these downloads:


We can see a trend with a smaller number of downloads for each week. Some day this curve will reach zero and not move anymore, but when?
Since April, 19 MIDP 1.0 handsets were responsible for 1069 new downloads, correspondig to 17.8% of the total. For me it is enough to keep evolving this version. Because old users may come back and download an updated application.

Related topics: