Animating Images in MIDP

开发者常询问在MIDP中如何显示动画图像,MIDP 1.0无直接支持但可自行实现。先创建一系列PNG格式图像,可将其放于服务器或打包在JAR文件。通过类管理帧,利用定时器推进动画,还可模拟图像透明度,最后用MIDlet测试动画。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



Developers using the Mobile Information Device Profile (MIDP) often ask how they can get a MIDlet to display animated images. MIDP 1.0 provides no direct support for animated images (the MIDP 2.0 specification, still in development, does), but it's not that hard to do it yourself.

The basic premise of any animation is to draw separate images quickly enough that the human eye sees movement. The images must of course be drawn in sequence, and the smaller the changes from one image to the next the better.

The first thing to do, then, is to use your favorite painting application to create a series of identically-sized images to compose the animation. Each separate image represents a different frame of the animation. You will need several frames -- the more frames, the smoother the resulting animation. Be sure to save the images in PNG (Portable Network Graphics) format, the only image format supported across all MIDP implementations.

There are two ways to make the images available to the MIDlet that is going to animate them. One way is to place them on a web server and have the MIDlet download them using MIDP's built-in HTTP support. The simpler way, however, is to package them with the MIDlet in its JAR file. If you're using the J2ME Wireless Toolkit, just place the PNG files in your project's res directory.

Animation is mostly a matter of bookkeeping: keeping track of the current frame and advancing to the next frame as appropriate. It makes sense to use a class to do the housekeeping for you, so define an AnimatedImage class:

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 );
    }
}

When you instantiate AnimatedImage you pass the constructor an array of Image objects representing the individual animation frames. The images are assumed to be of identical height and width. Use the Image.createImage() method to load an image from the JAR file:

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;
}

For example, to load the series of frames stored as /images/bird0.png, /images/bird1.png, and so on through /images/bird6.png, and then create an animated image, use:

Image[] frames = loadFrames( "/images/bird", 7 );
AnimatedImage ai = new AnimatedImage( frames );
ai.move( 20, 20 ); // set top-left to 20,20

Note that an AnimatedImage keeps track of its position and draws itself relative to that position.

You can also pass in an optional Canvas instance and an optional clip list. If you specify a canvas and use a timer to advance the animation to the next frame automatically, as in the next code sample, the canvas is automatically repainted after the advance. This behavior is completely optional, however -- the application can also choose when to repaint.

Because MIDP 1.0 does not support image transparency, the AnimatedImage class simulates it using a clip list, a set of rectangular areas within the image. The image is drawn multiple times, each time with the clipping region set to one of the areas in the clip list. The clip list is specified on a per-frame basis, so you need to create an array of integers for each frame of the image. The array size is a multiple of four, because each clipping area consists of four values: the left coordinate, the top coordinate, the width, and the height. The coordinate values are relative to the top left corner of the image. Note that using a clip list slows down the animation. For complex images you may prefer to use vector graphics to do the drawing.

The AnimatedImage class extends java.util.TimerTask, which allows you to schedule it with a timer. A previous Tech Tip discussed how to use timers in detail. Here's an example of using one for animation:

Timer timer = new Timer();
AnimatedImage ai = ..... // get the image 
timer.schedule( ai, 200, 200 );

Every 200 milliseconds or so, the timer invokes the AnimatedImage.run() method, which causes the animation to move to the next frame. The animation also repaints itself if a canvas is available.

All we need now is a MIDlet to try out the animation. We define a simple extension of Canvas that lets us "attach" animated images to it:

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 );
        }
    }
}

The code for the AnimatedCanvas class is quite simple, consisting of an animation registration method and a paint method. Every time the canvas is painted, it erases its background, then cycles through the list of registered animations, directing each to draw itself. Notice that the class uses double buffering to reduce flicker (an earlier Tech Tip discussed double buffering).

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();
        }
    }
}

The seven-frame image set gives you an animation of a bird flapping its wings. The MIDlet places five birds at random locations with random refresh speeds. You can improve on this simple example in any number of ways, but it should be enough to get you going.


资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 华为移动服务(Huawei Mobile Services,简称 HMS)是一个全面开放的移动服务生态系统,为企业和开发者提供了丰富的工具和 API,助力他们构建、运营和推广应用。其中,HMS Scankit 是华为推出的一款扫描服务 SDK,支持快速集成到安卓应用中,能够提供高效且稳定的二维码和条形码扫描功能,适用于商品扫码、支付验证、信息获取等多种场景。 集成 HMS Scankit SDK 主要包括以下步骤:首先,在项目的 build.gradle 文件中添加 HMS Core 库和 Scankit 依赖;其次,在 AndroidManifest.xml 文件中添加相机访问和互联网访问权限;然后,在应用程序的 onCreate 方法中调用 HmsClient 进行初始化;接着,可以选择自定义扫描界面或使用 Scankit 提供的默认扫描界面;最后,实现 ScanCallback 接口以处理扫描成功和失败的回调。 HMS Scankit 内部集成了开源的 Zxing(Zebra Crossing)库,这是一个功能强大的条码和二维码处理库,提供了解码、生成、解析等多种功能,既可以单独使用,也可以与其他扫描框架结合使用。在 HMS Scankit 中,Zxing 经过优化,以更好地适应华为设备,从而提升扫描性能。 通常,ScanKitDemoGuide 包含了集成 HMS Scankit 的示例代码,涵盖扫描界面的布局、扫描操作的启动和停止以及扫描结果的处理等内容。开发者可以参考这些代码,快速掌握在自己的应用中实现扫码功能的方法。例如,启动扫描的方法如下: 处理扫描结果的回调如下: HMS Scankit 支持所有安卓手机,但在华为设备上能够提供最佳性能和体验,因为它针对华为硬件进行了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值