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 vòng lặp cho hoạt cảnh với đối tượng Timer(Creating an Animation Loop with Timer)
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();
...
}
Tạo chuyển động cho một hình ảnh trên màn hình (Moving an Image Across the Screen)
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.
rocketship.gif:
starfield.gif:
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.
This is a picture of the applet's GUI. To run the applet, click the picture. The applet will appear in a new browser window.
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);
}
//If we have a valid width and height for the //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 thi tuần tự các hình ảnh (Displaying a Sequence of Images)
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 giao diện và thực hiện hoạt cảnh (Improving the Appearance and Performance of Image Animation)
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 để nạp và nạp hình ảnh (Using MediaTracker to Download Images and Delay Image Display)
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:
//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 việc nạp hình ảnh (Speeding Up Image Loading)
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.
5. Giải quyết các vấn đề về đồ hoạ
Mô tả một vài vấn đề liên quan đến đồ hoạ, giải pháp để giải quyết các vấn đề
này.
Bài 7: Chuyển đổi qua Swing
Trong bài này sẽ trình bày cách thức để chuyển đổi một chương trình từ AWT sang sử dụng các thành phần Swing. Nếu một chương trình được viết để sử dụng với JDK 1.0, nghĩa là thay vì sử dụng hệ thống các sự kiện listening được giới thiệu trong JDK 1.1 thì lại sử dụng các phương thức như là handleEvent và
action, lúc đó, điều trước tiên là chuyển đổi chương trình để sử dụng hệ thống các sự kiện mới hơn.