The MIDlet entry point is a simple Main
class
that contains the standard MIDlet lifecycle methods. When the startApp
method is called, it launches the Splash
class, which shows basic information about the MIDlet. When the
user taps the Start Jamming button, the Drumkit
view opens, containing the main UI of the MIDlet.
The Drumkit
view consists of a single GameCanvas
-based view. The view background is a static 240
x 400 pixel PNG image, where the drum pads are displayed. The background
is designed to also work on a 240 x 320 screen resolution. The pads
themselves are drawn as circular, but, for easier touch event handling,
the actual areas where taps are listened to are rectangular elements.
The same applies to the buttons on the main view (play, record, and
exit).
For maximum responsiveness, touch events are handled
with the pointerPressed
method instead of pointerReleased
, which is generally more common in UI design.
When the events are handled immediately after the tap has been received,
the corresponding sample can be played instantly.
protected void pointerPressed(int x, int y) { pressed = true; if (kitEnabled) { for (int i = 0; i < 4; i++) { Pad pad = pads[i]; if (pad.contains(x, y)) { if (recording) { // Record sample tape.addElement(new Sample(pad)); } // Play sound assigned to the pad SoundManager.playSound(pad.getSound()); pad.setHit(true); // Set timer for monitoring long tap if (!recording) { acivePad = i; monitorLongTap(x, y); } break; } } } playBtn.pointerPressed(x, y); recordBtn.pointerPressed(x, y); exitBtn.pointerPressed(x, y); wheel.pointerPressed(x, y); }
The record button is animated using the AnimatedButton
class, which uses the Sprite
class for the animation. The animated
sprites are all within a single PNG file from where they are read
when the sprite is active. The following code snippet shows how the AnimatedButton
class starts and stops the animation.
public class AnimatedButton extends Button { private long interval; private Timer animator; private Sprite animation; public AnimatedButton(String unpressed_image_url, String pressed_image_url, String disabled_image_url, long interval, Listener listener) { super(unpressed_image_url, pressed_image_url, disabled_image_url, listener); this.interval = interval; this.animation = new Sprite(getPressedImage(), getWidth(), getHeight()); } // ... /** * Start animation */ private void startAnimation(long interval) { if (animator != null) { stopAnimation(); } animator = new Timer(); animator.schedule(new TimerTask() { public void run() { animation.nextFrame(); } }, 0, interval); } /** * Stop animation */ private void stopAnimation() { if (animator != null) { animator.cancel(); animator = null; animation.setFrame(0); } } }
The wheel menu has a selection of samples for pads. It opens with a long tap and has a simple hide and show animation. The menu consists of 12 PNG icons, which are positioned in a circle for selecting different drum samples. The animations are done by varying the circle radius and the angle passed to sine and cosine functions.
Animations use Timer
and TimerTasks
. The following update function
is called after lengthening or shortening the radius. The loop calculates
new positions for the wheel buttons:
public void updateWheel() { float ratio = (float) radius / MAX_RADIUS; int newX = originX + (int) ((float) (destinationX - originX) * ratio); int newY = originY + (int) ((float) (destinationY - originY) * ratio); int btnX = 0; int btnY = 0; // AngleExtra is used to get the spinning effect float angleExtra = 8 * ratio; for (int i = 0; i < BUTTON_COUNT; i++) { btnX = (int) (Math.cos(i * ANGLE + angleExtra) * radius); btnY = (int) (Math.sin(i * ANGLE + angleExtra) * radius); buttons[i].setPosition(btnX - btnWidth / 2 + newX, btnY - btnHeight / 2 + newY); } }
Long tap detection also uses a Timer
. When the user touches the screen, a new Timer
with
a new TimerTask
is initialized. If a long tap timer
already exists, it is cancelled. Also, the boolean flag pressed
turns true
, and if the user lifts his/her finger
from the screen, pressed
turns false
. The timer task simply checks if the pressed
flag
is still true
after a given period, which in this
case is one second. The following code snipped shows how the long
tap events are handled:
private boolean pressed = false; // ... private int acivePad = -1; // ... private Timer longTapTimer; // ... protected void pointerPressed(int x, int y) { pressed = true; if (kitEnabled) { for (int i = 0; i < 4; i++) { Pad pad = pads[i]; if (pad.contains(x, y)) { // ... // Set timer for monitoring long tap if (!recording) { acivePad = i; monitorLongTap(x, y); } break; } } } // ... wheel.pointerPressed(x, y); } // ... protected void pointerReleased(int x, int y) { // ... wheel.pointerReleased(x, y); // Stop monitoring long taps if (longTapTimer != null) { longTapTimer.cancel(); longTapTimer = null; } pressed = false; } // ... private void monitorLongTap(final int x, final int y) { // Reset long tap timer if (longTapTimer != null) { longTapTimer.cancel(); } longTapTimer = new Timer(); final int pad = acivePad; // Task that shows prepares wheel menu to show up in the right place longTapTimer.schedule( new TimerTask() { public void run() { if (pressed) { wheel.setOrigin(x, y); // Calculate destination coordinates for the wheel menu centerX = getWidth() / 2; centerY = getHeight() / 2; int destinationX = centerX - (int) (0.2 * (centerX - x)); int destinationY = centerY - (int) (0.5 * (centerY - y)); wheel.setDestination(destinationX, destinationY); stopTape(); // ... // Preselect a percussion and open the menu wheel.select(pads[pad].getSound()); wheel.show(); kitEnabled = false; } } }, 1000); }