Java/J2ME/Animation
Содержание
A MIDlet that displays the Doggy animation.
<source lang="java">
/*
* Copyright (c) 2000-2001 Sun Microsystems, Inc. All Rights Reserved. */
import javax.microedition.midlet.*; import javax.microedition.lcdui.*; /**
* A MIDlet that displays the Doggy animation. * * @author Mark A. Patel - Motorola, Inc. * @author Roger Riggs - Sun Microsystems, Inc. **/
public class DoggyMIDlet extends MIDlet implements CommandListener {
Command cmdExit; /** * Constructs a new DoggyMIDlet **/ public DoggyMIDlet() { cmdExit = new Command("Exit", Command.EXIT, 1); } /** * Starts the app by creating a new Doggy instance and displaying it **/ protected void startApp() throws MIDletStateChangeException { Doggy d; d = new Doggy(); d.addCommand(cmdExit); d.setCommandListener(this); Display.getDisplay(this).setCurrent(d); } protected void pauseApp() { } protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { } public void commandAction(Command c, Displayable d) { if (c == cmdExit) { try { destroyApp(false); } catch (Exception e) {} notifyDestroyed(); } }
}
class Doggy extends Canvas implements Runnable { /** * Number of frames in the animation **/ static final int FRAME_COUNT = 17; /** * Normal frame delay (milliseconds) **/ static final int FRAME_DELAY = 180; /** * Frame delay for the last frame where the dog is sleeping **/ static final int LAST_FRAME_DELAY = 3000; /** * Relative horizontal position where each of the frames * should be rendered. 0 represents the left edge of the screen * and 1024 represents the right edge of the run distance * (1024 is used so that scaling can be performed using * simple bit shifts instead of division operations). **/ static final int[] framePositions = { 0, 50, 186, 372, 558, 744, 930, 1024, 1024, 834, 651, 465, 279, 93, 0, 0, 0 }; /** * An Image containing the 17 frames of the dog running, * stacked vertically. * Using a single image is much more efficient than using several * images with each containing a single frame. * Each frame can be rendered seperately by setting the clip * region to the size of a single frame and then * rendering the image at the correct position so that the desired * frame isaligned with the clip region. **/ Image doggyImages = null; /** * Width of a single animation frame **/ int frameWidth = 0; /** * Height of a single animation frame **/ int frameHeight = 0; /** * Index of the current frame **/ int frameIndex = 0; /** * The distance, in pixels, that the dog can run (screen width less * the width of a single frame) **/ int runLength = 0; /** * Indicates if the animation is currently running **/ boolean running = false; /** * Called when this Canvas is shown. This method starts the timer * that runs the animation sequence. **/ protected void showNotify() { if (doggyImages == null) { try { doggyImages = Image.createImage("/examples/animation/Doggy.png"); frameWidth = doggyImages.getWidth(); frameHeight = doggyImages.getHeight() / FRAME_COUNT; } catch (Exception ioe) { return; // no image to animate } } runLength = getWidth() - frameWidth; running = true; frameIndex = 0; new Thread(this).start(); } /** * Called when this Canvas is hidden. This method stops the * animation timer to free up processing * power while this Canvas is not showing. **/ protected void hideNotify() { running = false; } public void run() { // Need to catch InterruptedExceptions and bail if one occurs try { while (running) { Thread.sleep((frameIndex == FRAME_COUNT - 1) ? LAST_FRAME_DELAY : FRAME_DELAY); // Remember the last frame index so we can compute // the repaint region int lastFrameIndex = frameIndex; // Update the frame index frameIndex = (frameIndex + 1) % FRAME_COUNT; // Determine the left edge of the repaint region int repaintLeft = framePositions[lastFrameIndex]; int repaintRight = framePositions[frameIndex]; if (framePositions[lastFrameIndex] > framePositions[frameIndex]) { repaintLeft = framePositions[frameIndex]; repaintRight = framePositions[lastFrameIndex]; } // Scale the repaint coordinates to the width of the screen repaintLeft = (repaintLeft * runLength) >> 10; repaintRight = (repaintRight * runLength) >> 10; // Trigger a repaint of the affected portion of the screen // Repaint the region where the last frame was rendered // (ensures that it is cleared) repaint(repaintLeft, 0, frameWidth + repaintRight - repaintLeft, frameHeight); } } catch (InterruptedException e) {} } public void paint(Graphics g) { // Clear the background (fill with white) // The clip region will limit the area that // actually gets cleared to save time g.setColor(0xFFFFFF); g.fillRect(0, 0, getWidth(), getHeight()); // Translate the graphics to the appropriate // position for the current frame g.translate((framePositions[frameIndex] * runLength) >> 10, 0); // Constrain the clip region to the size of a single frame g.clipRect(0, 0, frameWidth, frameHeight); // Draw the current frame by drawing the entire image with // the appropriate vertical offset so that the desired frame // lines up with the clip region. g.drawImage(doggyImages, 0, -(frameIndex * frameHeight), Graphics.LEFT + Graphics.TOP); }
}
</source>
Animated Timer
<source lang="java">
/*--------------------------------------------------
* AnimatedTimer - Main midlet. * Shows canvas with an animated timer. Includes * configuration options to start/stop the timer * and to adjust the sleep interval of the thread * * Example from the book: Core J2ME Technology * Copyright John W. Muchow http://www.CoreJ2ME.ru * You may use/modify for any non-commercial purpose *-------------------------------------------------*/
import java.util.Stack; import javax.microedition.lcdui.Alert; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.rumand; import javax.microedition.lcdui.rumandListener; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.Gauge; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.List; import javax.microedition.midlet.MIDlet; public class AnimatedTimer extends MIDlet {
private Display display; // The display protected TimerCanvas cvTimer; // Canvas to display timer protected OptionsList lsOptions; // List to change timer options protected SleepForm fmSleep; // Form with gauge to set timer sleep protected DisplayManager displayMgr; // Class to help manage screens public AnimatedTimer() { display = Display.getDisplay(this); cvTimer = new TimerCanvas(this); lsOptions = new OptionsList("Timer options", List.IMPLICIT, this); fmSleep = new SleepForm("Adjust sleep", this); // Create a display manager object displayMgr = new DisplayManager(display, cvTimer); } protected void startApp() { // Start with the canvas display.setCurrent(cvTimer); } protected void pauseApp() { } protected void destroyApp(boolean unconditional) { } public void exitMIDlet() { destroyApp(true); notifyDestroyed(); }
} /*--------------------------------------------------
* Use a stack to push and pop displayable objects * * public void pushDisplayable(Displayable) * public void popDisplayable() * public void home() * * Example from the book: Core J2ME Technology * Copyright John W. Muchow http://www.CoreJ2ME.ru * You may use/modify for any non-commercial purpose *-------------------------------------------------*/
class DisplayManager extends Stack {
private Display display; // Reference to Display object private Displayable mainDisplayable; // Main displayable for MIDlet private Alert alStackError; // Alert for error conditions /*-------------------------------------------------- * Display manager constructor *-------------------------------------------------*/ public DisplayManager(Display display, Displayable mainDisplayable) { // Only one display object per midlet, this is it this.display = display; this.mainDisplayable = mainDisplayable; // Create an alert displayed when an error occurs alStackError = new Alert("Displayable Stack Error"); alStackError.setTimeout(Alert.FOREVER); // Modal } /*-------------------------------------------------- * Push the current displayable onto stack and set * the passed in displayable as active *-------------------------------------------------*/ public void pushDisplayable(Displayable newDisplayable) { push(display.getCurrent()); display.setCurrent(newDisplayable); } /*-------------------------------------------------- * Return to the main displayable object of MIDlet *-------------------------------------------------*/ public void home() { while (elementCount > 1) pop(); display.setCurrent(mainDisplayable); } /*-------------------------------------------------- * Pop displayable from stack and set as active *-------------------------------------------------*/ public void popDisplayable() { // If the stack is not empty, pop next displayable if (empty() == false) display.setCurrent((Displayable) pop()); else // On error show an alert // Once acknowledged, set "mainDisplayable" as active display.setCurrent(alStackError, mainDisplayable); }
} /*--------------------------------------------------
* Class TimerCanvas * * Animate a sequence of images to simulate * a moving timer * * Example from the book: Core J2ME Technology * Copyright John W. Muchow http://www.CoreJ2ME.ru * You may use/modify for any non-commercial purpose *-------------------------------------------------*/
class TimerCanvas extends Canvas implements Runnable, CommandListener {
private AnimatedTimer midlet; // Main midlet private Command cmExit; // Exit midlet private Command cmOptions; // Display options list private Image im = null; // Sequence of images private int imageCount = 4; // Four images in the sequence private int imageWidth; // Width of one image in the sequence private int imageHeight; // Height of one image in the sequence private int imageIndex; // Current image in the sequence private int translate_x; // Translated x and y private int translate_y; private int viewport_x; // Location of the viewport private int viewport_y; private boolean active = false; // Timer active? private boolean requestedToStop = false; // Did user request to stop timer private int sleepTime = 400; // Current sleep time (milliseconds) public TimerCanvas(AnimatedTimer midlet) { // Call canvas constructor super(); // Save reference to MIDlet so we can // access the display manager class this.midlet = midlet; // Create commands & listen for events cmExit = new Command("Exit", Command.EXIT, 1); cmOptions = new Command("Config", Command.SCREEN, 2); addCommand(cmExit); addCommand(cmOptions); setCommandListener(this); } /*-------------------------------------------------- * Application manager is about to display canvas *-------------------------------------------------*/ protected void showNotify() { if (im == null) { try { // Read the png from a file and get width and // height of one image in the sequence im = Image.createImage("/timer.png"); imageHeight = im.getHeight(); imageWidth = im.getWidth() / imageCount; // Get the coordinates for x/y of viewport // Viewport is centered on the display viewport_x = (getWidth() / 2) - (imageWidth / 2); viewport_y = (getHeight() / 2) - (imageHeight / 2); // Set first translated coordinate to match viewport translate_x = viewport_x; translate_y = viewport_y; } catch (Exception e) { System.err.println("Unable to read png file."); } // Begin with the first image in the sequence imageIndex = 0; } // If the user has not requested to stop the timer... if (!requestedToStop) active = true; new Thread(this).start(); } /*-------------------------------------------------- * Application manager is no longer displaying canvas *-------------------------------------------------*/ protected void hideNotify() { active = false; } /*-------------------------------------------------- * Draw next timer in sequence *-------------------------------------------------*/ protected void paint(Graphics g) { if (im != null) { // Due to a bug in MIDP 1.0.3 we need to // force a clear of the display g.setColor(255, 255, 255); // White pen g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(0, 0, 0); // Black pen // Viewport at center of display g.setClip(viewport_x, viewport_y, imageWidth, imageHeight); // Draw image at translated coordinates g.drawImage(im, translate_x, translate_y, Graphics.TOP | Graphics.LEFT); } } /*-------------------------------------------------- * Loop forever, translating image coordinates *-------------------------------------------------*/ public void run() { try { while (active) { Thread.sleep(sleepTime); repaint(); // Reached the last image in sequence if (imageIndex == imageCount - 1) { // Reset translated coordinates translate_x = viewport_x; translate_y = viewport_y; } else { // Translate coordinate system to the left translate_x -= imageWidth; } // Which image in the sequence is next imageIndex = (imageIndex + 1) % imageCount; } } catch (InterruptedException e) { } } /*-------------------------------------------------- * Called from the "Config" options menu *-------------------------------------------------*/ public void startTimer() { requestedToStop = false; active = true; repaint(); } /*-------------------------------------------------- * Called from the "Config" options menu *-------------------------------------------------*/ public void stopTimer() { requestedToStop = true; active = false; repaint(); } /*-------------------------------------------------- * Called from form/gauge to adjust sleep *-------------------------------------------------*/ public void setSleep(int sleepTime) { this.sleepTime = sleepTime; } /*-------------------------------------------------- * Called from form/gauge to adjust sleep *-------------------------------------------------*/ public int getSleep() { return sleepTime; } /*-------------------------------------------------- * Command event handling *-------------------------------------------------*/ public void commandAction(Command c, Displayable s) { if (c == cmOptions) { // Push current displayable and show the options list midlet.displayMgr.pushDisplayable(midlet.lsOptions); } else if (c == cmExit) { midlet.exitMIDlet(); } }
} /*--------------------------------------------------
* Class SleepForm * * Form with gauge to adjust sleep interval of timer * * Example from the book: Core J2ME Technology * Copyright John W. Muchow http://www.CoreJ2ME.ru * You may use/modify for any non-commercial purpose *-------------------------------------------------*/
class SleepForm extends Form implements CommandListener {
private AnimatedTimer midlet; // Main midlet private Command cmBack, // Back to options list cmHome, // Go to main displayable (canvas) cmSave; // Save new sleep time private Gauge gaSleep; // Gauge to adjust sleep public SleepForm(String title, AnimatedTimer midlet) { // Call the form constructor super(title); // Save reference to MIDlet so we can // access the display manager class this.midlet = midlet; // Commands cmSave = new Command("Save", Command.SCREEN, 1); cmBack = new Command("Back", Command.BACK, 2); cmHome = new Command("Home", Command.SCREEN, 2); // Gauge to adjust the length of timer sleep gaSleep = new Gauge("Timer Sleep", true, 100, 1000); // Set to current sleep. Gauge holds values 0 to 100, // divide the current sleep (milliseconds) by 10 gaSleep.setValue(midlet.cvTimer.getSleep() / 10); // Add to form and listen for events append(gaSleep); addCommand(cmSave); addCommand(cmBack); addCommand(cmHome); setCommandListener(this); } /*-------------------------------------------------- * Command event handling *-------------------------------------------------*/ public void commandAction(Command c, Displayable s) { if (c == cmSave) { // Gauge returns a value between 0 and 100 // We want milliseconds, so multiply by 10 midlet.cvTimer.setSleep(gaSleep.getValue() * 10); // Return to main midlet midlet.displayMgr.home(); } else if (c == cmBack) { // Pop the last displayable off the stack midlet.displayMgr.popDisplayable(); } else if (c == cmHome) { // Return to main midlet midlet.displayMgr.home(); } }
} /*--------------------------------------------------
* Class OptionsList * * List to provide options for configuring of timer * * Example from the book: Core J2ME Technology * Copyright John W. Muchow http://www.CoreJ2ME.ru * You may use/modify for any non-commercial purpose *-------------------------------------------------*/
class OptionsList extends List implements CommandListener {
private AnimatedTimer midlet; // Main midlet private Command cmBack; public OptionsList(String title, int listType, AnimatedTimer midlet) { // Call list constructor super(title, listType); // Save reference to MIDlet so we can // access the display manager class this.midlet = midlet; // Create the list entries append("Sleep interval", null); append("Start", null); append("Stop", null); // Create command and listen for events cmBack = new Command("Back", Command.BACK, 1); addCommand(cmBack); setCommandListener(this); } /*-------------------------------------------------- * Command event handling *-------------------------------------------------*/ public void commandAction(Command c, Displayable s) { // Event generated by the implicit list if (c == List.SELECT_COMMAND) { switch (getSelectedIndex()) { case 0: // Push current displayable and show the form // to adjust the timer sleep midlet.displayMgr.pushDisplayable(midlet.fmSleep); break; case 1: // Start timer and return to previous displayable midlet.cvTimer.startTimer(); midlet.displayMgr.popDisplayable(); break; case 2: // Stop timer and return to previous displayable midlet.cvTimer.stopTimer(); midlet.displayMgr.popDisplayable(); break; } } else if (c == cmBack) { // Return to previous displayable midlet.displayMgr.popDisplayable(); } }
}
</source>
Animation MIDlet
<source lang="java">
/* J2ME in a Nutshell By Kim Topley ISBN: 0-596-00253-X
- /
import java.util.Timer; import java.util.TimerTask; import javax.microedition.lcdui.Canvas; import javax.microedition.lcdui.rumand; import javax.microedition.lcdui.rumandListener; import javax.microedition.lcdui.Display; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.Gauge; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Item; import javax.microedition.lcdui.ItemStateListener; import javax.microedition.midlet.MIDlet; public class AnimationMIDlet extends MIDlet
implements CommandListener, ItemStateListener { // The MIDlet"s Display object private Display display; // Flag indicating first call of startApp protected boolean started; // Exit command private Command exitCommand; // Setup command private Command setupCommand; // Run command private Command runCommand; // Configuration form private Form form; // Animation canvas private AnimationCanvas canvas; // Gauge for block count private Gauge blockGauge; // Gauge for frame rate private Gauge rateGauge; // Initial frame rate private static final int FRAME_RATE = 1; // Initial number of blocks private static final int BLOCK_COUNT = 1; protected void startApp() { if (!started) { display = Display.getDisplay(this); form = new Form("Animation"); rateGauge = new Gauge("Frame rate", true, 10, FRAME_RATE); blockGauge = new Gauge("Blocks", true, 4, BLOCK_COUNT); form.append(rateGauge); form.append(blockGauge); form.setItemStateListener(this); canvas = createAnimationCanvas(); exitCommand = new Command("Exit", Command.EXIT, 0); setupCommand = new Command("Setup", Command.SCREEN, 0); runCommand = new Command("Run", Command.SCREEN, 0); canvas.addCommand(exitCommand); canvas.addCommand(setupCommand); form.addCommand(exitCommand); form.addCommand(runCommand); form.setCommandListener(this); canvas.setCommandListener(this); display.setCurrent(form); started = true; } } protected void pauseApp() { } protected void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable d) { if (c == exitCommand) { // Exit. No need to call destroyApp // because it is empty. notifyDestroyed(); } else if (c == runCommand) { display.setCurrent(canvas); } else if (c == setupCommand) { display.setCurrent(form); } } public void itemStateChanged(Item item) { if (item == blockGauge) { int count = blockGauge.getValue(); if (count < 1) { count = 1; } canvas.setBlockCount(count); } else if (item == rateGauge) { int count = rateGauge.getValue(); if (count < 1) { count = 1; } canvas.setFrameRate(count); } } // Creates the canvas that will draw the block protected AnimationCanvas createAnimationCanvas() { return new AnimationCanvas(); } class AnimationCanvas extends Canvas { // Size of each block protected static final int SIZE = 4; // Initial speeds in the X direction protected final int[] xSpeeds = { 2, -2, 0, -2 }; // Initial speeds in the Y direction protected final int[] ySpeeds = { 2, -2, 2, -0 }; // Background color protected int background = display.isColor() ? 0 : 0xc0c0c0; // Foreground color protected int foreground = display.isColor() ? 0xffff00 : 0; // Width of screen protected int width = getWidth(); // Height of screen protected int height = getHeight(); // The screen update rate protected int frameRate; // The blocks to draw on the screen protected Block[] blocks; // The update timer protected Timer timer; // The update timer task protected TimerTask updateTask; // Gets the maximum number of blocks public int getMaxBlocks() { return blocks.length; } // Constructs a canvas with default settings AnimationCanvas() { setBlockCount(BLOCK_COUNT); setFrameRate(FRAME_RATE); } // Sets the number of blocks to draw public void setBlockCount(int count) { if (count > xSpeeds.length) { throw new IllegalArgumentException("Cannot have more than " + xSpeeds.length + " blocks"); } blocks = new Block[count]; createBlocks(); } // Gets the number of blocks to draw public int getBlockCount() { return blocks.length; } // Sets the number of updates per second public void setFrameRate(int frameRate) { if (frameRate < 1 || frameRate > 10) { throw new IllegalArgumentException("Frame rate must be > 0 and <= 10"); } this.frameRate = frameRate; if (isShown()) { startFrameTimer(); } } // Gets the number of updates per second public int getFrameRate() { return frameRate; } // Paint canvas background and all // of the blocks in their correct locations. protected void paint(Graphics g) { // Paint with the background color g.setColor(background); g.fillRect(0, 0, width, height); // Draw all of the blocks g.setColor(foreground); synchronized (this) { for (int i = 0, count = blocks.length; i < count; i++) { g.fillRect(blocks[i].x, blocks[i].y, SIZE, SIZE); } } } // Notification that the canvas has been made visible protected void showNotify() { // Start the frame timer running startFrameTimer(); } // Notification that the canvas is no longer visible protected void hideNotify() { // Stop the frame timer stopFrameTimer(); } // Creates the blocks to be displayed private void createBlocks() { int startX = (width - SIZE)/2; int startY = (height - SIZE)/2; for (int i = 0, count = blocks.length; i < count; i++) { blocks[i] = new Block(startX, startY, xSpeeds[i], ySpeeds[i]); } } // Starts the frame redraw timer protected void startFrameTimer() { timer = new Timer(); updateTask = new TimerTask() { public void run() { moveAllBlocks(); } }; long interval = 1000/frameRate; timer.schedule(updateTask, interval, interval); } // Stops the frame redraw timer protected void stopFrameTimer() { timer.cancel(); } // Called on expiry of timer. public synchronized void moveAllBlocks() { // Update the positions and speeds // of all of the blocks for (int i = 0, count = blocks.length; i < count; i++) { blocks[i].move(); // Request a repaint of the screen repaint(); } } // Inner class used to represent a block on the screen class Block { int x; // X position int y; // Y position int xSpeed; // Speed in the X direction int ySpeed; // Speed in the Y direction Block(int x, int y, int xSpeed, int ySpeed) { this.x = x; this.y = y; this.xSpeed = xSpeed; this.ySpeed = ySpeed; } void move() { x += xSpeed; if (x <= 0 || x + SIZE >= width) { xSpeed = -xSpeed; } y += ySpeed; if (y <= 0 || y + SIZE >= height) { ySpeed = -ySpeed; } } } }
}
</source>
Animation MIDlet 2
<source lang="java">
/* J2ME in a Nutshell By Kim Topley ISBN: 0-596-00253-X
- /
import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class AnimationMIDlet2 extends AnimationMIDlet {
// Create an AnimationCanvas2 protected AnimationMIDlet.AnimationCanvas createAnimationCanvas() { return new AnimationCanvas2(); } class AnimationCanvas2 extends AnimationMIDlet.AnimationCanvas { // Override superclass paint method to take // into account the clipping rectangle protected void paint(Graphics g) { // Get the clipping rectange int clipX = g.getClipX(); int clipY = g.getClipY(); int clipWidth = g.getClipWidth(); int clipHeight = g.getClipHeight(); // Paint with the background color - only // the area within the clipping rectangle g.setColor(background); g.fillRect(clipX, clipY, clipWidth, clipHeight); // Draw all of the blocks g.setColor(foreground); synchronized (this) { for (int i = 0, count = blocks.length; i < count; i++) { g.fillRect(blocks[i].x, blocks[i].y, SIZE, SIZE); } } } // Called on expiry of timer. public synchronized void moveAllBlocks() { // Update the positions and speeds // of all of the blocks and repaint // only the part of the screen that // they occupy for (int i = 0, count = blocks.length; i < count; i++) { // Request a repaint of the current location Block block = blocks[i]; repaint(block.x, block.y, SIZE, SIZE); blocks[i].move(); // Request a repaint of the new location repaint(block.x, block.y, SIZE, SIZE); } } }
}
</source>