Building Up a Media Center Application with TWUIK
Part of the “Coding MIDlet with TWUIK” Tutorial Series
TUTORIAL PART 1
Getting Started and the Main Menu Screen
Introduction
In tutorial, we are going to show you the basics of creating a midlet using TWUIK, our J2ME GUI SDK. We are going to walk you through the various features and benefits of TWUIK by building up a Media Center application. This application will feature a flashy and rich yet simple user interface to provide functionalities such as viewing the photos in your gallery, playing music from MP3 or WAV files, as well as playing video from 3GPP files. We hope from this you can learn in more details about TWUIK especially on how easy and maintainable it is to code a MIDlet with it. There will be 6 parts of this tutorial. The first part will cover the grounding and show the implementation of the first screen. The remaining parts will revolve around the remaining screens, and the last part finishes with optimization tips. So, let’s start the TWUIK-ing fun!
Up Close and Personal with Fundamentals of TWUIK RME
TWUIK RME consists of a collection of libraries centred on the core module itself. The various libraries provide a rich set of animated, FLASH-like GUI components. It features a plug-and-play concept to package portions of the whole library collection into an API package so-called TINY API and distributed in a small footprint packaged JAR. TWUIK RME currently provides the following main TINY APIs: TINY Form, TINY Window, TINY Widgets, TINY FX, and TINY Font. We will use some bits of each of these APIs as we go along building up the Media Center application.
The TWUIK architecture is based on animation elements. Each element is tied to one animation engine to make up an ‘Animation System’. Each animation system consists of one AnimationCanvas object, which works at the same rate as the animation engine, say, x frame per second (FPS), and a set of Component objects, which can work at a different rate of animation (being a subrate of the AnimationCanvas’ one, though). We name the animation rate of a Component as tick-per-animation (TPA) which indicates how many ticks involved in one animation step of this component. So suppose a Component is working at y TPA, its actual frame rate is x/y FPS. This is to be taken into consideration whenever constructing a new Component.
Each Component can either be double-buffered or not. When a component is in double buffering mode, TWUIK creates an additional buffer for it and when the component is requested for frame update, it will paint the latest update on the buffer rather than directly on the AnimationCanvas. This is a key feature in TWUIK to improve the painting efficiency of rendering each screen update. We will look back into this in the final part of this tutorial when we discuss about optimization. For now, we will just make all the Components that we construct to be in non double-buffering mode.
There are three types of events that are supported in TWUIK: canvas events, component events and FX events. Instances of canvas events (including canvas-related events, animation-related events, key-events, and pointer-events) are reported to the CanvasEventListener interface implementer. An AnimationCanvas can also have one MainInputHandler Component, which is a Component that is designed to handle input events directly among all the other components it contains. The type of input events that can be handled in this way include all those grouped under the key-events and pointer-events.
Similarly, instances of component events (which are specific to each Component) are reported to the ComponentEventListener interface implementer. This means, rather than using a separate listener for events belonging to each component, TWUIK uses a universal listener model. Events are then differentiated using their event ID which is relevant only to the corresponding Component that fires the event.
TINY FX consists of three types of effects engines: transition FX engine, motion FX engine, and transformation FX engine. Each of these engine types can generate FX events. Instances of FX events are reported to the interface implementer of the corresponding engine type’s FX event listener. Such events indicate the progress and the status of the running FX engine, such as whenever the engine is started or has finished.
Enough with all the discussion of TWUIK introduction, I want to get straight to the coding! Fine, starting from the next sections, we’ll start building the Media Center application! First, we will define the application structure, and then there we start.
Media Center: The Application Structure and UI Design
The application we are going to build, as has been said, is a Media Center application that supports viewing photos, playing music, as well as playing video. We will split between these features into individual screens. So there will be the main menu screen, photo gallery screen, audio player screen, video player screen, and then the settings screen (for configuration of preferences). For menu navigation system, we will use TWUIK’s FishEyeMenu widget. This menu widget will exists throughout all the screens. Without further ado, here is our initial UI design for the application:

As you can see, this is just an overview UI design. We will look at the details of the design for each screen and what features it offers when we discuss about the individual screen. Now you just need to know that we will make use of FishEyeMenu widget, PhotoPreviewGallery widget, and MagnificationList widget. Through this tutorial you will learn how to use each of these widgets. Furthermore, we will also create our own custom widgets, that is, for progress bar, for volume bar, and for playing video.
Note that some of the widgets will appear in most if not all of the screens. These are the FishEyeMenu and the progress bar. We will refer to these widgets as the application-level components (as opposed to screen-level components), and these widgets will be controlled by the controller midlet (which we will discuss very soon).
The design of the Settings screen is left until the tutorial part that discusses implementation of that screen since it mostly involves form components only so it does not really deserve much UI design. Starting from next section we will get straight into the coding-level of TWUIK-ing!
The AnimationCanvas Class
The AnimationCanvas class in TWUIK represents a virtual display screen as a Container (aka placeholder) of Components. Each AnimationCanvas coordinates the animation and interaction of the Components that it contains. It is important that TWUIK’s AnimationCanvas class is not confused with the LCDUI’s Canvas class (i.e., AnimationCanvas does not extend from Canvas) although they both function as a display screen. Similar to for Canvas, however, an AnimationCanvas is displayed onto the screen by calling the setCurrent method of the TWUIKDisplay (c.f. LCDUI’s Display).
Each AnimationCanvas has a given frame rate per second (the rate for updating the screen). An AnimationCanvas can be either in enabled mode or disabled mode. As an option, an animation canvas can also have an hourglass component.
As a simple example, the following code snippet creates a new AnimationCanvas, sets it up, creates and adds a component onto it, and then displays it onto the screen after disabling it:
... TWUIKDisplay.init (myMIDlet); AnimationCanvas myCanvas = new AnimationCanvas (50); //20 FPS myCanvas.setCanvasEventListener (myMIDlet); myCanvas.startStopAnimation(); SomeComponent myComponent = new SomeComponent (2,...); //2 TPA (i.e.10 FPS) myCanvas.addComponent (myComponent); myCanvas.setEnabledMode (false); TWUIKDisplay.setCurrent (myCanvas); ...
With the above code, the AnimationCanvas will be created and displayed onto the screen. We registers our MIDlet as the CanvasEventListener for this AnimationCanvas, but we then disable the AnimationCanvas. As a result of this, input events will not be notified to the MIDlet. I hope this simple example will be able to kick start our discussion for the following sections.
Using FishEyeMenu: Creating a Button Factory
As we have discussed previously, there will be two application-level components that we will use in our Media Center application. Namely, the FishEyeMenu widget and the progress bar widget. The progress bar widget is a custom-made widget but yet very simple. We will not discuss about its implementation in this tutorial, and only its API will be presented (when we discuss about the the framework and controller MIDlet later). The FishEyeMenu widget, on the other hand, is a built-in widget in TWUIK.
The FishEyeMenu widget align buttons horizontally. The selected button will be highlighted and displayed in its centre. Other buttons are distributed besides this selected one and with smaller size as they are distributed farther from the selected one, thus producing fish eye effect.
Buttons that can added to the FishEyeMenu are created from NeonButton. Please Refer to TINY&trade Extra for creating a NeonButton. When creating a NeonButton the developer will need to specify two images, one is for highlighted image, and the other is for the unhightlighted image. These button images need to be put under the “/buttons” resource folder and the naming need to be the same as the button name. For instance, to create a button named as “audio” you need to have two images: “/buttons/audio.png” for the highlighted image and “/buttons/audio_gray.png” for the unhighlighted one. So, here we go, following is a list of all the images we will use throughout our Media Center application.
![]() |
![]() |
||||
| audio | audio_gray | exit | exit_gray | next | next_gray |
| novisual | novisual_gray | novolume | novolume_gray | pause | pause_gray |
| photo | photo_gray | play | play_gray | playlist | playlist_gray |
![]() |
|||||
| prev | prev_gray | seek | seek_gray | setting | setting_gray |
![]() |
|||||
| slide | slide_gray | stop | stop_gray | thumb | thumb_gray |
![]() |
|||||
| tomenu | tomenu_gray | video | video_gray | visual | visual_gray |
![]() |
![]() |
![]() |
|||
| volume | volume_gray | zoomin | zoomin_gray | zoomout | zoomout_gray |
Creating a button using NeonButton based on the provided two images will cause the creation of intermediate images between the highlighted one and the unhighlighted one. This is configurable as a parameter into the method that creates a NeonButton, called the animation step. For our Media Center application, we will use 2. The creation of these images on-the-fly everything the program is run for every single button available is heavy. For this purpose, NeonButton provides a built-in support to conveniently store and load these images into/from the recordstore. This support is provided by the loadNeonButtonImagesFromRecordStore method and the updateNeonButtonImagesToRecordStore.
To encapsulate all the actions and checkings required for the creation of all the NeonButtons we will need throughout our application, we write a factory code, namely the ButtonFactory class. The implementation of this class is rather straightforward and will not be discussed here. We can just assume now that we already have such a class, which when initialized, will try to create all the neon buttons from the recordstore if any, otherwise will create them as new. Each NeonButton is identified using an ID. And the creation of the ButtonFactory is as simple as calling its constructor. Following are the declaration of IDs for all the NeonButtons that we can acquire from the ButtonFactory using its getNeonButton(int) API method (the ID names should be explanatory):
/*Constants for button ID*/ public static final int TOTALBUTTON = 18; // general buttons public static final int ID_UNDEFINED = -1; public static final int ID_EXIT = 0; public static final int ID_TOMENU = 1; // main menu screen buttons public static final int ID_PHOTO = 2; public static final int ID_AUDIO = 3; public static final int ID_VIDEO = 4; public static final int ID_SETTING = 5; // photo screen buttons //...to be added in future // audio screen buttons //...to be added in future //video screen buttons //...to be added in future
Thus, whenever we need to acquire the instance of the corresponding NeonButton (for example, the “play” button), we use:
buttonFactory.getNeonButton (ButtonFactory.ID_PLAY)
Coding in a Framework: Screens with a Controller
We have discussed that some components will appear in almost if not all of the screens in the application. We have also considered referring to these components as application-level components. To recall, these components are the FishEyeMenu and the custom-made progress bar widget (from now on, we will call it ProgressBar). So, since this is the case, and we have the notion of screens, we can define a framework.
Our framework principle would be: screens with a controller MIDlet. The main and only MIDlet in the application will be used to control or manage the various screens we have. We refer to this MIDlet as the Screen Controller. At code-level, this will be just an ordinary MIDlet that holds the instances of all the screens, which we name as MMCScreen.
Any MMCScreen is capable of status query callbacks from the Screen Controller such as whether it is ready to accept input events, or whether its menu can be currently interacted. Furthermore, it is also capable of receiving event notification callbacks from the Screen Controller, such as screen activation/deactivation, menu animation progress or events, and input events. So, MMCScreen is implemented as an abstract class as such:
Listing 1.1. Code for MMCScreen
abstract class MMCScreen{
/*Protected fields*/
protected AnimationCanvas canvas;
protected boolean bReadyToAcceptInputEvents;
protected boolean bMenuCanBeInteracted;
/*MIDlet status query callbacks*/
public abstract boolean isReadyToAcceptInputEvents();
public abstract boolean canMenuBeInteracted();
/*MIDlet event notification callbacks*/
public abstract void activated();
public abstract void menuEntryAnimEnded();
public abstract void menuExitAnimEnded();
public abstract void menuButtonFired (int iButtonID);
public abstract void menuSelectionChanged (int iButtonID);
public abstract void keyPressed (int iKeyCode);
public abstract void keyReleased (int iKeyCode);
public abstract void keyRepeated (int iKeyCode);
}
Remember that our Screen Controller is the MIDlet. Therefore, in our MIDlet, we need to declare the IDs for the screens, creates and sets up the application-level components, execute/provide code that adds the application-level components onto the current screen when they are to be shown (and reconfigures the components accordingly), and finally handles the canvas and component events occurring on any current screen. Following is the entire code for our Screen Controller, called MMCMIDlet. Explanation then follows as we discuss the code part by part.
Listing 1.2. Code for MMCMIDlet
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import com.tricastmedia.twuik.*;
import com.tricastmedia.twuik.fonts.*;
import com.tricastmedia.twuik.widgets.*;
public class MMCMIDlet extends MIDlet implements ComponentEventListener,
CanvasEventListener{
/*Initialize all the fonts that will be reused throughout the app run*/
public static final TwuikFont fontSysPropSmallBold = new SystemFont (
Font.FACE_PROPORTIONAL,
Font.STYLE_BOLD,
Font.SIZE_SMALL);
/*Internal IDs for identifying current screen*/
private static final int SCREENID_MAINMENU = 0;
private static final int SCREENID_PHOTO = 1;
private static final int SCREENID_AUDIO = 2;
private static final int SCREENID_VIDEO = 3;
private static final int SCREENID_SETTINGS = 4;
/*MIDlet relateds*/
private boolean bStarted = false;
/*Application-level components and screens*/
private ButtonFactory btnFactory;
FishEyeMenu menu;
ProgressBar progressBar;
private MainMenu mainMenu;
/*Working variables*/
private MMCScreen curScreen;
private AnimationCanvas curCanvas;
private int iCurScreenID;
private int iPrevScreenID;
private boolean bKeyRepeated=false;
public MMCMIDlet(){
/*TWUIK Initializations*/
TWUIKDisplay.init (this);
btnFactory = new ButtonFactory();
/*Initialize menu and other application-level components*/
menu = new FishEyeMenu (1,0,0,10,90,fontSysPropSmallBold);
menu.setEventListener (this);
progressBar = new ProgressBar (1,0,0,10,20);
/*Initialize working variables*/
iPrevScreenID = -1;
}
public void startApp(){
if (!bStarted){
bStarted = true;
gotoMainMenu(); //starts the application with the MainMenu screen
};
}
public void pauseApp(){}
public void destroyApp(boolean unconditional){}
void addMenu (AnimationCanvas canvas){
/*Ensure that the menu animation has been stopped first*/
if (! menu.isAnimationStopped()) menu.startStopAnimation();
/*Generic initializations for the menu regardless of which screen canvas*/
menu.iX = 0;
menu.iY = canvas.getHeight()-80;
menu.setSize (canvas.getWidth(),80);
menu.setButtonLayoutPositions (-(int) (68.0f/176*canvas.getWidth()),35,
-(int) (42.0f/176*canvas.getWidth()),35,
0, 35,
(int) (42.0f/176*canvas.getWidth()),35,
(int) (68.0f/176*canvas.getWidth()),35);
canvas.addComponent (menu);
/*Specific initializations depending on which screen it is to stay in*/
switch (iCurScreenID){
case SCREENID_MAINMENU:
menu.setFEPanelBackground (null, 0x000000);
menu.removeAllButton();
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_PHOTO));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_AUDIO));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_VIDEO));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_SETTING));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_EXIT));
switch (iPrevScreenID){
default: menu.setCurSel (ButtonFactory.ID_PHOTO); break;
};
break;
//...similarly for other screens
};
/*Restart the menu animation and begins with the entry animation*/
menu.resetStatus();
menu.startStopAnimation();
menu.initButtonEntryExitEffect (true);
}
void removeMenu (AnimationCanvas canvas){
canvas.removeComponent (menu); //simply detach the menu from the
//specified screen canvas
}
void addProgressBar (AnimationCanvas canvas){
/*Ensure that the progress bar animation has been stopped first*/
if (! progressBar.isAnimationStopped()) progressBar.startStopAnimation();
/*Generic initializations regardless of which screen canvas*/
progressBar.iX = 6;
progressBar.iY = canvas.getHeight()-menu.getHeight()-10-20;
progressBar.setSize (canvas.getWidth()-6-6,20);
canvas.addComponent (progressBar);
/*Specific initializations depending on which screen it is to stay in*/
switch (iCurScreenID){
//...for later
};
/*Restart the menu animation and begins with the entry animation*/
progressBar.startStopAnimation();
}
void removeProgressBar (AnimationCanvas canvas){
canvas.removeComponent (progressBar); //simply detach the progress bar from
//the specified screen canvas
}
void gotoMainMenu(){
/*Creates a new MainMenu screen, and initializes screen-related variables*/
mainMenu = new MainMenu (this);
curScreen = mainMenu;
curCanvas = mainMenu.canvas;
iPrevScreenID = iCurScreenID;
iCurScreenID = SCREENID_MAINMENU;
/*Attach the menu onto the MainMenu screen canvas*/
addMenu (curCanvas);
/*Make the MainMenu screen canvas as the current one*/
curCanvas.setEventListener (this);
TWUIKDisplay.setCurrent (curCanvas);
mainMenu.activated();
/*Ensure that any temporary objects on the heap particularly from previous
screen are garbage-collected*/
System.gc();
}
/*Other unimplemented canvas events*/
public void timerHasReallyStopped (AnimationCanvas prevCanvas){}
public void showNotify (AnimationCanvas canvas){}
public void hideNotify (AnimationCanvas canvas){}
public void screenModeChanged (AnimationCanvas canvas){}
public void keyPressed (AnimationCanvas canvas, int iKeyCode){
/*Do nothing if cur screen is currently not ready to accept input events*/
if (! curScreen.isReadyToAcceptInputEvents()) return;
/*For key press events that are dealing with menu and application-level*/
switch (curCanvas.getGameActionNoAlt (iKeyCode)){
case Canvas.LEFT:
if (curScreen.canMenuBeInteracted()){ menu.selectPrevButton();return; }
case Canvas.RIGHT:
if (curScreen.canMenuBeInteracted()){ menu.selectNextButton();return; }
case Canvas.FIRE: menu.fireHlButton();
if (curScreen.canMenuBeInteracted()){ menu.fireHlButton(); return; }
}
/*Otherwise, the event will be forwarded to be handled by the cur screen*/
curScreen.keyPressed (iKeyCode);
}
public void keyReleased (AnimationCanvas canvas, int iKeyCode){
/*Do nothing if cur screen is currently not ready to accept input events*/
if (! curScreen.isReadyToAcceptInputEvents()) return;
/*Otherwise, the event will be forwarded to be handled by the cur screen*/
curScreen.keyReleased (iKeyCode);
}
public void keyRepeated (AnimationCanvas canvas, int iKeyCode){
/*Do nothing if cur screen is currently not ready to accept input events*/
if (! curScreen.isReadyToAcceptInputEvents()) return;
/*Simply do the same thing as the key press event*/
bKeyRepeated = true;
keyPressed (canvas,iKeyCode);
bKeyRepeated = false;
}
public void componentEventFired (Component c, int iEventID,
Object paramObj, int iParam){
if (c == menu){ //If the event is from the menu, simply forward it to the
//corresponding callback of the current screen...
switch (iEventID){
case FishEyeMenu.EVENT_FISHEYESCROLLED:
curScreen.menuSelectionChanged (iParam);
break;
case FishEyeMenu.EVENT_FISHEYESHAKED:
curScreen.menuButtonFired (iParam);
break;
case FishEyeMenu.EVENT_ENTRYANIMENDED:
curScreen.menuEntryAnimEnded();
break;
case FishEyeMenu.EVENT_EXITANIMENDED:
curScreen.menuExitAnimEnded();
break;
};
};
}
}
OK, that’s about 200 lines of code. First to look at is the initialization code within the constructor:
TWUIKDisplay.init (this); btnFactory = new ButtonFactory();
Basically, before you can do anything with TWUIK, it is necessary to call TWUIKDisplay.init method first passing in the instance of your MIDlet. Following that, we then initialize our ButtonFactory which will create all the NeonButtons we will use in the application. Pretty straightward here. Next following the above code, we will find initialization code that creates and sets up the application-level components:
menu = new FishEyeMenu (1,0,0,10,90,fontSysPropSmallBold); menu.setEventListener (this); progressBar = new ProgressBar (1,0,0,10,20);
There are a couple things need to be explained here. First, every component, as we have discussed, have an associated animation tick count. This is usually the first parameter in the constructor of a component (although some components may decide to set a default value for this rather than take it as parameter from the client code). We set this value as 1 for both the FishEyeMenu and the ProgressBar. In most cases, you will use 1 unless you need optimization based on double-buffering (to be discussed in the last tutorial part) or when you want to slow down the animation of the component when there is no any other way else.
The API for the constructor of FishEyeMenu is as follow
public FishEyeMenu (int iAnimTickCount,
int iX, int iY, int iWidth, int iHeight,
TwuikFont labelfont)
This constructor will basically create a new FishEyeMenu object with the specified animation tick count, position, size, and label font (for the buttons). Note that font is abstracted as TwuikFont in TWUIK. It is basically an abstraction for SystemFont, BitmapMonoFont, BitmapPropFont, and VectorFont. In the listing, you can find examples of creating any of these fonts in the code where the MIDlet initializes a pool of fonts to be used throughout the application.
The API for the constructor of ProgressBar is also straightforward, which simply is used to create a new ProgressBar object with the specified position and size. By default the ProgressBar uses the MODE_INDICATORBAR mode. But we will reinitialize this everytime we add the progress bar onto the screen anyway.
So, let us look at the code for adding the FishEyeMenu onto a specified canvas (similar things are done for the progress bar as well, but obviously simpler). First is the reinitialization of its size and position:
menu.iX = 0; menu.iY = canvas.getHeight()-80; menu.setSize (canvas.getWidth(),80);
And then we reconfigure the layout positions based on the canvas size (there are five parameters for the x and y coordinates for each of the five buttons; we calculate relatively based on the values we have tried to look good on the screen of 176x220 size):
menu.setButtonLayoutPositions (-(int) (68.0f/176*canvas.getWidth()),35, -(int) (42.0f/176*canvas.getWidth()),35, 0, 35, (int) (42.0f/176*canvas.getWidth()),35, (int) (68.0f/176*canvas.getWidth()),35);
We then add the FishEyeMenu component onto the canvas:
canvas.addComponent (menu);
All the buttons specific to the current screen are then added and we also set default selected one
menu.removeAllButton();
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_PHOTO));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_AUDIO));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_VIDEO));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_SETTING));
menu.addButton (btnFactory.getNeonButton (ButtonFactory.ID_EXIT));
switch (iPrevScreenID){
default: menu.setCurSel (ButtonFactory.ID_PHOTO); break;
};
Finally, we then start the animation of the FishEyeMenu (initiating with its entry animation):
menu.resetStatus(); menu.startStopAnimation(); menu.initButtonEntryExitEffect (true);
Note that calling resetStatus is a requirement from FishEyeMenu which has to be called every time it is reconfigured.
Show screen code, for the main menu (the MainMenu class will be discussed later). Just remember that MainMenu is a subclass of MMCScreen, thus providing us the interface for getting the canvas as well as to activate it. Here is some code snippet in the Screen Controller’s gotoMainMenu method:
void gotoMainMenu(){
/*Creates a new MainMenu screen, and initializes screen variables*/
//...
/*Attach the menu onto the MainMenu screen canvas*/
addMenu (curCanvas);
/*Make the MainMenu screen canvas as the current one*/
curCanvas.setEventListener (this);
TWUIKDisplay.setCurrent (curCanvas);
mainMenu.activated();
//...
}
Note that after creating and setting up the screen, we register our Screen Controller as the canvas event listener object for the AnimationCanvas of the screen. After that we displays the screen and activates it. Note that displaying an AnimationCanvas onto the screen requires the use of TWUIKDisplay instead of Display as we have discussed earlier (in similar way using the setCurrent method but on the class rather than on an instance object, however).
Our Screen Controller will intercept all the input events (i.e., key events) and process them accordingly. The intercept is to process those input events used for controlling the application-level components (i.e. the FishEyeMenu most likely). Other input events will then be forwarded to the current MMCScreen. Note how we process the LEFT, RIGHT, and FIRE keys for controlling the FishEyeMenu in the following code in the keyPressed event (LEFT for navigating to previous button, RIGHT for navigating to next button, and FIRE for selecting the currently highlighted button):
switch (curCanvas.getGameActionNoAlt (iKeyCode)){
case Canvas.LEFT:
if (curScreen.canMenuBeInteracted()){ menu.selectPrevButton();return; }
case Canvas.RIGHT:
if (curScreen.canMenuBeInteracted()){ menu.selectNextButton();return; }
case Canvas.FIRE: menu.fireHlButton();
if (curScreen.canMenuBeInteracted()){ menu.fireHlButton();return; }
;
First we query the current MMCScreen for whether it is ready to be interacted for its menu or not, and then we process the corresponding action appropriately. Similar actions are done for the keyReleased and keyRepeated events.
Similarly, the Screen Controller also intercepts the component events and process them accordingly. Here we will only intercept the events that can be generated from our FishEyeMenu widget. Recall that when we create this component, we have registered the Screen Controller as its component event listener object, and so any events occurring on it will be notified to the Screen Controller. Following are events that can be generated by a FishEyeMenu:
- EVENT_SCROLLED: when changing the highlighted button (i.e. navigation).
- EVENT_SHAKED: when the currently highlighted button is selected/fired.
- EVENT_ENTRYANIMENDED: when the entry animation of the menu has ended. After this event the menu will go to activated state, for which it starts to process any key events.
- EVENT_EXITANIMENDED: when the exit animation of the menu has ended. After this event the menu will go to not-activated state, for which it is no longer able to receive any key events.
We intercept all the aforementioned events and forward the corresponding notification to the current MMCScreen accordingly. And thus we have the following code for implementing the componentEventFired callback:
public void componentEventFired (Component c, int iEventID,
Object paramObj, int iParam){
if (c == menu){
switch (iEventID){
case FishEyeMenu.EVENT_FISHEYESCROLLED:
curScreen.menuSelectionChanged (iParam);
break;
case FishEyeMenu.EVENT_FISHEYESHAKED:
curScreen.menuButtonFired (iParam);
break;
case FishEyeMenu.EVENT_ENTRYANIMENDED:
curScreen.menuEntryAnimEnded();
break;
case FishEyeMenu.EVENT_EXITANIMENDED:
curScreen.menuExitAnimEnded();
break;
};
};
}
That’s all about it for our little framework of screens with a controller. We are now ready to get started with completing the implementation of each screen then. We’ll start with the main menu screen first, which is explained in the next section.
Starting with the First Screen: Main Menu
Now we are going to look at the implementation for our first screen in the application, the main menu. In this screen we allow the user to choose either to view the photo gallery, to open the music player, to open the video player, to configure the settings/preferences, or to exit the application. This screen will be the screen where each of the other screens jumps to in order to allow to user to switch to another screen. Let us look at the UI design in further details first:

1. ImageBox
- showing the large icon version of the currently selected button
- after menu entry animation has ended, this icon drops from above the screen and then keeps jumping/bouncing indefinitely
- when it is time to switch to another screen, after the menu exit animation has ended, and as soon as this icon touches the “ground“ again, it will then fly upward beyond the screen
2. AnimationCanvas
- where we add the ImageBox and FishEyeMenu components onto
- the background is set to pure black solid color
3. FishEyeMenu
- black solid background color
- showing entry animation when screen first shown and exit animation when switching to another screen
- buttons include photo icon, audio player icon, video player icon, settings icon, and exit icon
For this screen, we are going to see how motion effects are used. TWUIK features visual FX engines which include transition FX, motion FX, and transformation FX. Each of these engines can be plugged onto any component freely. In this example, we will only see the motion FX engine in action. We will look at the other FX engines in the later parts of this tutorial.
First we have a large version of the currently selected menu icon around the centre of the screen, which keeps bouncing over and over. This icon is represented using an ImageBox, a built-in component widget in TWUIK. As for motion FX, we have one for when the icon drops from top of the screen (when the screen is just shown), another for when the icon keeps bouncing, and another for when the icon flies upward beyond the screen. Let us look at the whole code listing for the implementation of this screen first (MainMenu) and then the part-by-part explanation follows.
Listing 1.3. Code for MainMenu
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import com.tricastmedia.twuik.*;
import com.tricastmedia.twuik.widgets.*;
import com.tricastmedia.twuik.motionfxs.*;
import com.tricastmedia.twuik.extras.*;
/**
* A class to represent the MainMenu screen, encapsulating the canvas,
* components, as well as the actions or tasks within the MainMenu screen.
*/
class MainMenu extends MMCScreen implements MotionFXListener{
private static final int FRAMEDELAY = 1000 / 25;
//Controller-midlet relateds
private MMCMIDlet midlet;
//Components
private ImageBox ibMenuIcon;
//Working variables
private int iMenuIconJumpHeightEstimate;
private int iMenuIconYPos;
private MOFXLinear mofxMenuIconEntry;
private MOFXLinear mofxMenuIconJump;
private MOFXLinear mofxMenuIconExit;
private boolean bReadyForExitAnimation;
private boolean bMenuExitAnimationEnded;
public MainMenu (MMCMIDlet midlet){
/*Initialize canvas-relateds*/
this.midlet = midlet;
canvas = new AnimationCanvas (FRAMEDELAY);
canvas.setBgColor (0x000000);
canvas.startStopAnimation();
/*Initialize components*/
Image[] menuIconImgs = new Image[5];
menuIconImgs[0] = ImageUtil.loadImageFromResource (
"/mainmenu/photo_icon.png");
menuIconImgs[1] = ImageUtil.loadImageFromResource (
"/mainmenu/audio_icon.png");
menuIconImgs[2] = ImageUtil.loadImageFromResource (
"/mainmenu/video_icon.png");
menuIconImgs[3] = ImageUtil.loadImageFromResource (
"/mainmenu/setting_icon.png");
menuIconImgs[4] = ImageUtil.loadImageFromResource (
"/mainmenu/exit_icon.png");
ibMenuIcon = new ImageBox (1,false,0,-130,canvas.getWidth(),130,
menuIconImgs,null,null,0);
ibMenuIcon.goTo (0);
ibMenuIcon.setVisibleMode (false);
ibMenuIcon.startStopAnimation();
canvas.addComponent (ibMenuIcon);
/*Initialize working variables*/
bReadyToAcceptInputEvents = false;
bMenuCanBeInteracted = true;
bReadyForExitAnimation = false;
bMenuExitAnimationEnded = false;
iMenuIconJumpHeightEstimate = 30;
iMenuIconYPos = canvas.getHeight()-iMenuHeight-ibMenuIcon.getHeight()+15;
}
private void switchMenuIcon(){
String iconNameStr;
switch (midlet.menu.getCurSelID()){
case ButtonFactory.ID_PHOTO: ibMenuIcon.goTo (0); break;
case ButtonFactory.ID_AUDIO: ibMenuIcon.goTo (1); break;
case ButtonFactory.ID_VIDEO: ibMenuIcon.goTo (2); break;
case ButtonFactory.ID_SETTING: ibMenuIcon.goTo (3); break;
default: ibMenuIcon.goTo (4); break;
};
}
private void doExitAction(){
midlet.notifyDestroyed();
}
public boolean isReadyToAcceptInputEvents(){
return bReadyToAcceptInputEvents;
}
public boolean canMenuBeInteracted(){
return bMenuCanBeInteracted;}
public void activated(){
switchMenuIcon();
}
public void menuEntryAnimEnded(){
/*Start the entry motion effects on the menu icon*/
mofxMenuIconEntry = new MOFXLinear (1,MOFXLinear.MOFX_LINEAR_CACCEL);
mofxMenuIconEntry.setMotionFXListener (this);
mofxMenuIconEntry.reinit (ibMenuIcon.iX,-ibMenuIcon.getHeight(),
ibMenuIcon.iX,iMenuIconYPos,
20,0.0f,5.0f);
ibMenuIcon.motionFX = mofxMenuIconEntry;
ibMenuIcon.setVisibleMode (true);
}
public void menuExitAnimEnded(){
bMenuExitAnimationEnded = true;
if (mofxMenuIconExit == null) doExitAction(); //do the exit action if not
//yet done by the ending of
//the icon exit motion
}
public void menuButtonFired (int iButtonID){
bReadyForExitAnimation = true;
bReadyToAcceptInputEvents = false;
midlet.menu.initButtonEntryExitEffect (false);
}
public void menuSelectionChanged (int iButtonID){
switchMenuIcon();
}
public void keyPressed (int iKeyCode){}
public void keyReleased (int iKeyCode){}
public void keyRepeated (int iKeyCode){}
public void motionStarted (MotionFX mofx){
}
public void motionProgressed (MotionFX mofx, int iProgress){
}
public void motionFinished (MotionFX mofx){
if (mofx == mofxMenuIconEntry){ //when the icon entry motion has ended
mofxMenuIconEntry = null;
/*Start the repeated jump motion effects on the menu icon*/
mofxMenuIconJump = new MOFXLinear (1,MOFXLinear.MOFX_LINEAR_CACCEL);
mofxMenuIconJump.setMotionFXListener (this);
mofxMenuIconJump.reinit (ibMenuIcon.iX,ibMenuIcon.iY,
ibMenuIcon.iX,ibMenuIcon.iY,
iMenuIconJumpHeightEstimate,
0.0f,-canvas.getHeight()/220.0f*5.0f);
mofxMenuIconJump.enableRepeatMode (true);
ibMenuIcon.motionFX = mofxMenuIconJump;
bReadyToAcceptInputEvents = true;
} else if (mofx == mofxMenuIconJump){ //whenever icon jump motion has ended
if (bReadyForExitAnimation){
/*Start the exit motion effects on the menu icon*/
mofxMenuIconJump = null;
mofxMenuIconExit = new MOFXLinear (1,MOFXLinear.MOFX_LINEAR_CACCEL);
mofxMenuIconExit.setMotionFXListener (this);
mofxMenuIconExit.reinit (ibMenuIcon.iX,ibMenuIcon.iY,
ibMenuIcon.iX,-ibMenuIcon.getHeight(),
10,0.0f,-5.0f);
ibMenuIcon.motionFX = mofxMenuIconExit;
};
} else if (mofx == mofxMenuIconExit){ //when the icon exit motion has ended
mofxMenuIconExit = null;
if (bMenuExitAnimationEnded) doExitAction(); //do the exit action if not
//yet done by the ending of
//the menu exit animation
};
}
}
That’s it, and that’s about 200 lines of code again. First, let us look at the code (task by task) for the constructor which performs initializations such as creation of the canvas, creation of components, and initialization of working variables.
this.midlet = midlet; canvas = new AnimationCanvas (FRAMEDELAY); canvas.setBgColor (0x000000); canvas.startStopAnimation();
So, we first initialize all the canvas-related variables, such as getting the reference to the screen controller midlet, creating a new AnimationCanvas object, configures its background color, and then starts its animation (of course, by default, no components exits yet on this canvas, so nothing can be seen on it if it is displayed). Note how we create a new AnimationCanvas object. We pass in the screen update rate for this AnimationCanvas. FRAMEDELAY is our declared constant integer variable which is assigned the value of 1000 / 25. The parameter expects the update rate to be in milliseconds. We want 25 FPS (frame-per-second). So 1000 divided by 25 would then give us the millisecond equivalent.
Next we create our ImageBox widget to contain the large version of the menu icon. This is the key component that we later will add various motion FX engines onto it, thus greatly animating it. The API for the constructor of ImageBox is as follow:
public ImageBox (int iAnimTickCount, boolean bDoubleBuffered, int iX, int iY, int iWidth, int iHeight, Image[] imgs, TransitionFX forwardTSFX, TransitionFX backwardTSFX, long lTransitionDelay)
Again, all the first 6 parameters are common to any Component as we have discussed earlier. Specific to ImageBox are the last four parameters. The constructor basically expects an array of Image objects (yes, ImageBox supports a collection of Images which you can pick to show at anytime or even to play a slideshow of all of them). Then follow are parameters specifying the engine objects for both forward and backward transition effects. For this, we are just going to leave them to null since we will only use ImageBox for showing one Image at a time only and not a slideshow. The same goes for the last parameter, the transition delay, for which we will set to 0.
OK, let us analyze the code that creates and sets up our ImageBox then.
Image[] menuIconImgs = new Image[5]; menuIconImgs[0] = ImageUtil.loadImageFromResource ( "/mainmenu/photo_icon.png"); menuIconImgs[1] = ImageUtil.loadImageFromResource ( "/mainmenu/audio_icon.png"); menuIconImgs[2] = ImageUtil.loadImageFromResource ( "/mainmenu/video_icon.png"); menuIconImgs[3] = ImageUtil.loadImageFromResource ( "/mainmenu/setting_icon.png"); menuIconImgs[4] = ImageUtil.loadImageFromResource ( "/mainmenu/exit_icon.png"); ibMenuIcon = new ImageBox (1,false,0,-130,canvas.getWidth(),130, menuIconImgs,null,null,0); ibMenuIcon.goTo (0); ibMenuIcon.setVisibleMode (false); ibMenuIcon.startStopAnimation(); canvas.addComponent (ibMenuIcon);
First we load all the images to put into the Image array that is going to be passed to the constructor of ImageBox. We can actually load the each image only when the corresponding button is selected, but that would cause some delay. Of course, if we pre-load the images, we will consume more memory, but just focus on speed first for now. Nonetheless, there is a trade-off on whether to optimize speed or to optimize memory. We are not going to bother about this here.
The code then calls the constructor of ImageBox with all the necessary parameters. The goTo method is to select, from the specified Image list, the index of the Image that is to currently show on the ImageBox. Since we are not going to show the menu icon (i.e., the ImageBox) yet until the FishEye menu entry animation has ended, we initially set the visibility of the ImageBox to false, thus the following method call to setVisibleMode. Finally, we start its animation and add it onto the MainMenu’s AnimationCanvas. Following that, then we initialize some working variables which we will use/manipulate later. Essentially, the screen is initially not ready to accept input events yet and its menu cannot be interacted yet (we will later set these flags to true when every entry animation has ended).
We then write the code of a method called switchMenuIcon that is to be called when the menu button selection has been changed (and also called when the screen is first activated). The purpose of this method is to select the appropriate Image as the menu icon using the ImageBox’s goto method, which we have seen. Note in the Listing 1.3 that we just use a switch construct based on the value returned by the FishEyeMenu’s getCurSelID method which returns the ID of the button currently highlighted. Recall that we have a list of button IDs defined in our ButtonFactory which we use when we create each corresponding NeonButton. So based on this ID we then know which menu icon Image to use accordingly.
In our “Screens with a controller” framework, we have define that each screen extend from MMCScreen. Recall that we have the various notification callback methods that each screen needs to implement accordingly. So, now we are going to look at the implementation of each of these callbacks for the MainMenu.
In the activated callback, we simply call the switchMenuIcon method which we have implemented earlier in order to select the proper menu icon image according to the first selected menu button. Similarly, we make the same method call inside the menuSelectionChanged callback. As for the key input events callback, we don’t have any processing, and thus we leave their implementations empty.
Now, the fun part begins. It’s about making things to animate using various MotionFX engines! The FishEyeMenu itself has an internal MotionFX engine which it uses to perform entry animation and exit animation effects. It provides the API called initButtonEntryExitEffect that we can use to initiate either the entry or exit animation. We have seen this when we implement our screen controller MIDlet, in which we call this method passing true value (i.e., to indicate entry effect, rather than exit) when the MainMenu is to be shown. We thus carry out the following actions for the menuButtonFired callback:
bReadyForExitAnimation = true; bReadyToAcceptInputEvents = false; midlet.menu.initButtonEntryExitEffect (false);
Don’t worry about the bReadyForExitAnimation first, but basically we just set it to true so that later in our bouncing motion effects (after it touches the ground, it will then initiate the flying upward effects indicating the exit of the screen). The other flag assignment is to make sure that no input events is to be accepted anymore once the menu button has been fired (i.e., because we are exiting the screen). Then, we start the exit animation of the FishEyeMenu.
OK, now it’s about our custom motion FXs that we are going to add. First of all, every motion FX engine extends from the MotionFX abstract class, which essentially provides a basic framework and common APIs. The framework defines that any MotionFX engine can generate events and uses callback methods to notify them to the registered MotionFXListener. These events are:
- motionStarted – called when the MotionFX engine is just started
- motionProgressed – called whenever the MotionFX engine advances a step, informing the current progress (as percentage)
- motionFinished – called when the MotionFX engine has ended its animation
There are various motion FX engine types available in TWUIK such as Uni-Directional Motion (Constant-Speed, Constant-Acceleration, Variable-Acceleration, and Pullback), Circular Motion (Constant Angular Speed, Constant-Acceleration, and Variable-Acceleration) and Shake Motion. For Constant-Speed Uni-Directional Motion and Shake Motion, we have used them implicitly as we use the FishEyeMenu (which use these FX types to implement its entry and exit animation).
For our custom FX engines, we are only going to use the Constant-Acceleration Uni-Directional Motion (but nevertheless, use it to perform different kinds of behaviour: dropping, indefinite-bouncing, and then flying). For this motion type, we use the MOFXLinear class and its LINEAR_CACCEL constant. Let’s look at the implementation of the dropping effects first, which we initiate soon after the FishEyeMenu entry animation has ended (inside the menuEntryAnimEnded callback):
mofxMenuIconEntry = new MOFXLinear (1,MOFXLinear.MOFX_LINEAR_CACCEL); mofxMenuIconEntry.setMotionFXListener (this); mofxMenuIconEntry.reinit (ibMenuIcon.iX,-ibMenuIcon.getHeight(), ibMenuIcon.iX,iMenuIconYPos, 20,0.0f,5.0f); ibMenuIcon.motionFX = mofxMenuIconEntry; ibMenuIcon.setVisibleMode (true);
That is pretty much what we need to do to sets up the MOFXLinear and plugs in onto our ImageBox component. First, we construct the MOFXLinear object specifying the LINEAR_CACCEL type, and then registers the MainMenu as the MotionFXListener. Then we call reinit to configures the starting and ending position for the motion, as well as some control parameters. The API looks like this:
public void reinit(int startPosX, int startPosY, int endPosX, int endPosY, int nTotalStep, float startSpeedX, float startSpeedY)
So, besides the positioning parameters, we need to specify the number of steps for the animation (and that indicates the number of times the motionProgressed event is reported), the starting horizontal speed, and the starting vertical speed. What we want to achieve is an effect where the icon drop from the top of the screen down to the centre of the screen. So we set the starting position at beyond the top of the screen by the height of the icon and the ending position at the centre of the screen. Since it is only a horizontal motion that we want, the difference between the starting and ending position is thus only on the y-coordinate. Similarly, we don’t have any starting horizontal speed, but the starting vertical speed is set to be a factor of 5.0. The speeds and number of steps are all adjusted based on trying out the behaviour of the effects during the application run. Note that pluggin in a MotionFX engine onto a component is simply by setting its member variable motionFX to the particular MotionFX object that we have created and set up.
There we go, we have our first motion FX enabled. Next is to start the indefinite-bouncing effects when the dropping effects has ended. For this, we catch the motionFinished event for the mofxMenuIconEntry (our dropping MotionFX engine) and then creates and sets up the indefinite-bouncing effects as such:
mofxMenuIconJump = new MOFXLinear (1,MOFXLinear.MOFX_LINEAR_CACCEL);
mofxMenuIconJump.setMotionFXListener (this);
mofxMenuIconJump.reinit (ibMenuIcon.iX,ibMenuIcon.iY,
ibMenuIcon.iX,ibMenuIcon.iY,
iMenuIconJumpHeightEstimate,
0.0f,-canvas.getHeight()/220.0f*5.0f);
mofxMenuIconJump.enableRepeatMode (true);
ibMenuIcon.motionFX = mofxMenuIconJump;
The bouncing effect can be achieved using the MOFXLinear with LINEAR_CACCEL type again. But this time, the starting and ending position is the same, but with negative starting vertical speed. The indefinite animation of the motion FX can be achieved by calling the enableRepeatMode API method passing a true value as the parameter. Again, we register the MainMenu as the MotionFXListener to find out whether it has complete each cycle or not, for which, if it has, we will then starts the flying effects when it is time to exit the screen (which we check using the bReadyForExitAnimation flag that we have seen earlier where we set to true whenever the menu button has been fired. Thus, we have the following code within the motionFinished event of the bouncing effects engine:
if (bReadyForExitAnimation){
mofxMenuIconExit = new MOFXLinear (1,MOFXLinear.MOFX_LINEAR_CACCEL);
mofxMenuIconExit.setMotionFXListener (this);
mofxMenuIconExit.reinit (ibMenuIcon.iX,ibMenuIcon.iY,
ibMenuIcon.iX,-ibMenuIcon.getHeight(),
10,0.0f,-5.0f);
ibMenuIcon.motionFX = mofxMenuIconExit;
};
The flying effect, as we can see, is basically the reverse of our earlier dropping effect. So instead of going down, it goes up (and thus the negative value for the starting vertical speed).
Finally, we synchronize the ending of both the FishEyeMenu exit animation and the icon flying effect using bMenuExitAnimationEnded and mofxMenuIconExit as flags to indicate whether either of them has ended, respectively. So, we can find the following code within the motionFinished event of the flying effects engine:
mofxMenuIconExit = null; if (bMenuExitAnimationEnded) doExitAction();
And the following code within the menuExitAnimEnded notification callback from the screen controller:
bMenuExitAnimationEnded = true; if (mofxMenuIconExit == null) doExitAction();
The method doExitAction is just our method to carry out the corresponding exit action appropriately (which, for now it does nothing specific yet but only to quit the application). There we go, are finally done with the implementation of our first screen, the main menu. Pretty straightforward isn’t it?! Below is a screenshot of the main menu screen of our media centre application running on the emulator.
I hope the explanation on the implementation of this screen has taught you how to use the great motion FX engines in TWUIK. We will see more advanced stuff in later tutorial parts. The next part will discuss the implementation of the photo gallery screen, which shows how to use the PhotoPreviewGallery widget, the transformation FX engines, and the transition FX engines. So, see you soon!
For enquiry about TWUIK, please email us at (sales@tricastmedia.com)








