在SUN的技术文章里面有一篇文章讲述了如何基于MIDP1.0实现动画,个人觉得很不错。在MIDP1.0中并为直接对动画提供支持,如果我们明白动画的原理,熟悉Timer和TimerTask的话实现动画并非很困难的事情。
动画实际上就是一系列连续的帧,当他们变换的足够快的时候,我们人的眼睛就会觉得他是运动的。所以如果我们能够周期性的切换画面,从第一帧到第二帧到第三帧 ......那么我们就可以制作出动画的效果了。Timer和TimerTask正好可以很好的帮我们完成这一功能,如果你还不熟悉这两个类可以参考使用Java中的Timer和TimerTask。我们首先扩展TimerTask实现AnimatedImage类,它的功能就是在特定位置画图
import java.util.*; import javax.microedition.lcdui.*;
// Defines an animated image, which is just a set // of images of equal size which are drawn in turn // to simulate movement.
public class AnimatedImage extends TimerTask { private Canvas canvas; private Image[] images; private int[][] clipList; private int current; private int x; private int y; private int w; private int h;
// Construct an animation with no canvas.
public AnimatedImage(Image[] images) { this(null, images, null); }
// Construct an animation with a null clip list.
public AnimatedImage(Canvas canvas, Image[] images) { this(canvas, images, null); }
// Construct an animation. The canvas can be null, but // if not null then a repaint will be triggered on it // each time the image changes due to a timer event. // If a clip list is specified, the image is drawn // multiple times, each time with a different clip // rectangle, to simulate transparent parts.
public AnimatedImage(Canvas canvas, Image[] images, int[][] clipList) { this.canvas = canvas; this.images = images; this.clipList = clipList;
if (images != null && clipList != null) { if (clipList.length < images.length) { throw new IllegalArgumentException(); } }
if (images != null && images.length > 0) { w = images[0].getWidth(); h = images[0].getHeight(); } }
// Move to the next frame, wrapping if necessary.
public void advance(boolean repaint) { if (++current >= images.length) { current = 0; }
if (repaint && canvas != null && canvas.isShown()) { canvas.repaint(x, y, w, h); canvas.serviceRepaints(); } }
// Draw the current image in the animation. If // no clip list, just a simple copy, otherwise // set the clipping rectangle accordingly and // draw the image multiple times.
public void draw(Graphics g) { if (w == 0 || h == 0) return;
int which = current;
if (clipList == null || clipList[which] == null) { g.drawImage(images[which], x, y, g.TOP | g.LEFT); } else { int cx = g.getClipX(); int cy = g.getClipY(); int cw = g.getClipWidth(); int ch = g.getClipHeight();
int[] list = clipList[which];
for (int i = 0; i + 3 <= list.length; i += 4) { g.setClip(x + list[0], y + list[1], list[2], list[3]); g.drawImage(images[which], x, y, g.TOP | g.LEFT); }
g.setClip(cx, cy, cw, ch); } }
// Moves the animation's top left corner.
public void move(int x, int y) { this.x = x; this.y = y; }
// Invoked by the timer. Advances to the next frame // and causes a repaint if a canvas is specified.
public void run() { if (w == 0 || h == 0) return;
advance(true); } }
我们需要预先在jar文件里面存储几个大小一样,形态有序的图片,在MIDlet启动的时候把它装载在一个Image数组,并把它作为参数传递给AnimatedImage。 private Image[] loadFrames( String name, int frames ) throws IOException { Image[] images = new Image[frames]; for( int i = 0; i < frames; ++i ){ images[i] = Image.createImage( name + i + ".png" ); }
return images; }
Image[] frames = loadFrames( "/images/bird", 7 ); AnimatedImage ai = new AnimatedImage( frames ); ai.move( 20, 20 ); // set top-left to 20,20
下面是两个测试效果的AnimatedCanvas和AnimationTest类的源代码,在这里你可以得到全部的源文件和图片资源。仔细读读这几个类是很有好处的。有一点值得一提的是,在绘制图片的时候应该判断设备是否支持双缓冲,如果不支持要使用双缓冲技术避免画面闪烁。而且我们也不适宜把动画图片作的过大,这样会增大jar文件的大小,同时会给设备造成很大的负担。
protected void paint(Graphics g) { Graphics saved = g;
if (offscreen != null) { g = offscreen.getGraphics(); }
g.setColor(255, 255, 255); g.fillRect(0, 0, getWidth(), getHeight());
int n = images.size(); for (int i = 0; i < n; ++i) { AnimatedImage img = (AnimatedImage) images.elementAt(i); img.draw(g); }
if (g != saved) { saved.drawImage(offscreen, 0, 0, Graphics.LEFT | Graphics.TOP); } }
import java.io.*; import java.util.*; import javax.microedition.lcdui.*; import javax.microedition.midlet.*;
// MIDlet that displays some simple animations. // Displays a series of birds on the screen and // animates them at different (random) rates.
public class AnimationTest extends MIDlet implements CommandListener {
private static final int BIRD_FRAMES = 7; private static final int NUM_BIRDS = 5;
private Display display; private Timer timer = new Timer(); private AnimatedImage[] birds; private Random random = new Random();
public static final Command exitCommand = new Command("Exit", Command.EXIT, 1);
public AnimationTest() { }
public void commandAction(Command c, Displayable d) { if (c == exitCommand) { exitMIDlet(); } }
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { exitMIDlet(); }
public void exitMIDlet() { timer.cancel(); // turn it off... notifyDestroyed(); }
// Generate a non-negative random number...
private int genRandom(int upper) { return (Math.abs(random.nextInt()) % upper); }
public Display getDisplay() { return display; }
// Initialize things by creating the canvas and then // creating a series of birds that are moved to // random locations on the canvas and attached to // a timer for scheduling.
protected void initMIDlet() { try { AnimatedCanvas c = new AnimatedCanvas(getDisplay()); Image[] images = loadFrames("/images/bird", BIRD_FRAMES);
int w = c.getWidth(); int h = c.getHeight();
birds = new AnimatedImage[NUM_BIRDS]; for (int i = 0; i < NUM_BIRDS; ++i) { AnimatedImage b = new AnimatedImage(c, images); birds[i] = b; b.move(genRandom(w), genRandom(h)); c.add(b); timer.schedule(b, genRandom(1000), genRandom(400)); }
c.addCommand(exitCommand); c.setCommandListener(this);
getDisplay().setCurrent(c); } catch (IOException e) { System.out.println("Could not load images"); exitMIDlet(); } }
// Load the bird animation, which is stored as a series // of PNG files in the MIDlet suite.
private Image[] loadFrames(String name, int frames) throws IOException { Image[] images = new Image[frames]; for (int i = 0; i < frames; ++i) { images[i] = Image.createImage(name + i + ".png"); }
return images; }
protected void pauseApp() { }
protected void startApp() throws MIDletStateChangeException { if (display == null) { display = Display.getDisplay(this); initMIDlet(); } } }
import java.util.*; import javax.microedition.lcdui.*;
// A canvas to which you can attach one or more // animated images. When the canvas is painted, // it cycles through the animated images and asks // them to paint their current image.
public class AnimatedCanvas extends Canvas { private Display display; private Image offscreen; private Vector images = new Vector();
public AnimatedCanvas(Display display) { this.display = display;
// If the canvas is not double buffered by the // system, do it ourselves...
if (!isDoubleBuffered()) { offscreen = Image.createImage(getWidth(), getHeight()); } }
// Add an animated image to the list.
public void add(AnimatedImage image) { images.addElement(image); }
// Paint the canvas by erasing the screen and then // painting each animated image in turn. Double // buffering is used to reduce flicker.
protected void paint(Graphics g) { Graphics saved = g;
if (offscreen != null) { g = offscreen.getGraphics(); }
g.setColor(255, 255, 255); g.fillRect(0, 0, getWidth(), getHeight());
int n = images.size(); for (int i = 0; i < n; ++i) { AnimatedImage img = (AnimatedImage) images.elementAt(i); img.draw(g); }
if (g != saved) { saved.drawImage(offscreen, 0, 0, Graphics.LEFT | Graphics.TOP); } } }

|