One of the keys to the success of Java is its capability to add animation and action to Web pages without requiring you to download huge video files. Even though Java is an interpreted language (assuming that your users' browsers don't have a JIT-Just In Time compiler-installed), it's more than fast enough for most animation tasks. Listing 17.12 shows a simple animation that moves a yellow square over a red and blue checkerboard pattern.
Listing 17.12. A simple animation applet.
import java.awt.*;import java.applet.Applet;public class simplest_animation extends Applet implements Runnable{int x_pos,y_pos;int n_squares;int dh,dw;Color color_1,color_2;Color square_color;int square_size;int square_step;Thread the_thread;public void start() {if (the_thread == null) {the_thread = new Thread(this);the_thread.start();}}public void stop() {the_thread.stop();}public void run() {while (true) {repaint();try {the_thread.sleep(50);} catch(InterruptedException e) {};}}public void init(){int w_height, w_width;//set the starting point of the moving squarex_pos = 0;y_pos = 0;//set the size of the moving squaresquare_size = 40;//set how much the square moves between screen redrawssquare_step = 2;//specifies the number of squares on each side of the checkerboardn_squares = 10;//get the size of the applet's drawing areaw_height = size().height;w_width = size().width;//determine the size of the squares in the checkerboarddh = w_height/n_squares;dw = w_width/n_squares;//set the colors for the checkerboard.//Could have used Color.blue and//Color.red instead of creating new color instancescolor_1 = new Color(0,0,255);color_2 = new Color(255,0,0);//set the color for the moving squaresquare_color = new Color(255,255,0);}public void draw_check(Graphics g) {int i,j,offset;int x,y;//this draws a checkerboardoffset = 0;for(i=0;i<n_squares;i++) {y = i * dh;offset++;for(j=0;j<n_squares;j++) {x = j * dw;if (((j + offset)% 2) > 0) {g.setColor(color_1);} else {g.setColor(color_2);}g.fillRect(x,y,dw,dh);}}}public void paint(Graphics g){//draw the blue and red checkerboard backgrounddraw_check(g);//increment the position of the moving squarex_pos += square_step;y_pos += square_step;//set the drawing color to the square colorg.setColor(square_color);//draw a filled rectangle that movesg.fillRect(x_pos,y_pos,square_size, square_size);}}
What you can't see unless you run the applet is that the whole image is flickering; you can see the background through the yellow square. All in all, the word that comes to mind is ugly-hardly the sort of thing that will help sell a Web page. You can reduce this flicker in several ways. A large part of the flicker occurs when repaint invokes the update method. You saw earlier in this chapter that the update method erases the applet by drawing a background colored rectangle. This erasing, followed by the drawing of the background rectangle, causes a lot of the flicker. You can avoid this by overriding the update method with this version:
public void update(Graphics g) {paint(g);}
This eliminates the flicker in most of the image, but you'll still see flicker in the region of the rectangle. That's because the background is drawn and then the square is drawn over it. Your eye sees the background and then the yellow square every time the screen is redrawn. One way to reduce the amount of drawing that has to be done is to tell the AWT to redraw only the part of the screen that has changed.
The version of the paint method shown in Listing 17.13 does just that. It creates two rectangles: one for where the square is and one for where it will be after it's moved in this call to paint. The union method on Rectangle then is used to get the smallest rectangle that contains both those rectangles. That rectangle is the one that contains all the changes between the two frames. Next, clipRect is used to tell the AWT to repaint only this rectangle.
Note |
Instead of creating two new rectangles each time paint is called, you could save the second rectangle in an instance variable and then use it as the old rectangle in the next call to paint. |
Listing 17.13. Repainting the minimal area.
public void paint(Graphics g){Rectangle old_r, new_r,to_repaint;draw_check(getGraphics());//Figure out where the square is nowold_r = new Rectangle(x_pos,y_pos,square_size,square_size);//Figure out where the square will be after this method executesnew_r = new Rectangle((x_pos + square_step),(y_pos +square_step),square_size, square_size);//Find the smallest rectangle that contains the old and new positionsto_repaint = new_r.union(old_r);//Tell Java to only repaint the areas that have been// affected by the moving squareg.clipRect(to_repaint.x,to_repaint.y,to_repaint.width,to_repaint.height);x_pos += square_step;y_pos += square_step;g.setColor(square_color);g.fillRect(x_pos,y_pos,square_size, square_size);}
Although using clipRect reduces the amount of work the AWT has to do, it's still not fast enough to fool your eye. To do that, you need to use double buffering.
Double buffering involves doing all your drawing to an invisible, off-screen bitmap-an image, actually-and then copying that off-screen image to the applet. This is called bitblitting on the Mac and results in much faster drawing. Listing 17.14 shows the commented changes you need to make in the animation applet to do double buffering.
Listing 17.14. Using double buffering.
public class simple_animation extends Applet implements Runnable{....//Define an image to use for offscreen drawingImage offscreen;Graphics offscreen_graphics;....public void init(){....//create the offscreen image and get its Graphics instanceoffscreen = createImage(size().width,size().height);offscreen_graphics = offscreen.getGraphics();//draw the background checkerboarddraw_check();}....public void draw_check() {int i,j,offset;int x,y;offset = 0;for(i=0;i<n_squares;i++) {y = i * dh;offset++;for(j=0;j<n_squares;j++) {x = j * dw;if (((j + offset)% 2) > 0) {offscreen_graphics.setColor(color_1);} else {offscreen_graphics.setColor(color_2);}offscreen_graphics.fillRect(x,y,dw,dh);}}}public void paint(Graphics g){Rectangle old_r, new_r,to_repaint;old_r = new Rectangle(x_pos,y_pos,square_size,square_size);new_r = new Rectangle((x_pos + square_step),(y_pos +square_step),square_size, square_size);to_repaint = new_r.union(old_r);draw_check();//just draw what's needed except for the first timeif (x_pos < 1) {g.clipRect(to_repaint.x,to_repaint.y,to_repaint.width,to_repaint.height);}x_pos += square_step;y_pos += square_step;//same as before but now the square is drawn to the offscreen imageoffscreen_graphics.setColor(square_color);offscreen_graphics.fillRect(x_pos,y_pos,square_size,square_size);//now that the offscreen image is all done draw the whole thing//to the screeng.drawImage(offscreen,0,0,this);}}
No comments:
Post a Comment