BLOGGER TEMPLATES AND Blogger Templates »

Senin, 06 Mei 2013

A Little Variation of Good 'Ol J2ME Analog Clock


If you manage to get here by typing "analog clock j2me" or some sort in Google, chances are you have five or more tabs opened in your browser. It's just that an analog clock is pretty much a common thing around, with J2ME being no exception.
That's why this time I'll take a little spin on the analog clock and give it a way to make the thing run faster - or so I hope. In this post we will see how  - instead of the usual sine & cos function (or method, whatever) - to use arrays containing values that corresponds to certain sine & cos value of an angle in an analog clock program using J2ME.

Why Array?

If you have done this before using the sine & cos function, you may ask "Why reinvent the wheels?". You see, the wheels are really a staple of human creativity, as it allow us to move heavy things such as a carriage with much ease than before. That's why, to improve it's usefulness, many improvements are made to the wheels itself, such as using rubber instead of just wood or iron as the outer shell to maximize friction while maintaining the same reliability, and also...
Wait, why are we talking about wheels? Okay, so using sine & cos function, the same goal can be achieved (which is making a line spin, seriously) but if there's one thing we all know about mobile phone: it's slow. Okay, maybe not that slow, but it still run very slowly compared to PCs. So, instead of telling the phone to calculate sine & cos value of an angle every second (which is the same sixty values, anyway) we will give the phone a set of numbers, perfectly ordered from 0 to 360 degrees value of sine & cos, and then told the phone to "skim the catalog" instead. It will be much faster to simply look up a value from a table than calculating it all the way (even humans know it). A lecturer in my college told me about this - and even manage to give an assignment about it (guess which one it is, haha).

The Code

Since it's an analog clock, it will be wise to leave the "ticking" task to a Thread, since the "ticking" will happen forever and we'll still need the MIDlet to hear our Commands. I'm using a Canvas to draw the clock, but if you have a better idea you can go green light (whatever it's supposed to mean).

The MIDlet:
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;

public class WatchMidlet extends MIDlet
{
    Display theDisplay = null;
    WatchCanvas theCanvas = null;
    TimeTicker theTicker = null;

    public WatchMidlet()
    {
        super();
    }

    public void startApp()
    {
        theCanvas = new WatchCanvas(this);
        theTicker = new TimeTicker(this);
        theTicker.start();

        if (theDisplay == null)
        {
            theDisplay = Display.getDisplay(this);
        }
        theDisplay.setCurrent(theCanvas);
    }

    public void pauseApp()
    {
    }

    public void destroyApp(boolean unconditional)
    {
    }

    public void quitApp()
    {
        theTicker.setAlive(false);
        destroyApp(true);
        notifyDestroyed();
    }

    public void tick()
    {
        theCanvas.detik();
    }
}

The Thread:
public class TimeTicker extends Thread
{
    boolean isAlive = true;
    WatchMidlet theMidlet = null;
    int tickTime = 1000;

    public TimeTicker(WatchMidlet w)
    {
        super();
        this.theMidlet = w;
    }

    public void run()
    {
        while (isAlive)
        {
            try
            {
                Thread.sleep(tickTime);
            } catch (Exception e)
            {
            }
            theMidlet.tick();
        }
    }

    public void setAlive(boolean val)
    {
        this.isAlive = val;
    }
}

The Canvas:
import javax.microedition.lcdui.*;
public class WatchCanvas extends Canvas implements CommandListener
{
    Command exitCommand = new Command("Exit", Command.EXIT, 0);
    WatchMidlet theMidlet;
    double sin[] =
    {
        0.00, 0.10, 0.21, 0.31, 0.41, 0.50, 0.59, 0.67, 0.74, 0.81,
        0.87, 0.91, 0.95, 0.98, 0.99, 1.00, 0.99, 0.98, 0.95, 0.91,
        0.87, 0.81, 0.74, 0.67, 0.59, 0.50, 0.41, 0.31, 0.21, 0.10,
        0.00, -0.10, -0.21, -0.31, -0.41, -0.50, -0.59, -0.67, -0.74, -0.81,
        -0.87, -0.91, -0.95, -0.98, -0.99, -1.00, -0.99, -0.98, -0.95, -0.91,
        -0.87, -0.81, -0.74, -0.67, -0.59, -0.50, -0.41, -0.31, -0.21, -0.10,
        0.00
    };
    double cos[] =
    {
        1.00, 0.99, 0.98, 0.95, 0.91, 0.87, 0.81, 0.74, 0.67, 0.59,
        0.50, 0.41, 0.31, 0.21, 0.10, 0.00, -0.10, -0.21, -0.31, -0.41,
        -0.50, -0.59, -0.67, -0.74, -0.81, -0.87, -0.91, -0.95, -0.98, -0.99,
        -1.00, -0.99, -0.98, -0.95, -0.91, -0.87, -0.81, -0.74, -0.67, -0.59,
        -0.50, -0.41, -0.31, -0.21, -0.10, 0.00, 0.10, 0.21, 0.31, 0.41,
        0.50, 0.59, 0.67, 0.74, 0.81, 0.87, 0.91, 0.95, 0.98, 0.99,
        1.00
    };
    int[] theta =
    {
        15, 14, 13, 12, 11, 10, 9, 8, 7, 6,
        5, 4, 3, 2, 1, 0, 59, 58, 57, 56,
        55, 54, 53, 52, 51, 50, 49, 48, 47, 46,
        45, 44, 43, 42, 41, 40, 39, 38, 37, 36,
        35, 34, 33, 32, 31, 30, 29, 28, 27, 26,
        25, 24, 23, 22, 21, 20, 19, 18, 17, 16,
    };
    int indexS = 0, indexM = 0, indexH = 0;
    int length = 100;
    int length2 = 50;
    int centrX = 0;
    int centrY = 0;
    int prevSecX = 0, prevMinX = 0, prevHourX = 0;
    int prevSecY = 0, prevMinY = 0, prevHourY = 0;
    int currSecX = 0, currMinX = 0, currHourX = 0;
    int currSecY = 0, currMinY = 0, currHourY = 0;
    boolean begin = true;

    public WatchCanvas(WatchMidlet w)
    {
        super();
        this.theMidlet = w;
        addCommand(exitCommand);
        setCommandListener(this);
    }

    public void paint(Graphics g)
    {
        if (begin)
        {
            centrX = getWidth() / 2;
            centrY = getHeight() / 2;
            currSecX = pointX(0, length);
            currSecY = pointY(0, length);
            prevSecX = centrX;
            prevSecY = centrY;
            currMinX = pointX(0, length);
            currMinY = pointY(0, length);
            prevMinX = centrX;
            prevMinY = centrY;
            currHourX = pointX(0, length2);
            currHourY = pointY(0, length2);
            prevHourX = centrX;
            prevHourY = centrY;
            g.setColor(255, 255, 255);
            g.fillRect(0, 0, getWidth(), getHeight());
            g.setColor(0, 0, 0);
            for (int i = 1; i <= 12; i++)
            {
                if(i == 12)
                {
                    g.drawString("12",
                        pointX(0, length), pointY(0, length),
                        Graphics.BASELINE | Graphics.HCENTER);
                } else
                {
                    g.drawString(String.valueOf(i),
                        pointX(i * 5, length), pointY(i * 5, length),
                        Graphics.BASELINE | Graphics.HCENTER);
                }
            }
            begin = false;
        }

        g.setColor(255, 255, 255);
        g.drawLine(centrX, centrY, prevSecX, prevSecY);
        g.drawLine(centrX, centrY, prevMinX, prevMinY);
        g.drawLine(centrX, centrY, prevHourX, prevHourY);

        g.setColor(0, 0, 255);
        g.drawLine(centrX, centrY, currSecX, currSecY);
        g.setColor(0, 255, 0);
        g.drawLine(centrX, centrY, currMinX, currMinY);
        g.setColor(255, 0, 0);
        g.drawLine(centrX, centrY, currHourX, currHourY);
    }

    public void commandAction(Command c, Displayable d)
    {
        if (c == exitCommand)
        {
            theMidlet.quitApp();
        }
    }

    public void detik()
    {
        indexS++;
        if (indexS == 60)
        {
            indexS = 0;
            menit();
        }
        prevSecX = currSecX;
        prevSecY = currSecY;

        currSecX = pointX(indexS, length);
        currSecY = pointY(indexS, length);
        this.repaint();
    }

    public void menit()
    {
        indexM++;
        if (indexM == 60)
        {
            indexM = 0;
            jam();
        }
        prevMinX = currMinX;
        prevMinY = currMinY;

        currMinX = pointX(indexM, length);
        currMinY = pointY(indexM, length);
        this.repaint();
    }

    public void jam()
    {
        indexH++;
        if (indexH == 60)
        {
            indexH = 0;
        }
        prevHourX = currHourX;
        prevHourY = currHourY;

        currHourX = pointX(indexH, length2);
        currHourY = pointY(indexH, length2);
        this.repaint();
    }

    int pointX(int clockNum, int handLength)
    {
        return centrX + (int) (handLength * cos[theta[clockNum]]);
    }

    int pointY(int clockNum, int handLength)
    {
        return centrY - (int) (handLength * sin[theta[clockNum]]);
    }
}

If you do it right it should look something like this:


You may say the result is exactly the same with the "function" way, but the truth will be told when you let this application and another - that uses the sine & cos function - run for a while (I recommend in a phone, not an emulator). You will see a significant difference in performance after running both methods for approximately an hour.

You can find the complete project (in NetBeans) here, in this link, where you click.

There will be two MIDlets: one is the clock above and the other, is...
Let's just say you've been warned.

0 komentar: