Thực hiện các hoạt cảnh

Một phần của tài liệu Tổng quan về Java UI pptx (Trang 45 - 49)

Nhiều chương trình thực hiện hoạt cảnh, hoặc là hoạt hình của chú vịt Duke đang tung tăng bơi lội hay chỉ đơn giản là một hình ảnh chuyển động trên màn hình. Trong phần này sẽ nói về cách thực hiện hình ảnh động, cách sử dụng đối tượng Timer để thực hiện hoạt cảnh.

Tạo một Animation Loop

Bước quan trọng nhất để tạo một chương trình hoạt hình chính là khởi tạo các Framework một cách chính xác. Ngoại trừ các hoạt hình thực hiện trực tiếp các đáp ứng cho các sự kiện mở rộng (ví dụ như việc người dùng kéo một đối tượng trên màn hình), một chương trình thực hiện hoạt cảnh cần có một vòng lặp của hoạt hình.

Minh hoạ cho mục này có trong các ví dụ AnimatorAppletTimer.java và AnimatorApplicationTimer.java. Sau đây là phần tóm lược chung nhất của cả hai ví dụ. Đây cũng là xườn của một chương trình hoạt cảnh:

public class AnimatorClass ... implements ActionListener { int frameNumber = -1;

Timer timer;

boolean frozen = false; JLabel label;

//In initialization code:

//From user-specified frames-per-second value, determine //how long to delay between frames.

...

//Set up a timer that calls this object's action handler. timer = new Timer(delay, this);

...

//Set up the components in the GUI.

public synchronized void startAnimation() { ...

timer.start(); ...

}

public synchronized void stopAnimation() { ...

timer.stop(); ...

}

public void actionPerformed(ActionEvent e) { //Advance the animation frame.

frameNumber++;

//Request that the frame be painted. label.setText("Frame " + frameNumber); }

...

//When the application's GUI appears:

startAnimation(); ...

}

Hoạt hình

Bạn nên lưu ý rằng Graphics không thực hiện hoạt cảnh một cách trôi chảy – nghĩa là đôi khi một phần hay tất cảc vùng vẽ bị nháy.

Loại trừ nháy hình

Sự nháy hình là kết quả của hai sự kiện:

• Mặc định, nền của hoạt cảnh sẽ bị xoá (nó bao gồm cả vùng nền vẽ lại) trước khi phương thức paint() được gọi.

• Thao tác của phương thức paint() làm cho máy tính không thể thực hiện các hoạt cảnh với tần suất hiển thị cao. Kết quả là một phần của hoạt cảnh được bỏ qua.

Bỏ qua phương thức update()

Để loại trừ sự nháy hình, bạn nên bỏ qua phương thức update(). Lí do là nằm trong cách yêu cầu của AWT tới các thành phần tự vẽ lại nó (như Applet, Canvas, hoặc Frame) repaint itself.

AWT yêu cầu vẽ lại băngz cách gọi phương thức update() của các thành phần. Mặc định phương thức update() xó nền các thành phần trước khi gọi phương thức paint(). Bởi vì loại trừ nháy hình là loại trừ các thao tác vẽ không cần thiết, nên bước đầu tiên phải bỏ qua phương thức update() vì thế nó chỉ xoá nền các thành phần khi cần thiết. Khi lệnh vẽ được chuyển từ phương thức paint() đến phương thức update(), bạn nên cần sửa đổi lệnh vẽ do đó nó sẽ không phụ thuộc vào việc nền có bị xoá hay không

Ví dụ :

public void paint(Graphics g) { update(g);

}

public void update(Graphics g) { Color bg = getBackground(); Color fg = getForeground();

...//same as old paint() method until we draw the rectangle:

if (fillSquare) { g.fillRect(x, y, w, h); fillSquare = false; } else { g.setColor(bg); g.fillRect(x, y, w, h); g.setColor(fg); fillSquare = true;

}

...//same as old paint() method

}

Cắt tỉa vùng ve

Một kỹ thuật mà bạn có thể sử dụng trong phương thức update() là clipping vùng được vẽ. Việc cắt tỉa được thực hiện bởi phương thức clipRect() method.

Chuyển động ảnh qua màn hình

Cách đơn giản nhất để tạo hoạt cảnh là di chuyển một hình ảnh trên màn hình. Trong thế giới của hoạt cảnh truyền thống, điều này được gọi là cutout animation.

Có hai hình mà applet sử dụng.

Và đây là giao diện của applet. Cần lưu ý là để khởi động hay dừng applet thì click chuột lên applet.

Lưu ý: hình rocketship có ảnh nền là transparent. Đoạn mã để thực hiện hoạt cảnh này không mấy phức tạp. Nói chung, nó cũng giống như xườn đưa ra ở bên trên. Thay vì sử dụng một label để thực hiện hoạt cảnh thì nó sử dụng một thành phần tùy biến. Thành phần tùy biến ở đây là lớp con của JPanel nhằm thực thi việc vẽ cả hai hình ảnh ở trên:

...//Where the images are initialized:

Image background = getImage(getCodeBase(),

"images/rocketship.gif"); Image foreground = getImage(getCodeBase(),

"images/starfield.gif"); ...

public void paintComponent(Graphics g) {

super.paintComponent(g); //paint any space not covered //by the background image int compWidth = getWidth();

int compHeight = getHeight();

//If we have a valid width and height for the //background image, paint it.

imageWidth = background.getWidth(this); imageHeight = background.getHeight(this); if ((imageWidth > 0) && (imageHeight > 0)) { g.drawImage(background,

(compWidth - imageWidth)/2,

(compHeight - imageHeight)/2, this); }

//foreground image, paint it.

imageWidth = foreground.getWidth(this); imageHeight = foreground.getHeight(this); if ((imageWidth > 0) && (imageHeight > 0)) { g.drawImage(foreground, ((frameNumber*5) % (imageWidth + compWidth)) - imageWidth, (compHeight - imageHeight)/2, this); } }

Có thể bạn sẽ cho rằng việc xoá ảnh nền là không cần thiết khi sử dụng một ảnh nền nào đó. Tuy nhiên, việc xoá hình nền ở đây vẫn được quan tâm, bởi lẽ applet luôn luôn khởi động việc vẽ trước khi hình được nạp đầy đủ. Nếu hình rocketship được nạp trước hình nền thì ta sẽ thấy những phần khác nhau này của chương trình.

Hiển thị sự liên tục của ảnh

Trong ví dụ của phần này sẽ cung cấp những bước cơ bản của việc hiển thị tuần tự các hình ảnh để nó thật giống như hoạt cảnh mà ta thường thấy. Dưới đây là 10 hình ảnh mà applet sẽ sử dụng:

T1.gif: T2.gif: T3.gif: T4.gif: T5.gif:

T6.gif: T7.gif: T8.gif: T9.gif: T10.gif:

Mã của ví dụ này có trong tập tin ImageSequenceTimer.java, ví dụ này đơn giản hơn ví dụ vừa mô tả ở trên, chỉ đơn giản là tạo một vòng lặp để hiển thị thứ tự hết hình này đến hình kia thay vì di chuyển một hình ảnh. Dưới đây là sự khác biệt đó:

. . .//In initialization code:

Image[] images = new Image[10]; for (int i = 1; i <= 10; i++) {

images[i-1] = getImage(getCodeBase(), "images/duke/T"+i+".gif"); }

. . .//In the paintComponent method:

g.drawImage(images[ImageSequenceTimer.frameNumber % 10], 0, 0, this);

Cách khác để thực hiện ví dụ này là dùng một label để hiển thị các hình ảnh. Thay vì sử dụng đoạn lệnh để vẽ lại hình thì ta dùng phương thức setIcon để thay đổi hình được hiển thị.

Cải tiến hình thức và hiển thị chủa hoạt hình

Lưu ý hai việc trong vấn đề hoạt cảnh ở trên:

• Trong khi một bức ảnh đang được nạp, chương trình sẽ hiển thị một phần của toàn bộ bức ảnh, các phần khác có thể chưa được hiển thị.

• Nạp một bức ảnh sẽ cần một thời gian tương đối dài.

Sử dụng lớp MediaTracker có thể giải quyết được vấn đề về hiển thị hình ảnh. MediaTracker còn có thể giảm thiểu lượng thời gian để nạp hình ảnh. Cách khác để cải tiến thời gian nạp hình là thay đổi dạng thức của tập tin ảnh. Trong phần này sẽ đề cập tới vấn đề này.

Sử dụng MediaTracker để tải ảnh và hiển thị ảnh

Lớp MediaTracker cho phép nạp dữ liệu của một nhóm các tập tin ảnh và kết thúc khi hình ảnh đã được nạp đầy đủ. Nói chung, dữ liệu của một hình ảnh chưa được tải về khi nó được vẽ trong lần đầu tiên. Để yêu cầu dữ liệu của các hình ảnh được chuẩn bị trước để tải về, ta có thể sử dụng các phương thức của MediaTracker như sau: checkID(anInt, true) hoặc checkAll(true). Để nạp dữ liệu về một cách đồng bộ, sử dụng phương thức waitForID hoặc waitForAll. Phương thức

MediaTracker sử dụng tiến trình của hệ thống để tải dữ liệu về, do đó có thể tăng tốc độ của đường truyền.

Để kiểm tra trạng thái của việc nạp dữ liệu về, ta dùng phương thức MediaTracker statusID hoặc

statusAll. Cách đơn giản nhất để kiểm tra xem dữ liệu của hình ảnh có đang được tải về hay không thì

dùng phương thức checkID hoặc checkAll.

Chương trình MTImageSequenceTimer.java là một ví dụ về việc sử dụng phương thức MediaTracker

waitForAll và checkAll. Applet vẫn hiển thị dòng chữ "Please wait..." cho đến khi tất cả các hình ảnh

đều được nạp đầy đủ.

Những thay đổi về mã dưới đây sử dụng MediaTracker để hiển thị hình ảnh. Những sự khác nhau được in đậm.

...//Where instance variables are declared:

MediaTracker tracker;

tracker = new MediaTracker(this);

...//In the init method:

for (int i = 1; i <= 10; i++) {

images[i-1] = getImage(getCodeBase(),

"images/duke/T"+i+".gif"); }

...//In the buildUI method,

//which is called by init and main, //allowing us to run the sample //as an applet or an application:

for (int i = 1; i <= 10; i++) {

tracker.addImage(images[i-1], 0);

}

...//At the beginning of the actionPerformed method:

try {

//Start downloading the images. Wait until they're loaded. tracker.waitForAll();

} catch (InterruptedException e) {}

...//In the paintComponent method:

//If not all the images are loaded, just clear the background //and display a status string.

if (!tracker.checkAll()) {

g.clearRect(0, 0, d.width, d.height);

g.drawString("Please wait...", 0, d.height/2); }

//If all images are loaded, paint. else {

...//same code as before... }

Tăng tốc độ nạp ảnh

Cho dù có hay không sử dụng MediaTracker, việc nạp hình ảnh sử dụng URLs (cách các applets thờng làm) luôn luôn tốn nhiều thời gian. Hầu hết thời gian ấy là để khởi tạo sự kết nối HTTP. Mỗi một tập tin hình ảnh đòi hỏi một kết nối HTTP khác nhau, và mỗi một kết nối ấy có thể tiêu tốn vài giây để khởi tạo. Cho nên, thời gian kéo dài là chuyện đương nhiên.

Cách thức để tránh xãy ra phiền phức trên là nên đặt tất cả các hình ảnh vào trong một tập tin ảnh. Có thể sử dụng tập tin JAR để thực hiện điều này.

Một phần của tài liệu Tổng quan về Java UI pptx (Trang 45 - 49)

Tải bản đầy đủ (DOC)

(49 trang)
w