Animations and transitions are two very interesting features of the Lightweight User Interface Toolkit (LWUIT). In this article we will see how to use these features in actual applications. We also develop a custom transition animation. Although very simple, this transition demonstrates fully the process of such customization.
I have used the Sprint Wireless Toolkit 3.3.1 (Sprint WTK) for the screenshots and also to build the CustomTransitionDemo
. I have also referred to the source code for the LWUIT Demo application that comes with the LWUIT download bundle to illustrate the usage of the animation and transition capabilities of LWUIT. For a proper understanding of this article, the toolkit and the LWUIT bundle should be downloaded and installed on your computer. Version 3.3.2 of the Sprint WTK has been released; I have tried it out and found that there is no problem in running our demo on the new version.
It was possible to animate images on the Java ME platform even before LWUIT appeared on the scene. There are a number of features in the javax.microedition.lcdui.game
package that support animation. In addition, the JSR 226
API provides support for SVG-based animation. LWUIT handles animation a little differently, providing support for a capability that is broad-based and simple to use. In the context of LWUIT, animation essentially allows objects to paint succeeding frames at timed intervals. Animation support in LWUIT also makes it easy for transitions to be implemented.
To be capable of being animated, an object has to implement the Animation
interface. Animation
has two methods.
animate
: This is a callback method invoked once for every frame. paint
: This method is called if animate
returns true
. If the object being animated is a component, it will have a paint
method and that is the one to be called as the signature of the two methods is identical. The Component
class extends Animation
. This makes every widget animation-capable. However, in order to actually carry out animation, an object must also be registered for animation by calling the registerAnimated
method on its parent form. The animate
method of a registered object is called once for every frame of animation as determined by the Display
class. If animate
returns true
, the paint
method of the object is called and a repaint takes place. To stop animation, the object must be deregistered by calling the deregisterAnimated
method on the parent form.
To see how an animation works, let us look at the AnimationDemo
class of the LWUITDemo application that comes with the Sprint Toolkit and also with the LWUIT download bundle. The screenshots below show snapshots of two different frames of the animation demo.
Figure 1. Two frames of the animation demo
The demo shows two examples of animation: one uses an animated image of Duke and the other repeatedly draws a set of images to simulate motion. In both cases, the base component that is animated is a button
. Other components can also be animated in a similar way. You can replace the buttons in the code for AnimationDemo
with labels and the demo will still work fine. Just remember to import the Label
class.
In the execute
method of AnimationDemo
, the first button -- animation -- is instantiated and added to the form (f
), which has been passed as a parameter to the method:
Button animation = new Button(UIDemoMIDlet.
getResource("duke").getAnimation("duke3_1.gif"));
animation.setBorderPainted(false);
animation.getStyle().setBgTransparency(0);
f.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
f.addComponent(animation);
The second and third lines are for visual effects only and the fourth sets the layout manager for the form. The interesting thing to note here is that the button has not been registered for animation. This is because the image itself is animated and does not require repainting. The image of Duke used here is an animated gif. At this time LWUIT supports only animated gif format; as a matter of fact, the Resource Editor does not recognize any other animated format.
For the second button, the image array is set up for the paint method:
Resources images = UIDemoMIDlet.getResource("images");
Image a = images.getImage("progress0.png");
Image b = images.getImage("progress1.png");
Image c = images.getImage("progress2.png");
final Image[] secondAnimation = new Image[] {
a,
b,
c,
a.rotate(90),
b.rotate(90),
c.rotate(90),
a.rotate(180),
b.rotate(180),
c.rotate(180),
a.rotate(270),
b.rotate(270),
c.rotate(270),
};
We now have a set twelve images that are to be drawn sequentially. The code for creating the second button looks like this:
Button animation2 = new Button() {
private int currentImage = 0;
private long lastInvoke;
public boolean animate() {
long current = System.currentTimeMillis();
if (current - lastInvoke > 50) {
lastInvoke = current;
currentImage++;
if (currentImage == secondAnimation.length) {
currentImage = 0;
}
return true;
}
return false;
}
public void paint(Graphics g) {
g.drawImage(secondAnimation[currentImage],
getX(), getY());
}
};
Here we have a button that implements both the animate
and paint
methods. The animate
method will be called at every frame interval. If more than 50 milliseconds have elapsed since the last call, animate
will increment the pointer to the image array and return true
; otherwise, it will just return false
. If true
is returned, then the paint method will be called and the appropriate image will be drawn.
The rest of the code for the second button is mainly for setting the visual parameters and for adding the button to the form. Note the last line of code, which shows that the button has been registered for animation. This ensures that the animate
method will be called for every frame.
animation2.setPreferredSize(new Dimension
(secondAnimation[0].getWidth(),
secondAnimation[0].getHeight()));
animation2.setBorderPainted(false);
animation2.getStyle().setBgTransparency(0);
f.addComponent(animation2);
f.registerAnimated(animation2);
And that is all there is to applying animation!
The term transition refers to the way a form is brought into (animate in transition) and taken out of (animate out transition) display. The base class for implementing transition is Transition
, which implements Animation
. This is why transitions work basically as animations and utilize the same callback mechanism as any other animated component. However, Transition
is different from Component
in the sense that a Component
animates its own rendering while Transition
controls the animated rendering of forms and dialogs.
There is also a difference between the ways in which component animations and transitions are used. To use transitions, it is not necessary to register the form concerned for animation. When a form transition occurs, the Display
object directly places the form into the queue for receiving callbacks.
The Transition
class is an abstract one and cannot be instantiated. The LWUIT library includes two concrete subclasses of Transition
that can be used to apply transitions. These classes are:
CommonTransitions
Transition3D
CommonTransitions
currently includes two types of transition animations.
Slide
: The new form slides in, pushing out the current one. Fade
: The new form fades in, while the current one fades out. CommonTransitions
works with a supporting class -- Motion
-- that provides the models for simulating physical motion. The three types of motions which Motion
contains are:
Friction
Spline
Linear
The Transition3D
class works with the M3G API (JSR 184) to provide transition animations with 3D effects. JSR 184 support is necessary for these transitions to work properly, and so the Motion
class is not required for Transition3D
. A current limitation of this class is that its transitions work only with forms, but that is likely to change soon . The 3D suite of transitions contains the following.
Cube
: Simulates a rotating cube, with the current form on the face rotating off the screen and the new one on the face rotating into the screen. Fly In
: The new form flies in. Rotation
: A two-dimensional version of Cube
in which a plane, rather than a cube, rotates. Static Rotation
: Applicable to a transition from a form into a dialog. Only the dialog rotates while the form remains static. Swing In
: The incoming form swings into place either from top to bottom or from bottom to top. Transitions are very easy to use, as we can see from the following code snippets (from the TransitionDemo
class of the LWUIT demo application), which are examples of how to create transitions by using the applicable factory methods:
//creates an out transition
//with a right-to-left outgoing slide
out = CommonTransitions.createSlide(
CommonTransitions.SLIDE_HORIZONTAL, false, runSpeed);
//creates an out transition
//with a left-to-right incoming slide
in = CommonTransitions.createSlide(
CommonTransitions.SLIDE_HORIZONTAL, true, runSpeed);
.
.
.
//creates outgoing and incoming fade transitions
out = CommonTransitions.createFade(runSpeed);
in = CommonTransitions.createFade(runSpeed);
.
.
.
//creates outgoing and incoming fly in transitions
out = Transition3D.createFlyIn(runSpeed);
in = Transition3D.createFlyIn(runSpeed);
You can see that there is no difference in the way a transition is created, regardless of whether it is a common transition or a 3D transition. After creating a transition, it has to be installed for the desired form. This too is simple:
//f is the form for which transitions are being set
f.setTransitionOutAnimator(out);
f.setTransitionInAnimator(in);
Note that if you set the outgoing transition for a form it is not necessary to set the incoming transition for the new form that is entering the screen; as the old form goes out it is automatically replaced by the new one. When an application has a number of forms, I find it a good practice to set only the outgoing transitions for all the forms. This way I avoid conflicts and missing transitions.
One of the great things about LWUIT is that it supports a good deal of customization. So, if you want a transition that is not available in the transition classes that come with the library, you can easily write your own. In this section we develop a simple transition to demonstrate the basic techniques for creating custom transitions. Before we start on our project, though, I would like to add a caveat here. The new transition is only a tool for understanding how transitions and motions work. To keep it simple, I have imposed certain restrictions that make this class unsuitable for general use. Some of these are:
Our transition is called Step Transition . As its name suggests, it makes the destination screen move into position in a series of discrete steps, unlike Slide Transition
. Another difference between the two transitions is that with step transition, the incoming screen does not appear to push the source screen out -- it moves over the source, gradually covering it.
To create a custom transition, we need to write a class that extends Transition
. In this case the class is StepTransition
. We also need a model for the appropriate motion and, for our demo, that is the StepMotion
class.
We start by looking at StepTransition
class. The Transition
class has the following abstract methods that have to be implemented by StepTransition
:
In addition, there is the empty initTransition
method that will have to be suitably implemented. Depending on the nature of the transition, this method may not be required at all, in which case you may omit it altogether.
The initTransition
method is a callback that is invoked at the beginning of each transition. So this is the place to initialize all parameters for starting the transition. We also instantiate a StepMotion
object, which will work out the position for rendering the destination screen (remember this is the only screen that moves) for each frame. Our code for initTransition
is pretty simple:
public void initTransition()
{
//initialize variables
//required to set up motion
Component source = getSource();
int width = source.getWidth();
position = 0;
int startoffset = 0;
//create a StepMotion object for this transition
motion = new StepMotion(startoffset,
width, duration, step);
//start the movement for the transition
motion.start();
}
Thanks to the simplicity of our specifications, we need to do just a few things to initialize the transition. What we do is initialize the variables required to create the motion object, create the motion, and start it running.
The animate
method is called once for every frame. This method checks to see if the motion object is missing or whether the transition is over. The check for completion of transition involves calling the isFinished
method of StepMotion
, which returns true
if the transition is done. If there there is no motion object or if the transition is complete, false
is returned so that this transition activity is terminated. Otherwise, the transition will continue and the position for drawing the destination screen corresponding to the next frame is obtained. Finally, animate
returns true
so the next frame can be drawn:
public boolean animate()
{
//see if there is a motion object
//and check if the transition is completed
if(motion == null || motion.isFinished())
{
//in either case terminate transition
return false;
}
//if transition is to continue
//get the new position
position = motion.getStep();
//continue with transition
return true;
}
Since StepTransition
implements Animation
, it has a paint
method that is invoked to refresh the destination screen for each frame. As we see in the code segment above, the position for drawing the destination screen is returned by the getStep
method of the StepMotion
class. Since the value of position changes only in discrete steps, it may remain unchanged over a number of frames. Drawing the screen(s) for every frame would be a waste of CPU time. To avoid this, getStep
returns -1 if the position has remained unchanged since the previous frame. The paint
method checks the value of position and calls paintSlideAtPosition
to do the actual painting only when necessary. The code for the paint
method is given below:
public void paint(Graphics g)
{
//paint only if new position is available
if(position > -1)
{
paintSlideAtPosition(g, position, 0);
return;
}
else
{
return;
}
}
The actual rendering is done by the paintComponent
method of the Component
class after the graphics context has been prepared by the following methods:
private void paintSlideAtPosition(Graphics g, int slideX, int slideY)
private void paint(Graphics g, Component cmp, int x, int y)
For our simple application, we could have combined all the paint methods into one. However, I have retained the structure of CommonTransitions
class that comes with the library. I hope this will make it easy to follow the method sequence for anyone who wishes to study the source code.
The paintSlideAtPosition
method first checks whether the first form of the application is being displayed and, in that case, inhibits transition. Otherwise it sets up the clip region and, more importantly for this application, the co-ordinates for translation so that proper shifting of the destination screen can occur. The next method takes care of the actual translation, calls the paintComponent
on Component
to draw the destination screen at the right position, and restores the original translation. The demo, as it stands, causes the destination screen to move over a stationary source. This happens because only the destination screen is repainted for each new position. If you want to achieve a push effect, just uncomment the two bold lines of code in the paintSlideAtPosition
method. This will cause the source screen also to be repainted and you will see it moving out of the display area as the destination screen moves in. The two methods are shown below:
private void paintSlideAtPosition(
Graphics g, int slideX, int slideY)
{
Component source = getSource();
//if this is the first form being displayed we
//can't do a step transition
//since we have no source form
if (source == null)
{
return;
}
Component dest = getDestination();
int w = -source.getWidth();
int h = 0;
//set the clips
//uncomment the two following lines
//for the "push" effect
//g.setClip(source.getAbsoluteX(),
source.getAbsoluteY(), source.getWidth(),
source.getHeight());
//paint(g, source, slideX , slideY );
g.clipRect(dest.getAbsoluteX(),
dest.getAbsoluteY(), source.getWidth(),
source.getHeight());
paint(g, dest, slideX + w, slideY + h);
}
private void paint(Graphics g, Component cmp, int x, int y)
{
//get original clip details
int cx = g.getClipX();
int cy = g.getClipY();
int cw = g.getClipWidth();
int ch = g.getClipHeight();
//translate to proper position
//for drawing the form
g.translate(x, y);
//get it painted
cmp.paintComponent(g, false);
//restore original values
g.translate(-x, -y);
g.setClip(cx, cy, cw, ch);
}
The copy
method just returns a copy of the step transition object. Note that a copy is returned and not the object itself, as the Display
class wants a copy to work with.
//return a functionally equivalent transition object
public Transition copy()
{
return new StepTransition(duration, step);
}
The constructor of StepMotion
takes four integers as parameters.
sourcevalue
: This is the starting position. If the new screen starts moving in from the left edge, then this will be zero. destinationvalue
: This is the finishing position. If the new screen should stop at the right edge, then this will be equal to the width of the display area. duration
: The time period (in milliseconds) over which the transition should last. steps
: The number of steps the transition should take. The constructor calculates the value by which the position of the destination screen has to be incremented from one step to the next. It also calculates the minimum time that has to elapse before the screen can be refreshed.
public StepMotion(int sourcevalue, int destinationvalue,
int duration, int steps)
{
this.destinationvalue = destinationvalue;
this.duration = duration;
this.steps = steps;
//the size of a step
stepsize = (destinationvalue - sourcevalue)/steps;
//the time interval between two successive steps
interval = duration/steps;
}
We have already met the three methods of StepMotion
. The only point to note here is that calculated step size may not be a factor of the total distance (destinationvalue - sourcevalue)
to be covered as we are using integer math. In such a case, the getStep
method will ensure that missing distance is made up as it always takes one step more than calculated number of steps. This is because isFinished
returns true only when the number of steps taken (stepnumber
) exceeds the required number of steps. This extra step doesn't really cause any problem, as the position is not allowed to exceed destinationvalue . The methods are:
//save the time as the beginning of an interval
public void start()
{
starttime = System.currentTimeMillis();
}
//return true if all steps have been taken care of
public boolean isFinished()
{
return stepnumber > steps;
}
//return the position
public int getStep()
{
//check if interval for next step is over
//if so increment stepnumber
//and return (stepsize*stepnumber)
//also reset starttime by calling start()
//if (stepsize*stepnumber>destinationvalue)
//then return destinationValue
//if interval not over return -1
if(System.currentTimeMillis() >=
starttime + interval)
{
stepnumber++;
int offset = stepnumber*stepsize;
if(offset > destinationvalue)
{
offset = destinationvalue;
}
start();
return offset;
}
else
{
return -1;
}
}
The MIDlet that puts it all together is also quite straightforward. After calling init
on Display
, it initializes the parameters for transition and sets up the theme . It then creates two labels for the two forms that will be used in the demo. We would like the the two forms to look somewhat different so that the transition becomes visually prominent. So the first form is created and its titlebar is made different from the theme setting. The label srclabel is added to the first form, and so are the commands. The MIDlet is then set as the command listener for the form. Finally the first form is made visible:
//init the LWUIT Display
Display.init(this);
//duration of transition in milliseconds
int duration = 3000;
//number of steps in transition
int numofsteps = 10;
//get the theme and set it
try
{
Resources r = Resources.open("/SDTheme1.res");
UIManager.getInstance().setThemeProps(
r.getTheme("SDTheme1"));
}
catch (java.io.IOException e)
{
}
//create two labels
Label srclabel = new Label("This is the source screen");
Label dstlabel = new Label(
"This is the destination screen");
//create the first form
first = new Form("Source");
//we make the titlebar different
//from the theme setting
first.getTitleStyle().setBgImage(null);
//add srclabel to the first form
first.addComponent(srclabel);
//add 'Next' command to first form
//the command id is 1
first.addCommand(new Command("Next", 1));
//add 'Exit' command to first form
//the command id is 0
first.addCommand(new Command("Exit", 0));
//this MIDlet is the listener
//for the first form's commands
first.setCommandListener(this);
//show the first form
first.show();
The next part of the code, which deals with the second form, is almost the same. The fundamental difference is that in and out transitions are set for this form. As this demo has only two forms, I have violated my own recommendation and set both transitions for the same form.
//create the second form
second = new Form("Destination");
//set the Out and In transitions for the second form
second.setTransitionOutAnimator(new StepTransition
(duration, numofsteps));
second.setTransitionInAnimator(new StepTransition
(duration, numofsteps));
//set Menu and background images for second form
try
{
second.getMenuStyle().setBgImage(Image.createImage
("/pattern3.png"));
}
catch(java.io.IOException ioe)
{
}
try
{
second.getStyle().setBgImage(Image.createImage
("/sdsym4.png"));
}
catch(java.io.IOException ioe)
{
}
//add dstlabel to second form
second.addComponent(dstlabel);
//add 'Previous' command to first form
//the command id is 2
second.addCommand(new Command("Previous", 2));
//this MIDlet is the listener
//for the second form's commands
second.setCommandListener(this);
All the above action takes place in the startApp
method. Now we take care of the commands and we're done:
public void actionPerformed(ActionEvent ae)
{
Command cmd = ae.getCommand();
switch (cmd.getId())
{
//'Exit' command
case 0:
notifyDestroyed();
break;
//'Next' command
//show second form
case 1:
second.show();
break;
//'Previous' command
//show first form
case 2:
first.show();
}
}
The three screenshots in Figures 2 through 4 show progressive images of the step transition from the first form to the second.
Figure 2. Step number 2 of transition
Figure 3. Step number 5 of transition
Figure 4. Step number 8 of transition
We have seen how to use the animation and transition functions of LWUIT and how to create our own transition. LWUIT is an evolving library and we are bound to see additions to its current repertoire. Developers will have to constantly update their knowledge to remain abreast of the new functions, capabilities, and refinements. It's going to need some hard work, but it's going to be great fun too!