用J2ME在移動(dòng)設(shè)備上完成動(dòng)畫
發(fā)表時(shí)間:2024-02-13 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]使用MIDP(Mobile Information Device Profile)的開發(fā)人員經(jīng)常會(huì)抱怨用些什么辦法才可以在一個(gè)MIDlet上顯示動(dòng)畫。MIDP 1.0 沒有直接提供對(duì)動(dòng)畫的支持(MIDP 2.0支持),但真要是自己去實(shí)現(xiàn),其實(shí)也并非是一件很難的事。 任何動(dòng)畫的最基本的前提,是要...
使用MIDP(Mobile Information Device Profile)的開發(fā)人員經(jīng)常會(huì)抱怨用些什么辦法才可以在一個(gè)MIDlet上顯示動(dòng)畫。MIDP 1.0 沒有直接提供對(duì)動(dòng)畫的支持(MIDP 2.0支持),但真要是自己去實(shí)現(xiàn),其實(shí)也并非是一件很難的事。
任何動(dòng)畫的最基本的前提,是要在足夠快的時(shí)間內(nèi)顯示和更換一張張的圖片,讓人的眼睛看到動(dòng)的畫面效果。圖片必須按照順序畫出來。從一張圖片到下一張圖片之間的變化越小,效果會(huì)越好。
首先要做的,是使用你的圖片處理軟件(比如ps或者firework)創(chuàng)建一系列相同大小的圖片來組成動(dòng)畫。每張圖片代表動(dòng)畫一幀。你需要制作一定數(shù)量的禎--越多的幀會(huì)讓你的動(dòng)畫看上去越平滑。制作好的圖片一定要保存成PNG(Portable Network Graphics)格式,MIDP唯一支持的圖片格式。
有兩個(gè)辦法讓你剛做好的圖片在MIDlet上變成動(dòng)畫。第一,把圖片都放到一個(gè)web服務(wù)器上,讓MIDlet下載他們,MIDP內(nèi)置的HTTP支持。第二個(gè)辦法更簡(jiǎn)單,把圖片用MIDlet打包成jar文件。如果你使用的是J2ME開發(fā)工具,把PNG文件放你的項(xiàng)目文件里面就可以了。
動(dòng)畫的過程其實(shí)更像帳本記錄:顯示當(dāng)前幀,然后適當(dāng)?shù)馗鼡Q到下一幀。那么使用一個(gè)類來完成這個(gè)工作應(yīng)該是很恰當(dāng)?shù),那好,我們就先定義一個(gè)AnimatedImage類:
import java.util.*;
import javax.microedition.lcdui.*;
// 定義了一個(gè)動(dòng)畫,該動(dòng)畫其實(shí)只是一系列相同大小的圖片
// 輪流顯示,然后模擬出的動(dòng)畫
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 );
};
};
你實(shí)例化一個(gè)AnimatedImage對(duì)象的時(shí)候你必須給AnimatedImage類的構(gòu)造方法傳一個(gè)Image對(duì)象數(shù)組,該數(shù)組代表動(dòng)畫的每一幀。使用的所有圖片必須具有相同的高度和寬度。
用Image.createImage()方法從jar文件里面加載圖片:
private Image[] loadFrames( String name, int frames )
throws IOException {;
Image[] images = new Image[frames];
for( int i = 0; i < frames; ++i ){;
images = Image.createImage( name + i +".png" );
};
return images;
};
你也可以傳遞一個(gè)Canvas對(duì)象(可選),和一個(gè)剪輯列表(clip list)。如果你指定了一個(gè)canvas和使用一個(gè)timer來自動(dòng)更換到動(dòng)畫的下一幀,就如下面的例子代碼中一樣,canvas在動(dòng)畫向前滾動(dòng)以后自動(dòng)被重畫(repaint)。不過這樣的實(shí)現(xiàn)辦法是可選的,你可以這樣做,也可以讓程序選擇合適的時(shí)候重畫canvas。
因?yàn)镸IDP 1.0不支持透明的圖片,AnimatedImage 類使用一個(gè)剪輯列表來模擬透明的效果,剪輯列表是圖片被剪成的方塊區(qū)域的系列。圖片被畫出來的時(shí)候分開幾次,每次畫一個(gè)剪輯列表里面的剪輯區(qū)域。剪輯列表在幀的基礎(chǔ)上被定義好,所以你需要為圖片的每一幀創(chuàng)建一個(gè)數(shù)組。數(shù)組的大小應(yīng)該是4的倍數(shù),因?yàn)槊恳粋(gè)剪輯面積保持了四個(gè)數(shù)值:左坐標(biāo),頂坐標(biāo),寬度以及高度。坐標(biāo)的原點(diǎn)是整個(gè)圖片的左上角。需要注意的是使用了剪輯列表會(huì)使動(dòng)畫慢下來。如果圖片更加復(fù)雜的話,你應(yīng)該使用矢量圖片。
AnimatedImage類擴(kuò)展了java.util.TimerTask,允許你設(shè)定一個(gè)timer。這里有個(gè)例子說明如何使用timer做動(dòng)畫:
Timer timer = new Timer();
AnimatedImage ai = ..... // get the image
timer.schedule( ai, 200, 200 );
每隔大約200毫秒,timer調(diào)用AnimatedImage.run()方法一次,這個(gè)方法使得動(dòng)畫翻滾到下一個(gè)幀,F(xiàn)在我們需要的是讓MIDlet來試試顯示動(dòng)畫!我們定義一個(gè)簡(jiǎn)單的Canvas類的子類,好讓我們把動(dòng)畫“粘貼上去”。
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 );
};
};
};
AnimatedCanvas 類的代碼相當(dāng)簡(jiǎn)單,由一個(gè)動(dòng)畫導(dǎo)入方法和一個(gè)paint方法。canvas畫布每次被畫,背景都會(huì)被擦除然后循環(huán)每個(gè)導(dǎo)入的AnimatedImage對(duì)象,直接畫到自己身上來(自己擴(kuò)展了canvas類)。
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 = 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 = Image.createImage( name + i + ".png" );
};
return images;
};
protected void pauseApp(){; };
protected void startApp()
throws MIDletStateChangeException {;
if( display == null ){;
display = Display.getDisplay( this );
initMIDlet();
};
};
};
七幀圖片的動(dòng)畫,你可以看到一個(gè)拍著翅膀的小鳥。MIDlet顯示了5只小鳥,小鳥的位置和刷新速度是隨機(jī)的。你可以用一些其他的辦法來改進(jìn)這個(gè)程序,但這個(gè)程序也應(yīng)該足夠能讓你上手了。