vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 2

Tag 10


Bilder, Sound und Animation

Den ersten Kontakt mit Java stellte für die meisten Leute animierter Text oder bewegte Bilder auf einer Webseite dar. Diese Animationsarten sind einfach, da sie hierfür nur wenige Methoden in Java implementieren müssen. Diese Methoden sind allerdings die Basis für jedes Applet, das den Bildschirminhalt dynamisch aktualisiert. Mit einfacher Animation zu beginnen, stellt einen guten Weg dar, komplexere Applets zu erstellen.

Animationen werden unter Java mit bestimmten Klassen und Methoden des Abstract Windowing Toolkit (AWT) umgesetzt. Heute lernen Sie, wie die verschiedenen Teile von Java zusammenarbeiten, so daß Sie bewegte Bilder erstellen und Applets dynamisch aktualisieren können.

Animationen zu erzeugen macht Spaß und ist in Java einfach. Es läßt sich schon mit den integrierten Methoden von Java für Linien, Schriften und Farben sehr viel machen. Für interessante Animationen benötigen Sie ein eigenes Bild für jedes Einzelbild der Animation - Sound dazu zu haben ist auch nett.

Heute lernen Sie die folgenden Themen kennen:

Animationen unter Java erstellen

Animationen sind unter Java ein verhältnismäßig einfacher Prozeß, der die folgenden Schritte umfaßt:

Dieses Schritte werden mit unterschiedlichen Dingen, die gezeichnet werden sollen, wiederholt, so daß der Eindruck der Bewegung entsteht. Sie können das Zeitintervall zwischen den Einzelbildern der Animation verändern oder Java anweisen, diese so schnell wie möglich auszugeben.

Zeichnen und Neuzeichnen

Wie Sie bereits gelernt haben, wird die paint()-Methode aufgerufen, wenn der Ausgabebereich eines Applets neu gezeichnet werden muß. Diese Methode wird aufgerufen, wenn ein Applet startet, da das Applet-Fenster leer ist und der Inhalt das erste Mal dargestellt werden muß. Sie wird auch aufgerufen, wenn das Applet-Fenster in den Vordergrund kommt, nachdem es von dem Fenster eines anderen Programms verdeckt wurde.

Sie können das Ausgabesystem von Java bitten, das Fenster neu zu zeichnen, indem Sie die Methode repaint() aufrufen.


Diese höfliche Ausdrucksform wurde hier aus einem ganz bestimmten Grund gewählt - repaint() stellt nämlich wirklich eher eine Anfrage als ein Kommando dar. Das Ausgabesystem von Java erhält diese Anfrage und verarbeitet sie, sobald dies möglich ist. Sollten die repaint()-Anfragen schneller auflaufen, als Java diese verarbeiten kann, werden eventuell einige übersprungen. In den meisten Fällen ist die Verzögerung zwischen dem Aufruf von repaint() und der eigentlichen Aktualisierung des Fensters vernachlässigbar.

Um das Erscheinungsbild dessen, was in einem Applet-Fenster angezeigt wird, zu ändern, zeichnen Sie die gewünschten Dinge und rufen repaint() auf. Anschließend zeichnen Sie etwas anderes und rufen repaint() erneut auf usw.

All dies findet nicht in der paint()-Methode statt, da diese nur für die Ausgabe eines Einzelbildes verantwortlich ist - nämlich das aktuelle. Die eigentliche Arbeit wird an anderer Stelle im Applet verrichtet.

An dieser anderen Stelle, die durchaus eine eigene Methode sein könnte, erstellen Sie Objekte, zeichnen diese und verrichten andere notwendige Aufgaben. Abschließend rufen Sie die Methode repaint() auf.


Obwohl Sie die Methode paint() auch selbst aufrufen können, sollten Sie für alle Anfragen zum Zeichnen des Ausgabebereiches Aufrufe der Methode repaint() verwenden. Die repaint()-Methode ist leichter anzuwenden - sie benötigt kein Graphics- Objekt als Parameter, wie das bei paint() der Fall ist - und Sie kümmert sich um alles, was zur Aktualisierung des Anzeigebereiches nötig ist. Sie werden dies später am heutigen Tag sehen, wenn Sie repaint() verwenden, um eine Animationssequenz zu erstellen.

Ein Applet starten und stoppen

Wie Sie sich von Tag 8 her erinnern werden, wird die start()-Methode beim Start eines Applets und die stop()-Methode beim Beenden eines Applets aufgerufen.

Diese Methoden sind leer, wenn Sie sie von der Klasse java.applet.Applet erben, so daß Sie diese überschreiben müssen, damit sie beim Start bzw. beim Beenden Ihres Programms etwas tun. Sie haben start() und stop() gestern nicht verwendet, da Sie die Applets paint() nur einmal verwendet haben.

Bei Animationen und anderen Java-Applets, die längere Zeit laufen, werden start() und stop() benötigt, um den Start Ihres Applets auszulösen und die Ausführung wieder zu beenden, sobald die Seite, auf der sich das Applet befindet, verlassen wird.

Animationen über Threads kontrollieren

Animationen stellen eine ideale Anwendung für Threads dar, Javas Möglichkeit für die Verarbeitung von mehr als einer Aufgabe zur selben Zeit.


Ein Thread ist ein Teil eines Programms, der eingerichtet wird, um eigenständig zu laufen, während der Rest des Programms etwas anderes tut. Dies wird auch als Multitasking bezeichnet, da das Programm mehr als eine Aufgabe zur selben Zeit ausführen kann.

Threads sind ideal für alles, was viel Rechenzeit in Anspruch nimmt und kontinuierlich ausgeführt wird, wie z.B. die wiederholten Zeichenoperationen, die eine Animation ausmachen.

Indem Sie die Arbeitslast der Animation in einen Thread packen, machen Sie den Weg dafür frei, daß sich der Rest des Programms mit anderen Dingen beschäftigen kann. Sie machen es auch für die Laufzeitumgebung des Applets einfacher, das Programm zu verarbeiten, da die gesamte Rechen- und zeitintensive Arbeit in ihrem eigenen Thread isoliert ist.

Applets mit Threads schreiben

Um einen Thread in einem Applet zu verwenden, können Sie fünf Veränderungen an dessen Quellcode vornehmen:

Das Schlüsselwort implements ähnelt dem Schlüsselwort extends, da es die Klasse verändert, die in derselben Zeile deklariert wird. Im folgenden sehen Sie ein Beispiel für eine Klasse, die sowohl extends als auch implements verwendet:

public class DancingBaby extends java.applet.Applet
    implements Runnable {
   // ...
}

Obwohl die Deklaration der Klasse in zwei Zeilen aufgeteilt wurde, deklariert alles vom Schlüsselwort public bis hin zur geschweiften Klammer »{« die Klasse.

Runnable ist eine besondere Art von Klasse, die als Schnittstelle bezeichnet wird. Wie Sie sich von vielleicht von Tag 2 her erinnern werden, stellen Schnittstellen für Klassen einen Weg dar, Methoden zu erben, die sie ansonsten nicht von deren Superklasse erben könnten.

Diese Methoden können von jeder beliebigen Klasse implementiert werden, die diese Verhaltensweisen benötigt. In diesem Beispiel wird die Runnable-Schnittstelle von einer Klasse implementiert, die als Thread arbeiten soll. Runnable bietet eine Deklaration für die Methode run(), die für den Start eines Threads aufgerufen wird.

Die Thread-Klasse ist Bestandteil des Paketes java.lang, so daß Sie diese nicht über eine import-Anweisung verfügbar machen müssen. Der Anfang bei der Erstellung eines Threads ist sehr einfach - lediglich die Vergabe eines Namens ist erforderlich. Dies ist im folgenden Beispiel gezeigt:

Thread runner;

Dieses Objekt kann in der start()-Methode des Applets erzeugt werden. Die Variable runner hat den Wert null, bis das Objekt erzeugt wird.

Der ideale Ort, es zu erzeugen, ist die start()-Methode des Applets. Die folgende Methode prüft, ob der Thread bereits erzeugt wurde. Ist dies nicht der Fall, erzeugt sie diesen:

public void start() {
    if (runner == null) {
        runner = new Thread(this);
        runner.start();
    }
}

Das Schlüsselwort this, das im Konstruktor Thread() verwendet wird, ist eine Möglichkeit, sich auf das Objekt, das die Methode ausführt - das Applet selbst in diesem Fall - zu beziehen. Indem Sie this verwenden, wird das Applet als die Klasse identifiziert, die die benötigten Verhaltensweisen für die Ausführung des Threads enthält.

Um einen Thread auszuführen, rufen Sie dessen start()-Methode auf, wie das in der folgenden Anweisung aus vorherigem Beispiel der Fall ist:

runner.start();

Der Aufruf der start()-Methode des Threads hat zur Folge, daß eine weitere Methode aufgerufen wird - die run()-Methode der Klasse, die den Thread beinhaltet.

In diesem Beispiel implementiert das Applet die Runnable-Schnittstelle und wurde mit dem runner-Objekt über das Schlüsselwort this verknüpft. Eine Methode mit dem Namen run() muß in das Applet eingefügt werden. Im folgenden ein Beispiel:

public void run() {
    // was Ihr Applet eigentlich tut
}

Die run()-Methode ist das Herz eines Applets, das mit Threads arbeitet. Sie sollte dazu verwendet werden, eine Animationssequenz zu steuern. Hier sollte alles vorgenommen werden, was für die Zeichnungen und zum Ändern der Dinge zwischen den Einzelbildern nötig ist.

Nachdem die run()-Methode mit allen Verhaltensweisen, die der Thread benötigt, ausgestattet wurde, ist der letzte Schritt dabei, das Applet threadfähig zu machen und den Thread über seine stop()-Methode zu beenden.

Einen Thread stoppen Sie, indem Sie sein Objekt auf null setzen. Dies beendet den Thread nicht. Allerdings können Sie die run()-Methode so gestalten, daß sie nur läuft, solange das Thread-Objekt verschieden von null ist.


Es gibt eine stop()-Methode, die zum Beenden von Threads aufgerufen werden kann. JavaSoft hat diese allerdings mit der Version 1.2 von Java verworfen. Durch die Verwendung der stop()-Methode des Threads entstehen Instabilitäten in der Laufzeitumgebung des Programms. Außerdem kann es bei dessen Ausführung zu Fehlern kommen, die nur schwer aufzudecken sind. Es wird den Programmierern stark davon abgeraten, mit stop() einen Thread unter Java zu stoppen, sogar in Java 1.02- und Java- 1.1-Programmen.

Indem Sie implements Runnable hinzufügen, ein Thread-Objekt erzeugen, das dem Applet zugeordnet ist, und die Methoden start(), stop() und run() des Applets verwenden, wird ein Applet zu einem Programm, das Threads verwendet.

Die Teile zusammenfügen

Die Programmierung mit Threads sollte klarer werden, wenn Sie dies direkt in Aktion sehen. Listing 10.1 beinhaltet ein einfaches animiertes Applet, das das Datum und die Zeit anzeigt. Die Darstellung wird in konstanten Intervallen aktualisiert. Dadurch ergibt sich eine Digitaluhr (siehe Abbildung 10.1).


Abbildung 10.1:
Das DigitalClock-Applet im Netscape Navigator

Dieses Applet verwendet die Methoden paint(), start() und stop(). Außerdem verwendet es Threads.

Listing 10.1: Der gesamte Quelltext von DigitalClock.java

 1: import java.awt.Graphics;
 2: import java.awt.Font;
 3: import java.util.Date;
 4:
 5: public class DigitalClock extends java.applet.Applet
 6:     implements Runnable {
 7:
 8:     Font theFont = new Font("TimesRoman",Font.BOLD,24);
 9:     Date theDate;
10:     Thread runner;
11:
12:     public void start() {
13:         if (runner == null) {
14:             runner = new Thread(this);
15:             runner.start();
16:         }
17:     }
18:
19:     public void stop() {
20:         if (runner != null) {
21:             runner = null;
22:         }
23:     }
24:
25:     public void run() {
26:         Thread thisThread = Thread.currentThread();
27:         while (runner == thisThread) {
28:             repaint();
29:             try {
30:                 Thread.sleep(1000);
31:             } catch (InterruptedException e) { }
32:         }
33:     }
34:
35:     public void paint(Graphics screen) {
36:         theDate = new Date();
37:         screen.setFont(theFont);
38:         screen.drawString("" + theDate.toString(), 10, 50);
39:     }
40: }

Um das Applet zu testen, fügen Sie es auf einer Webseite in einem Applet-Fenster mit den folgenden Attributen ein: width=380 und height=100.


Dieses Applet verwendet die Date-Klasse, um das aktuelle Datum und die Uhrzeit zu ermitteln. Dadurch wird das Applet kompatibel zu Java 1.02. In den neueren Versionen der Sprache sollten die Klassen Calendar und GregorianCalendar verwendet werden, da diese eine bessere Unterstützung internationaler Kalendersysteme bieten. Auf der CD zum Buch finden Sie eine Java-1.2-konforme Version des DigitalClock-Applets (DigitalClock12.java)

Animationen sind ein gutes Beispiel für die Art der Aufgaben, die einen eigenen Thread benötigen. Sehen Sie sich einmal die endlose while-Schleife im DigitalClock- Applet an. Wenn Sie nicht mit Threads arbeiten würden, würde die while-Schleife im Standard-Java-System-Thread laufen, der auch für die Ausgabe auf dem Bildschirm, die Verarbeitung von Benutzereingaben, wie z.B. Mausklicks, und dafür, daß intern alles aktuell ist, verantwortlich ist. Unglücklicherweise reißt die while-Schleife alle Java- Ressourcen an sich, wenn sie im Hauptsystem-Thread ausgeführt wird, und hält alles andere - inklusive der Bildschirmausgabe - davon ab, ausgeführt zu werden. Sie würden nichts auf dem Bildschirm sehen, da Java abwarten würde, bis die while-Schleife verarbeitet ist, bevor es irgend etwas anderes tut.

Sie betrachten in diesem Abschnitt das Applet aus der Perspektive der Teile der eigentlichen Animation. Anschließend werden Sie sich mit den Teilen beschäftigen, die die Threads verwalten.

Die Zeilen 8-9 definieren zwei Instanzvariablen: theFont und theDate. Diese nehmen Objekte auf, die die aktuelle Schrift bzw. das aktuelle Datum repräsentieren. Darüber erfahren Sie später mehr.

Die Methoden start() und stop() starten bzw. stoppen den Thread. Der wesentliche Teil der Arbeit wird in der run()-Methode (Zeilen 25-33) ausgeführt.

Innerhalb der run()-Methode findet die eigentliche Animation statt. Sehen Sie sich die while-Schleife in dieser Methode an (beginnend mit der Anweisung in Zeile 27). Der Ausdruck runner == thisThread gibt den Wert true zurück, bis das Objekt runner auf null gesetzt wird (dies geschieht in der stop()-Methode des Applets). In der Schleife wird ein Einzelbild der Animation erstellt.

Als erstes wird in der Schleife die Methode repaint() aufgerufen (Zeile 28), um das Applet neu auszugeben. In den Zeilen 29-31, so kompliziert diese auch erscheinen mögen, passiert nichts anderes, als daß vor der nächsten Schleifenwiederholung eine Pause von 1000 Millisekunden (1 Sekunde) eingelegt wird.

Die sleep()-Methode der Klasse Thread sorgt dafür, daß das Applet pausiert. Ohne die sleep()-Methode würde das Applet so schnell wie möglich ausgeführt werden. Die sleep()-Methode kontrolliert genau, wie schnell die Animation abläuft. Die try- und catch-Sachen rundherum ermöglichen es Java, Fehler, falls welche auftreten, zu behandeln. Diese Anweisungen werden an Tag 17 beschrieben.

In der paint()-Methode in den Zeilen 35-39 wird eine neue Instanz der Klasse Date erzeugt. Diese enthält das aktuelle Datum und die aktuelle Uhrzeit - beachten Sie bitte, daß diese Klasse in der Zeile 3 explizit importiert wurde. Dieses neue Date-Objekt wird der Instanzvariablen theDate zugewiesen.

In der Zeile 37 wird die aktuelle Schrift gesetzt. Dazu wird der Wert der Variablen theFont verwendet. Außerdem wird das Datum auf dem Bildschirm ausgegeben - beachten Sie bitte, daß Sie die Methode toString() der Klasse Date aufrufen müssen, um das Datum und die Zeit als String anzeigen zu lassen. Jedesmal, wenn paint() aufgerufen wird, wird ein neues theDate-Objekt erzeugt, das das aktuelle Datum und die Uhrzeit enthält.

Betrachten Sie nun die Code-Zeilen dieses Applets, die den Thread erzeugen und verwalten. Werfen Sie als erstes einen Blick auf die Deklaration der Klasse selbst in den Zeilen 5-6. Beachten Sie, daß die Klassendeklaration die Schnittstelle Runnable implementiert. Jede Klasse, die Sie erstellen und die Threads verwendet, muß Runnable beinhalten.

Zeile 10 definiert eine dritte Instanzvariable für diese Klasse, die runner genannt wird und den Typ Thread hat. Diese nimmt das Thread-Objekt für dieses Applet auf.

In den Zeilen 12-23 werden die geerbten Methoden start() und stop() definiert. Diese tun nichts, außer Threads zu erzeugen und zu zerstören. Diese Methodendefinitionen werden bei den meisten Klassen ähnlich sein, da sie lediglich die Infrastruktur die Threads, die von einem Programm verwendet werden, einrichten.

Zum Schluß noch die run()-Methode, in der die meiste Arbeit in Ihrem Applet verrichtet wird (Zeilen 25-33).

Das Flimmern in Animationen reduzieren

Wenn das DigitalClock-Applet ausgeführt wird, sehen Sie gelegentlich ein Flimmern in dem Text, den es anzeigt. Das Ausmaß des Flimmerns hängt von der Qualität der Java-Laufzeitumgebung ab, in der das Programm ausgeführt wird, wie auch von der Prozessorgeschwindigkeit. Allerdings ist es wahrscheinlich selbst auf schnellen PCs mit gut implementierter Java Virtual Machine störend.

Flimmern ist einer der Nebeneffekte der Art, wie der Ausgabebereich in einem Java- Programm aktualisiert wird. Und es ist eines der Probleme, auf die Sie bei der Erzeugung einer Animation stoßen werden.

Flimmern und wie Sie es vermeiden

Flimmern wird von der Art, wie Java jedes Einzelbild einer Animation darstellt, verursacht. Zu Beginn der heutigen Lektion haben Sie gelernt, daß bei einem Aufruf der repaint()-Methode diese die Methode paint() aufruft.

Tatsächlich ist hier aber noch ein Mittelsmann beteiligt. Wenn repaint() aufgerufen wird, ruft sie die Methode update() auf, die das Applet-Fenster von allen vorhandenen Inhalten befreit, indem Sie es mit dessen aktueller Hintergrundfarbe füllt. Die update() -Methode ruft anschließend die paint()-Methode auf.

Das Löschen des Bildschirminhalts in der Methode update() ist der Übeltäter in bezug auf das Flimmerproblem. Da das Applet-Fenster zwischen den Einzelbildern gelöscht wird, springen die Bereiche des Applet-Fensters, die sich nicht ändern, kurz zwischen dem Zustand gelöscht und neugezeichnet hin und her - mit anderen Worten sie flimmern.

Es gibt zwei Hauptmethoden, um das Flimmern in Ihren Java-Applets zu vermeiden:

Der einfachste Weg das Flimmern zu reduzieren, ist, die update()-Methode so zu überschreiben, daß sie den Bildschirm nicht löscht. Der erfolgreichste Weg, sich des Problems anzunehmen, ist allerdings die doppelte Pufferung.

So überschreiben Sie update()

Die standard update()-Methode jedes Applets hat die folgende Form:

public void update(Graphics g) {
    g.setColor(getBackground());
    g.fillRect(0, 0, size().width, size().height);
    g.setColor(getForeground());
    paint(g);
}

Die update()-Methode löscht den Bildschirm, indem sie das Applet-Fenster mit der Hintergrundfarbe füllt, die aktuelle Farbe auf die Vordergrundfarbe setzt und anschließend paint() aufruft. Wenn Sie update() mit Ihrer eigenen Version überschreiben, müssen Sie sicherstellen, daß Ihre Version etwas Ähnliches macht. In den beiden folgenden Abschnitten arbeiten Sie ein paar Beispiele durch, in denen Sie update() überschreiben, um das Flimmern zu reduzieren.

Lösung eins: Löschen Sie den Bildschirm nicht

Die erste Lösung, um das Flimmern zu reduzieren, ist, den Bildschirm überhaupt nicht zu löschen. Diese Lösung funktioniert natürlich nur bei wenigen Applets. Das ColorSwirl-Applet z.B. zeigt einen einzigen String (Look to the cookie!) an. Dieser String wird allerdings in unterschiedlichen Farben angezeigt, die dynamisch in andere übergehen. Dieses Applet flimmert füchterlich, wenn man es ausführt. In Listing 10.2 sehen Sie den ursprünglichen Quellcode für dieses Applet, und Abbildung 10.2 zeigt das Ergebnis.


Abbildung 10.2:
Die Ausgabe des ColorSwirl-Applets im Netscape Navigator

Listing 10.2: Der gesamte Quelltext von ColorSwirl.java

 1: import java.awt.Graphics;
 2: import java.awt.Color;
 3: import java.awt.Font;
 4:
 5: public class ColorSwirl extends java.applet.Applet
 6:     implements Runnable {
 7:
 8:     Font f = new Font("TimesRoman", Font.BOLD, 48);
 9:     Color colors[] = new Color[50];
10:     Thread runner;
11:
12:     public void start() {
13:         if (runner == null) {
14:             runner = new Thread(this);
15:             runner.start();
16:         }
17:     }
18:
19:     public void stop() {
20:         runner = null;
21:     }
22:
23:     public void run() {
24:         // Das Array der Farben initialisieren
25:         float c = 0;
26:         for (int i = 0; i < colors.length; i++) {
27:             colors[i] =
28:             Color.getHSBColor(c, (float)1.0,(float)1.0);
29:             c += .02;
30:         }
31:
32:         // Die einzelnen Farben durchgehen
33:         int i = 0;
34:         Thread thisThread = Thread.currentThread();
35:         while (runner == thisThread) {
36:             setForeground(colors[i]);
37:             repaint();
38:
39:             i++;
40:             try {
41:                 Thread.sleep(200);
42:             } catch (InterruptedException e) { }
43:             if (i == colors.length ) i = 0;
44:         }
45:     }
46:
47:     public void paint(Graphics screen) {
48:         screen.setFont(f);
49:         screen.drawString("Look to the Cookie!", 15, 50);
50:     }
51: }

Um dieses Applet zu testen, fügen Sie es in eine Webseite ein mit den folgenden Größen-Attributen im <APPLET>-Tag: height=150 width=450. Drei Dinge werden Ihnen bei diesem Applet vielleicht seltsam erscheinen:

Da Sie nun verstehen, was das Applet tut, ist es an der Zeit, das Flimmerproblem zu beheben. Das Flimmern entsteht, da es bei jeder Ausgabe des Applets einen Moment gibt, in dem der Bildschirm gelöscht wird. Anstatt daß der Text nahtlos von Rot zu Rosa nach Violett übergeht, geht er von Rot nach Grau nach Pink nach Grau nach Violett nach Grau usw. über.

Da lediglich das Löschen das Bildschirms dieses Problem verursacht, ist die Lösung einfach: Überschreiben Sie update(), und entfernen Sie den Teil, in dem der Bildschirm gelöscht wird. Es besteht kein Bedarf für das Löschen, da sich hier nichts außer der Farbe des Textes ändert. Dadurch, daß Sie das Löschen des Bildschirms aus update() entfernen, muß update() nur noch die paint()-Methode aufrufen. Im folgenden die update()-Methode, wie Sie in dem überarbeiteten ColorSwirl-Applet aussehen sollte:

public void update(Graphics screen) {
    paint(screen);
}

Indem Sie diese drei Zeilen hinzufügen, beenden Sie das Flimmern des Applets.


Sie finden die erste Version von ColorSwirl.java unter diesem Namen auf der CD des Buches und die verbesserte Version an derselben Stelle mit dem Namen BetterSwirl.java .

Sie lernen heute noch eine andere Methode zur Reduzierung des Flimmerns kennen - eine Technik, die als doppelte Pufferung bezeichnet wird.

Bilder laden und anzeigen

Der elementare Umgang mit Bildern wird unter Java von der Klasse Image geboten, die Teil des Paketes java.awt ist. Wenn Sie mit einem Applet arbeiten, können Sie Methoden der Klassen Applet und Graphics verwenden, um Bilder zu laden und anzuzeigen.

Bilder laden

Um ein Bild in Ihrem Applet anzeigen zu können, müssen Sie dies erst über das World Wide Web in Ihr Java-Programm laden. Bilder werden als separate Dateien außerhalb der .class-Dateien von Java gespeichert. Aus diesem Grund müssen Sie Java mitteilen, wo es diese Dateien findet.

Wenn Sie die Image-Klasse verwenden, muß das Bild im Format .GIF oder .JPG vorliegen.

Eine Adresse im Web wird unter Java von einem URL-Objekt repräsentiert. Das Akronym URL steht für Uniform Resource Locator. Die Klasse URL ist Teil des Paketes java.net, so daß Sie für diese Klasse wie schon bei der Image-Klasse eine import-Anweisung in Ihr Programm einfügen müssen.

Das URL-Objekt wird erzeugt, indem eine Web-Adresse an den Konstruktor der Klasse URL übergeben wird. Im folgenden ein Beispiel:

URL u = new URL("http://www.prefect.com/java21/images/book.gif");

Wenn Sie ein URL-Objekt haben, können Sie es dazu verwenden, ein Image-Objekt zu erzeugen, das die Grafikdatei repräsentiert.

Die Applet-Klasse bietet eine Methode namens getImage(), mit der ein Bild in ein Image-Objekt geladen werden kann. Es gibt zwei Möglichkeiten, diese Methode zu verwenden:

Obwohl der erste Weg einfacher erscheint, ist der zweite der flexiblere. Wenn Sie eine bestimmte Web-Adresse in Ihrem Applet verwenden, müssen Sie das Programm verändern und neu kompilieren, sobald Ihre Web-Site umzieht.

Die Klasse Applet hat zwei Methoden, mit denen man eine Basis-URL erzeugen kann, ohne eine feste Adresse im Programm angeben zu müssen:

Relative Pfadangaben

Der relative Pfad, den Sie als zweites Argument in getImage() verwenden, hängt davon ab, was Sie als erstes Argument verwendet haben.

Nehmen Sie z.B. eine Webseite unter der Adresse http://www.prefect.com/java21/ index.html, die eine Bilddatei mit der URL http://www.prefect.com/java21/ book.gif hat. Um dieses Bild in ein Applet zu laden, könnten Sie die folgende Anweisung verwenden:

Image img = new URL(getDocumentBase(), "book.gif");

Wenn, als weiteres Beispiel, die Bilddatei an die Adresse http://www.prefect.com/ java21/images/book.gif verschoben werden würde, könnten Sie die folgende Anweisung verwenden:

Image img = new URL(getDocumentBase(), "images/book.gif");

Ob Sie getDocumentBase() oder getCodeBase() verwenden, hängt davon ab, ob Sie Ihre Bilder in Unterordnern Ihres Java-Applets oder in Unterordnern der Webseite des Applets speichern.


Wenn Sie getDocumentBase() oder getCodeBase() verwenden, können Sie die Bilder auch laden, wenn Sie das Applet auf Ihrem eigenen Computer testen. Sie müssen es nicht auf einer Site im World Wide Web speichern, um feststellen zu können, ob es funktioniert.

Indem Sie eine dieser Methoden verwenden, machen Sie es möglich, daß das Applet mit seiner Webseite umzieht und Sie keine Änderungen am Programm vornehmen müssen.


Wenn Sie ein Java-Archiv (eine .JAR-Datei) verwenden, um Ihr Applet zum Benutzer zu bringen, können Sie Bilddateien und andere Datendateien in dem Archiv ablegen. Diese Dateien werden aus dem Archiv mit den .class-Dateien in den .JAR-Dateien automatisch extrahiert.

Bilder ausgeben

Nachdem Sie ein Bild in ein Image-Objekt geladen haben, können Sie es in einem Applet mit der Methode drawImage() der Graphics-Klasse anzeigen.

Um ein Bild mit seiner Orginalgröße anzuzeigen, rufen Sie die drawImage()-Methode mit vier Argumenten auf:

Ist eine Grafikdatei in dem img-Objekt gespeichert, kann die folgende paint()-Methode zur Anzeige verwendet werden:

public void paint(Graphics screen) {
    screen.drawImage(img, 10, 10, this);
}

Die x,y-Koordinaten, die der drawImage()-Methode übergeben werden, sind mit den x,y-Koordinaten vergleichbar, die bei der Anzeige eines Rechteckes verwendet werden. Der Punkt repräsentiert die linke, obere Ecke des Bildes.

Sie können ein Bild in einer anderen Größe anzeigen lassen, indem Sie zwei zusätzliche Argumente, also insgesamt sechs Argumente, verwenden:

Über die zwei zusätzlichen Argumente legen Sie die Breite und Höhe in Pixeln, die das Bild bei der Anzeige haben soll, fest. Falls diese nicht der eigentlichen Größe des Bildes entsprechen, wird das Bild skaliert, um diese Vorgaben zu erfüllen. Hierdurch wird das Bild selbst nicht verändert, so daß Sie diverse Aufrufe von drawImage() verwenden können, um ein Image-Objekt in vielen verschiedenen Größen auszugeben.

Zwei Methoden der Image-Klasse sind hilfreich, wenn Sie ein Bild nicht in dessen Originalgröße anzeigen. Die Methode getHeight() gibt die Höhe des Bildes und die Methode getWidth() die Breite als Integer zurück.

Ein Wort zu Image-Observern

Das letzte Argument der drawImage()-Methode ist das Schlüsselwort this. Wie Sie sich von den vorigen Tagen her erinnern werden, kann this innerhalb eines Objekts verwendet werden, um auf das Objekt selbst zu verweisen.

Über das Schlüsselwort this wird in der Methode drawImage() angegeben, daß das Applet den Ladevorgang eines Bildes aus dem World Wide Web verfolgen kann. Das Laden von Bildern wird über die ImageObserver-Schnittstelle verfolgt. Klassen, die diese Schnittstelle implementieren, wie z.B. Applet, können den Ladefortgang eines Bildes verfolgen. Dies ist sehr nützlich, um in einem Programm während des Ladens von Grafikdateien eine Meldung wie »Bilder werden geladen...« anzuzeigen.

Die vorhandene Unterstützung von ImageObserver sollte für einfache Anwendungen in bezug auf Bilder in einem Applet ausreichend sein, so daß das Schlüsselwort this als Argument für drawImage() verwendet wird.

Die Arbeit mit Bildern

Bevor Sie in die Animation von Bildern eintauchen, soll ein einfaches Applet als Beispiel für das Laden eines Bildes von einer URL und die anschließende Anzeige in zwei verschiedenen Größen dienen. Das Applet Fillmore in Listing 10.3 zeigt ein Bild des amerikanischen Präsidenten Millard Fillmore.

Listing 10.3: Der gesamte Quelltext von Fillmore.java

 1: import java.awt.Graphics;
 2: import java.awt.Image;
 3:
 4: public class Fillmore extends java.applet.Applet {
 5:     Image whig;
 6:
 7:     public void init() {
 8:         whig = getImage(getCodeBase(),
 9:             "images/fillmore.jpg");
10:     }
11:
12:     public void paint(Graphics screen) {
13:         int iWidth = whig.getWidth(this);
14:         int iHeight = whig.getHeight(this);
15:         int xPos = 10;
16:         // 25%
17:         screen.drawImage(whig, xPos, 10,
18:             iWidth / 4, iHeight / 4, this);
19:         // 100%
20:         xPos += (iWidth / 4) + 10;
21:         screen.drawImage(whig, xPos, 10, this);
22:     }
23: }

Bevor Sie das Fillmore-Applet testen können, müssen Sie folgendes tun:

Abbildung 10.3 zeigt die Ausgabe des Applets, das die Datei fillmore.jpg in zwei Größen anzeigt: 25 Prozent und 100 Prozent.


Abbildung 10.3:
Das Fillmore-Applet

In Zeile 5 wird die Variable whig der Image-Klasse zugeordnet. Sie müssen die Anweisung new hier nicht verwenden, um ein Image-Objekt zu erzeugen, da die getImage()- Methode in den Zeilen 8-9 ein solches zurückgibt.

Die Zeilen 13-14 verwenden getWidth() und getHeight(), zwei Methoden der Klasse Image, und speichern die zurückgegebenen Werte in Integer-Variablen. Dies ist notwendig, um eine verkleinerte Version des Bildes in den Zeilen 17-18 zu erzeugen.

In der Zeile 15 wird die xPos-Variable definiert, die die x-Koordinate für die beiden Versionen von Präsident Fillmore speichert. In Zeile 20 wird der Wert der Variablen so erhöht, daß sich das große Bild 10 Pixel rechts neben der kleineren Version befindet.

Animationen mit Bildern

Animationen mit Bildern zu erstellen, entspricht vom Prinzip her genau der Animation von Schriften, Farben und anderen Objekten. Sie verwenden dieselben Methoden und dieselbe Vorgehensweise für die Ausgabe, die Aktualisierung der Ausgabe und zur Reduzierung des Flimmerns. Der einzige Unterschied ist, daß Sie eine Reihe von Bildern haben, die Sie durchwechseln, anstelle einer Reihe von Zeichenoperationen.

Der beste Weg darzulegen, wie man Bilder animiert, ist, ein Beispiel durchzuarbeiten. Das kommende Projekt ist das längste, das Sie bisher in diesem Buch hatten. Keine Angst, es wird detailliert beschrieben. Das Neko-Applet ist eine gute Demonstration für die Programmierung mit Threads, den Umgang mit Bildern und Animation.

Ein Beispiel: Neko

Neko ist eine kleine Macintosh-Animation (ein Spiel), das 1989 von Kenji Gotoh geschrieben wurde. »Neko« heißt auf Japanisch »Katze« und die Animation handelt von einer kleinen Katze, die den Mauszeiger über den Bildschirm jagt, schläft, sich kratzt und sich ansonsten nett verhält. Das Neko-Programm ist seitdem auf fast jede denkbare Plattform übertragen worden und steht auch als Bildschirmschoner zur Verfügung.

Für dieses Beispiel implementieren Sie eine kleine Animation, die auf den Originalgrafiken von Neko basiert. Anders als der Original-Neko, der autonom war (er konnte die Ränder des Fensters »spüren«, sich umdrehen und in eine andere Richtung weiterlaufen), zwingt dieses Applet Neko dazu, von der linken Seite des Fensters aus loszulaufen, in der Mitte zu stoppen, zu gähnen, sich am Ohr zu kratzen, ein bißchen zu schlafen und dann nach rechts weiterzulaufen.

Schritt 1: Bilder zusammenstellen

Ehe Sie mit dem Schreiben des Java-Codes beginnen, um die Animation zu erstellen, sollten Sie alle Bilder zur Verfügung haben, aus der die Animation selbst besteht. Für diese Fassung von Neko werden neun Bilder (die Originalversion verwendet 36) benötigt. Diese sind in Abb. 10.4 zu sehen:


Abbildung 10.4:
Die Bilder für das Neko-Applet

Als Vorbereitung für dieses Projekt kopieren Sie die folgenden neun Bilddateien von der CD-ROM des Buches in den Ordner \J21Work\images, den Sie bereits zuvor erzeugt haben: Awake1.gif, Right1.gif, Right2.gif, Scratch1.gif, Scratch2.gif, Sleep1.gif, Sleep2.gif, Stop.gif und Yawn.gif.

Schritt 2: Organisieren und Laden der Bilder im Applet

Doch nun zum Applet. Die Grundidee ist, daß Sie über einen Satz von Bildern verfügen und diese schnell hintereinander ablaufen lassen, damit der Eindruck einer Bewegung entsteht. Die einfachste Möglichkeit, dies in Java zu erreichen, besteht darin, die Bilder in einem Array von Image-Objekten zu speichern und das jeweils aktuelle Bild dann mit Hilfe einer speziellen Variablen anzuzeigen. Für unser Beispiel soll das Array den Namen nekoPics die Variable currentImage haben. Während die einzelnen Elemente des Arrays mit einer for-Schleife durchlaufen werden, können Sie jedes Mal den Wert des aktuellen Bildes ändern.

Für das Applet Neko erstellen Sie Instanzvariablen, um diese beiden Dinge zu implementieren: ein Array für die einzelnen Bilder mit dem Namen nekoPics und eine Variable des Typs Image namens currentImg, welche das aktuelle Bild für die Anzeige enthält:

Image nekoPics[] = new Image[9];
Image currentImg;

Das Bild-Array enthält hier neun Elemente, weil die Neko-Animation über neun Bilder verfügt. Wenn Sie einen größeren oder kleineren Satz von Bildern verwenden, müssen Sie die entsprechende Anzahl der Bilder hier angeben.

Da die Neko-Animation die Katzenbilder an verschiedenen Positionen des Bildschirms zeichnet, müssen Sie auch die aktuellen x- und y-Koordinaten verfolgen, damit Sie die verschiedenen Methoden in diesem Applet erkennen können, wo mit dem Zeichnen begonnen werden soll. Der y-Wert bleibt bei diesem Applet konstant (Neko läuft auf immer der gleichen y-Koordinate (50) von links nach rechts), der x-Wert variiert allerdings. Im folgenden werden für diese beiden Positionen zwei Instanzvariablen deklariert:

int x;
int y = 50;

Doch nun zum Hauptteil des Applet. Während der Initialisierung des Applets werden alle Bilder eingelesen und im Array nekoPics gespeichert. Dazu verwenden Sie einen separaten Aufruf der Methode getImage() für jedes der neun Bilder. Eine etwas weniger redundante Methode ist, ein String-Array mit den Namen der neun Bilddateien zu erzeugen. Dieses Array wird in einer for-Schleife verwendet, um die Dateinamen an die getImage()-Methode zu übergeben. Diese Art Operation läßt sich besonders gut in einer init()-Methode ausführen.

public void init() {
    String nekoSrc[] = { "right1.gif", "right2.gif",
        "stop.gif", "yawn.gif", "scratch1.gif",
        "scratch2.gif", "sleep1.gif", "sleep2.gif",
        "awake.gif" };

    for (int i=0; i < nekoPics.length; i++) {
        nekopics[i] = getImage(getCodeBase(),
            "images/" + nekoSrc[i]);
    }
}

Da die Bilder in dem Unterordner images gespeichert sind, muß dieser Teil des Pfadargumentes in getImage() sein.

Schritt 3: Bilder animieren

Sobald die Bilder geladen sind, besteht der nächste Schritt darin, die Teile des Applets zu animieren. Dies geschieht innerhalb der run()-Methode des Applets.

In diesem Applet führt Neko fünf wesentliche Aktionen aus:

Sie könnten dieses Applet zwar so animieren, daß das richtige Bild zur gegebenen Zeit am Bildschirm gezeichnet wird, es ist aber sinnvoller, das Applet so zu schreiben, daß die Aktivitäten von Neko jeweils in einer einzelnen Methode enthalten sind. Auf diese Weise lassen sich bestimmte Aktivitäten (insbesondere die Animation von Nekos Laufen) wieder verwenden, wenn Neko dies in unterschiedlicher Reihenfolge ausführen soll.

Zu Beginn wird eine Methode erstellt, die Neko zum Laufen bringt. Die Methode nekorun() erwartet zwei Argumente: die x-Position des Ausgangspunktes und die x-Position des Endpunktes. Neko läuft dann zwischen diesen beiden Positionen (der y-Wert bleibt konstant). Hier ist der Anfang der Methode:

void nekorun(int start, int end) {
  // noch zu definieren
}

Es gibt zwei Bilder, die das Laufen von Neko darstellen und den Effekt des Laufens erzielen: Right1.gif und Right2.gif. Sie müssen also zwischen diesen beiden Bildern wechseln (gespeichert an den Positionen 0 und 1 im Bilder-Array) und diese gleichzeitig über den Bildschirm bewegen. Der Bewegungsteil läßt sich am einfachsten mit einer for-Schleife zwischen Anfangs- und Endargument definieren. Dafür wird die x- Position als aktueller Schleifenwert verwendet.

Um die Bilder zu tauschen, wird geprüft, welches Bild aus dem nekoPics-Array sich aktuell im currentImg-Objekt befindet, und das jeweils andere dann zugewiesen. Dies geschieht bei jeder Wiederholung der for-Schleife.

Durch den Aufruf von repaint() wird das Bild, das sich aktuell in currentImg befindet, ausgegeben.

Als letztes müssen Sie in der nekoRun()-Methode noch dafür sorgen, daß in der for- Schleife vor dem Wechsel der Bilder und der Ausgabe des neuen eine Pause eingelegt wird.

Da jede der Methoden für die Bewegungen von Neko eine Pause benötigt, fügen wir dem Applet eine Methode pause(), die wiederverwendet werden kann, hinzu. Diese Methode verwendet die Methode Thread.sleep(), wie im folgenden gezeigt:

void pause(int time) {
    try {
        Thread.sleep(time);
    } catch (InterruptedException e) { }
}

Nachdem der Aufruf von pause() eingefügt ist, hat die nekoRun()-Methode die folgende Definition:

void nekoRun(int start, int end) {
    for (int i = start; i < end; i+=10) {
        x = i;
        // Bilder tauschen
        if (currentImg == nekoPics[0])
            currentImg = nekoPics[1];
        else currentimg = nekoPics[0];
        repaint();
        pause(150);
    }
}

Beachten Sie, daß die Schleife in der zweiten Zeile um 10 Pixel erhöht wird. Warum 10 Pixel und nicht 5 oder 8? Diese Antwort ist überwiegend von Experimenten bestimmt, dabei entdecken Sie, welche Pixel-Anzahl adäquat ist. Zehn ist für diese Animation angemessen. Wenn Sie eigene Animationen erstellen, müssen Sie mit beiden Abständen experimentieren und die Zeit für den Schlaf herausfinden, bis die Animation ihren Vorstellungen entsprechend abläuft.

Sie haben gesehen, daß die nekoRun()-Methode das aktuelle Bild für die Animation in der Variablen currentImg speichert, bevor repaint() aufgerufen wird. Wenden wir uns jetzt der paint()-Methode zu, welche die Einzelbilder der Animation ausgibt. In diesem Fall ist die paint()-Methode sehr einfach. paint() ist im wesentlichen verantwortlich für das Zeichnen des aktuellen Bildes an der aktuellen x- und y-Position. Alle diese Informationen werden in Instanzvariablen gespeichert. Doch ehe mit dem Zeichnen begonnen wird, muß sichergestellt sein, daß die Bilder tatsächlich vorhanden sind (diese können auch gerade noch geladen werden). Um dies festzustellen und sich zu vergewissern, daß kein Bild gezeichnet wird, das nicht vorhanden ist (daraus können die verschiedensten Fehler entstehen), wird ein Test durchgeführt, der sicherstellt, daß currentimg nicht null ist, ehe drawImage() aufgerufen wird, um das Bild zu zeichnen:

public void paint(Graphics screen) {
    if (currentImg != null)
        screen.drawImage(currentImg, x, y, this);
}

Im folgenden wird die run()-Methode betrachtet, in der sich die wesentlichen Verarbeitungsvorgänge für diese Animation abspielen. Sie haben bereits die nekorun()-Methode erstellt; in run() rufen Sie nun diese Methode mit den entsprechenden Werten auf, um Neko vom linken Bildschirmrand zur Mitte laufen zu lassen:

// Neko läuft vom linken Bildschirmrand zur Mitte
nekoRun(0, size().width / 2);


Die size()-Methode der Applet-Klasse wurde nach Java 1.02 verworfen. Wenn Sie also dieses Applet für die aktuelle Java-Version 1.2 schreiben wollen, ersetzen Sie einfach die Methode size() durch getSize(). Die Methode nekoRun() würde dann wie folgt aufgerufen werden:

nekoRun(0, getSize().width / 2);

Das zweitwichtigste Verhalten von Neko in dieser Animation ist das Anhalten und Gähnen. Für alle diese Momente stehen jeweils eigene Bilder (an den Positionen 2 und 3 des Arrays) zur Verfügung, d.h. Sie benötigen keine separaten Methoden, um diese zu zeichnen. Sie müssen lediglich das jeweils passende Bild auswählen, repaint() aufrufen und für die Pause die richtige Zeit einstellen. In diesem Beispiel wurde für jede Pause vor dem Anhalten und Gähnen eine Sekunde eingestellt. Die richtige Zeit wurde durch Experimentieren ermittelt. Im folgenden finden Sie den zugehörigen Code:

// Stoppen und pausieren
currentImg = nekoPics[2];
repaint();
pause(1000);
// gähnen
currentImg = nekoPics[3];
repaint();
pause(1000);

Und nun zum dritten Teil der Animation: Nekos Kratzen. Für diesen Teil ist keine horizontale Bewegung definiert. Sie wechselt zwischen zwei verschiedenen Kratz-Bildern (an den Positionen 4 und 5 des Bilder-Arrays). Da das Kratzen jedoch eine eigene Aktion ist, soll hierfür auch eine eigene Methode verwendet werden (nekoScratch()).

Die nekoscratch()-Methode enthält ein einziges Argument: die Häufigkeit des Kratzens. Mit diesem Argument können Sie Wiederholungen definieren und innerhalb der Schleife zwischen den beiden verschiedenen Bildern wechseln und diese jeweils neu zeichnen lassen:

void nekoScratch(int numTimes) {
    for (int i = numTimes; i > 0; i--) {
        currentImg = nekoPics[4];
        repaint();
        pause(150);
        currentImg = nekoPics[5];
        repaint();
        pause(150);
    }
}

Innerhalb der run()-Methode können Sie nekoscratch() mit dem Argument 4 aufrufen:

// Viermal kratzen
nekoScratch(4);

Weiter geht`s! Nachdem sich Neko gekratzt hat, schläft er. Auch hierfür benötigen Sie zwei Bilder (an den Positionen 6 und 7 des Arrays), der Wechsel zwischen den Bildern wiederholt sich jeweils mit einer festgelegten Häufigkeit, gefolgt von einer Pause von 150 Millisekunden. Im folgenden finden Sie die nekosleep()-Methode, die ein einziges Argument erwartet. Dieses Argument gibt an, wie oft die Sequenz wiederholt wird:

void nekoSleep(int numTimes) {
    for (int i = numTimes; i > 0; i--) {
        currentImg = nekoPics[6];
        repaint();
        pause(250);
        currentImg = nekoPics[7];
        repaint();
        pause(250);
    }
}

Die nekoSleep()-Methode wird in der run()-Methode des Applets mit dem Argument 5 aufgerufen:

// 5 "Durchläufe" lang schlafen
nekoSleep(5);

Am Ende des Applet wacht Neko auf und läuft zur rechten Seite des Bildschirms. Das Bild für das Aufwachen ist das letzte Bild im Array (nekoPics[8]), und Sie können hierfür erneut die nekorun()-Methode verwenden:

// aufwachen und weglaufen
currentImg = nekoPics[8];
repaint();
pause(500);
nekoRun(x, size().width + 10);

Schritt 4: Applet fertigstellen

Es gibt noch eine Sache, die zur Fertigstellung des Applets notwendig ist. Die Bilder für die Animation verfügen alle über einen weißen Hintergrund. Wenn Sie diese Bilder auf dem Standardhintergrund von Applets (ein Mittelgrau) zeichnen, entsteht ein nicht sehr attraktives weißes Feld um die einzelnen Bilder.

Um dieses Problem zu beheben, definieren Sie einfach die Hintergrundfarbe des Applets am Anfang der run()-Methode als Weiß:

setBackground(Color.white);

Dieses Applet enthält viel Code und viele einzelne Methoden, mit denen eine relativ einfache Animation ausgeführt wird, aber im Grunde ist es nicht kompliziert. Der Kern aller Animationen in Java besteht darin, die Einzelbilder aufzubauen und dann repaint() aufzurufen, um das Zeichnen am Bildschirm zu ermöglichen.

Beachten Sie, daß in diesem Applet keine Schritte unternommen werden, um den Flimmereffekt zu reduzieren. Es hat sich herausgestellt, daß die Bilder dieses Applets und die Zeichenfläche so klein sind, daß das Flimmern hier nicht zum Problem wird. Wenn Sie eine Animation schreiben, sollten Sie die grundlegenden Dinge zuerst erledigen und dann zusätzliche Verhalten einfügen, um den Ablauf zu optimieren.

Um diesen Abschnitt abzuschließen, zeigt Listing 10.4 den kompletten Code für das Neko-Applet.

Listing 10.4: Der gesamte Quelltext von Neko.java

  1: import java.awt.Graphics;
  2: import java.awt.Image;
  3: import java.awt.Color;
  4:
  5: public class Neko extends java.applet.Applet
  6:     implements Runnable {
  7:
  8:     Image nekoPics[] = new Image[9];
  9:     Image currentImg;
 10:     Thread runner;
 11:     int x;
 12:     int y = 50;
 13:
 14:     public void init() {
 15:         String nekoSrc[] = { "right1.gif", "right2.gif",
 16:             "stop.gif", "yawn.gif", "scratch1.gif",
 17:             "scratch2.gif","sleep1.gif", "sleep2.gif",
 18:             "awake.gif" };
 19:
 20:         for (int i=0; i < nekoPics.length; i++) {
 21:             nekoPics[i] = getImage(getCodeBase(),
 22:                 "images/" + nekoSrc[i]);
 23:         }
 24:     }
 25:
 26:     public void start() {
 27:         if (runner == null) {
 28:             runner = new Thread(this);
 29:             runner.start();
 30:         }
 31:     }
 32:
 33:     public void stop() {
 34:         runner = null;
 35:     }
 36:
 37:     public void run() {
 38:         setBackground(Color.white);
 39:         // Neko läuft vom linken Bildschirmrand zur Mitte
 40:         nekoRun(0, size().width / 2);
 41:         // Stoppen und pausieren
 42:         currentImg = nekoPics[2];
 43:         repaint();
 44:         pause(1000);
 45:         // gähnen
 46:         currentImg = nekoPics[3];
 47:         repaint();
 48:         pause(1000);
 49:         // Viermal kratzen
 50:         nekoScratch(4);
 51:         // 5 "Durchläufe" lang schlafen
 52:         nekoSleep(5);
 53:         // aufwachen und weglaufen
 54:         currentImg = nekoPics[8];
 55:         repaint();
 56:         pause(500);
 57:         nekoRun(x, size().width + 10);
 58:     }
 59:
 60:     void nekoRun(int start, int end) {
 61:         for (int i = start; i < end; i += 10) {
 62:             x = i;
 63:             // Bilder tauschen
 64:             if (currentImg == nekoPics[0])
 65:                 currentImg = nekoPics[1];
 66:             else currentImg = nekoPics[0];
 67:             repaint();
 68:             pause(150);
 69:         }
 70:     }
 71:
 72:     void nekoScratch(int numTimes) {
 73:         for (int i = numTimes; i > 0; i--) {
 74:             currentImg = nekoPics[4];
 75:             repaint();
 76:             pause(150);
 77:             currentImg = nekoPics[5];
 78:             repaint();
 79:             pause(150);
 80:         }
 81:     }
 82:
 83:     void nekoSleep(int numTimes) {
 84:         for (int i = numTimes; i > 0; i--) {
 85:             currentImg = nekoPics[6];
 86:             repaint();
 87:             pause(250);
 88:             currentImg = nekoPics[7];
 89:             repaint();
 90:             pause(250);
 91:         }
 92:     }
 93:
 94:     void pause(int time) {
 95:         try {
 96:             Thread.sleep(time);
 97:         } catch (InterruptedException e) { }
 98:     }
 99:
100:     public void paint(Graphics screen) {
101:         if (currentImg != null)
102:             screen.drawImage(currentImg, x, y, this);
103:     }
104: }

Wenn dieses Applet mit dem Compiler aus dem JDK 1.2 kompiliert wird, zeigt dieser eine Warnung wegen der verworfenen Methode size() an. Diese Warnung können Sie getrost ignorieren - das Applet wird erfolgreich auf Java-1.02- und Java-1.1-kompatiblen Browsern, wie z.B. dem Netscape Navigator, laufen. Eine Java-1.2-Version dieses Applets, Neko12, finden Sie auf der CD-ROM zum Buch im Ordner \Source\Day10 .

Um dieses Applet zu testen, erzeugen Sie eine Webseite, in dem das Applet-Fenster eine Breite von 300 Pixeln und eine Höhe von 200 Pixeln hat. Abbildung 10.5 zeigt das Ergebnis.


Abbildung 10.5:
Das Neko-Applet

Anmerkung zur Verwendung von Grafik-Kontexten

Wenn Sie ausführlichen Gebrauch von Grafik-Kontexten in Ihren Applets oder Anwendungen machen, sollten Sie sich darüber im klaren sein, daß diese Kontexte häufig bestehen bleiben, nachdem die Arbeit damit abgeschlossen ist, auch wenn keine weiteren Referenzen dazu bestehen. Grafik-Kontexte sind spezielle Objekte im AWT, die im nativen Betriebssystem verwurzelt sind; der Garbage Collector von Java kann diese Grafik-Kontexte nicht selbst entfernen. Da nichtverwendete Objekte die Performance von Java beeinträchtigen können, sollten Sie die dispose()-Methode der Klasse Graphics verwenden, um Grafik-Kontexte explizit zu löschen. Ein guter Ort für die Plazierung dieser Methode ist die destroy()-Methode des Applets (diese haben Sie am 8.Tag als eine der Primärmethoden eines Applets kennengelernt, neben init() , start() und stop()):

public void destroy() {
    offscreenGraphics.dispose();
}

Doppelte Pufferung

Sie haben bereits eine einfache Möglichkeit kennengelernt, wie sich der Flimmereffekt in Java-Animationen reduzieren läßt. Eine zweite, etwas komplexere, aber auch meistens sinnvollere Technik zur Reduzierung des Flimmereffekts in Java-Animationen besteht in der sogenannten doppelten Pufferung.


Die doppelte Pufferung ist ein Vorgang, bei dem alle Zeichenaktivitäten in einem Puffer abseits des Bildschirms vorgenommen werden. Anschließend wird der gesamte Inhalt dieses Puffers in einem Schritt am Bildschirm angezeigt. Diese Technik wird doppelte Pufferung genannt, weil es zwei Puffer für Grafikausgaben gibt, zwischen denen Sie wechseln.

Bei der Verwendung der doppelten Pufferung erstellen Sie eine zweite Zeichenfläche (sozusagen außerhalb des Bildschirms), nehmen dort alle Zeichenoperationen vor und zeichnen dann am Ende die gesamte Zeichenfläche in einem Schritt im aktuellen Applet (und damit auf dem Bildschirm). Da sich diese Arbeit hinter den Kulissen vollzieht, wird damit die Möglichkeit ausgeschaltet, daß Zwischenschritte innerhalb des Zeichenvorgangs aus Versehen erscheinen und den Ablauf einer Animation stören.

Die Verwendung der doppelten Pufferung ist nicht immer die beste Lösung. Wenn das Applet viele störende Flimmereffekte aufweist, können Sie auch update() überschreiben und nur Teile des Bildschirms neuzeichnen. Dies kann das Problem bereits lösen. Der Doppelpuffer ist weniger effizient als der reguläre Puffer und beansprucht zudem mehr Speicherplatz, in einigen Fällen kann dies also nicht der beste Lösungsansatz sein. Wenn Sie jedoch rigoros alle Flimmereffekte einer Animation entfernen möchten, funktioniert diese Technik ausgesprochen gut.

Um ein Applet zu erstellen, das doppelte Pufferung verwendet, benötigen Sie zweierlei: ein sogenanntes Offscreen-Bild und einen Grafikkontext für dieses Bild. Die beiden simulieren den Effekt der Grafikoberfläche eines Applet: Der Grafikkontext (eine Instanz von Graphics) enthält die Zeichenmethoden, wie z.B. drawImage() (und drawString()), und Image enthält die Bildpunkte, die gezeichnet werden sollen.

Um ein Applet mit doppelter Pufferung zu versehen, sind vier weitere wichtige Schritte erforderlich: Zunächst müssen das Offscreen-Bild und der Grafikkontext in Instanzvariablen gespeichert werden, damit diese an die paint()-Methode weitergeleitet werden können. Richten Sie in Ihrer Klassendefinition folgende Instanzvariablen ein:

Image offscreenImage;
Graphics offscreen;

Als zweiten Schritt erstellen Sie während der Initialisierung des Applets ein Image- und ein Graphics-Objekt und weisen diese jenen Variablen zu (dazu muß die Initialisierung abgewartet werden, damit Sie wissen, wie groß sie werden). Die createImage()-Methode gibt Ihnen eine Instanz von Image, die Sie dann an die getGraphics()-Methode übergeben können, um einen neuen Graphics-Kontext für das Bild zu erhalten:

offscreenImage = createImage(size().width,
    size().height);
offscreen = offscreenImage.getGraphics();

Wann immer Sie jetzt am Bildschirm zeichnen (meist in der paint()-Methode), zeichnen Sie nun Offscreen-Grafiken und nicht auf der Zeichenoberfläche des Applets. Um z.B. ein Bild namens img an Position 10, 10 zu zeichnen, verwenden Sie diese Zeile:

offscreen.drawImage(img, 10, 10, this);

Am Ende der paint-Methode, wenn alle Zeichnungen im Offscreen-Bild ausgeführt sind, fügen Sie die folgende Zeile ein, um den Offscreen-Puffer auf den tatsächlichen Bildschirm zu übertragen:

screen.drawImage(offscreenImage, 0, 0, this);

Nun überschreiben Sie noch die Methode update(), damit diese den Bildschirm zwischen den Zeichenvorgängen nicht leert:

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

Im folgenden werden diese vier Schritte noch einmal zusammengefaßt:

1. Fügen Sie Instanzvariablen für den Bild- und Grafikkontext des Offscreen-Puffers ein.

2. Erstellen Sie ein Image-Objekt und einen Grafikkontext, nachdem das Applet initialisiert ist.

3. Nehmen Sie alle Applet-Zeichnungen im Offscreen-Puffer vor und nicht auf der Zeichenoberfläche des Applets.

4. Am Ende der paint()-Methode zeichnen Sie den Inhalt des Offscreen-Puffers auf den realen Bildschirm.

Das Checkers-Applet

Im folgenden finden Sie ein weiteres Beispiel für eine einfache Animation: Dieses Applet trägt den Namen Checkers. Ein rotes Oval (ein Damestein) bewegt sich von einem schwarzen auf ein weißes Quadrat wie auf einem Damebrett. Am Ende dieser Bewegung kehrt es zum Ausgangspunkt zurück und bewegt sich erneut.

Im folgenden wird erläutert, was dieses Applet ausführt: Die Instanzvariable xpos verfolgt die aktuelle Ausgangsposition des Damesteins (weil er sich horizontal bewegt, bleibt y konstant und muß nicht verfolgt werden, während x sich ändert). In der run()-Methode wird der Wert von x geändert und neu gezeichnet, wobei zwischen jeder Bewegung eine Wartezeit von 100 Millisekunden liegt. Der Damestein bewegt sich von einer Seite des Bildschirms zur anderen und wieder zurück, wobei er wieder seine ursprüngliche Position einnimmt, sobald er auf der rechten Seite des Bildschirms angelangt ist.

In der paint()-Methode werden die Hintergrundquadrate mit der Methode fillRect() gezeichnet (ein weißes und ein schwarzes), und anschließend wird der Damestein mit der Methode fillOval() an seine aktuelle Position gesetzt.

Dieses Applet flimmert ebenso wie das ColorSwirl-Applet sehr stark. Das einfache Überschreiben von update() reicht in diesem Fall nicht aus, da Teile des Bildschirms geleert und neu gezeichnet werden, während sich der Damestein quer über den Bildschirm bewegt. Der Flimmereffekt tritt in diesem Applet vor allem deswegen auf, weil zuerst der Hintergrund und dann darauf der Damestein gezeichnet wird.

Sie könnten dieses Applet dahingehend ändern, daß paint() nur jene Elemente mit clipRect() neu zeichnet, die sich ändern. Auf diese Weise läßt sich das Flimmern reduzieren. Aber bei dieser Strategie müssen die alten und neuen Positionen des Damesteins verfolgt werden, und dies ist nicht sehr elegant. Eine bessere Lösung besteht in diesem Fall darin, den Doppelpuffer einzusetzen und damit alle Flimmereffekte auszuschalten. Für dieses Beispiel einen Doppelpuffer einzufügen ist einfach. Fügen Sie zunächst die Instanzvariablen für das Offscreen-Bild und den Grafik-Kontext ein:

Image offscreenImg;
Graphics offscreen;

Fügen Sie als zweiten Schritt eine init()-Methode ein, um den Offscreen-Puffer zu initialisieren:

public void init() {
    offscreenImg = createImage(size().width, size().height);
    offscreen = offscreenImg.getGraphics();
}

Drittens ändern Sie die paint()-Methode, um in den Offscreen-Puffer anstatt in den Haupt-Grafikpuffer zu zeichnen:

public void paint(Graphics screen) {
    // Hintergrund zeichnen
    offscreen.setColor(Color.black);
    offscreen.fillRect(0, 0, 100, 100);
    offscreen.setColor(Color.white);
    offscreen.fillRect(100, 0, 100, 100);
    // Damestein zeichnen
    offscreen.setColor(Color.red);
    offscreen.fillOval(xPos, 5, 90, 90);
    screen.drawImage(offscreenImg, 0, 0, this);
}

Beachten Sie die letzte Anweisung dieser Methode. Dies ist die einzige Anweisung, die direkt auf das Applet etwas ausgibt. Diese Anweisung gibt den gesamten Offscreen- Puffer bei den Koordinaten (0,0) aus. Da offScreenImg in der Größe des Applet-Fensters angelegt wurde, füllt es dieses vollständig aus.

Und schließlich geben Sie in der destroy()-Methode des Applet explizit an, daß der in offscreen gespeicherte Grafik-Kontext entfernt werden soll:

public void destroy() {
    offscreen.dispose();
}

In Listing 10.5 ist der gesamte Quelltext des Checkers-Applets abgedruckt.

Listing 10.5: Der gesamte Quelltext von Checkers.java

 1: import java.awt.*;
 2:
 3: public class Checkers extends java.applet.Applet implements Runnable {
 4:     Thread runner;
 5:     int xPos = 5;
 6:     int xMove = 4;
 7:     Image offscreenImg;
 8:     Graphics offscreen;
 9:
10:
11:     public void init() {
12:         offscreenImg = createImage(size().width, size().height);
13:         offscreen = offscreenImg.getGraphics();
14:     }
15:
16:     public void start() {
17:         if (runner == null); {
18:             runner = new Thread(this);
19:             runner.start();
20:         }
21:     }
22:
23:     public void stop() {
24:        runner = null;
25:     }
26:
27:     public void run() {
28:         Thread thisThread = Thread.currentThread();
29:         while (runner == thisThread) {
30:             xPos += xMove;
31:             if ((xPos > 105) | (xPos < 5))
32:                 xMove *= -1;
33:             repaint();
34:             try {
35:                 Thread.sleep(100);
36:             } catch (InterruptedException e) { }
37:         }
38:    }
39:
40:    public void update(Graphics screen) {
41:        paint(screen);
42:    }
43:
44:    public void paint(Graphics screen) {
45:       // Hintergrund zeichnen
46:       offscreen.setColor(Color.black);
47:       offscreen.fillRect(0,0,100,100);
48:       offscreen.setColor(Color.white);
49:       offscreen.fillRect(100,0,100,100);
50:       // Damestein zeichnen
51:       offscreen.setColor(Color.red);
52:       offscreen.fillOval(xPos,5,90,90);
53:       screen.drawImage(offscreenImg, 0, 0, this);
54:    }
55:
56:    public void destroy() {
57:        offscreen.dispose();
58:    }
59: }

Sie können dieses Applet auf einer Webseite testen, indem Sie im <APPLET>-Tag für die Größe die folgenden Attribute verwenden: height=200 und width=300. Das Ergebnis sehen Sie in Abbildung 10.6.


Abbildung 10.6:
Das Checkers-Applet

Klänge laden und verwenden

Java bietet eine vordefinierte Unterstützung für das Abspielen von Klängen in Verbindung mit dem Ablauf von Animationen oder zum eigenständigen Abspielen an. Ebenso wie die Unterstützung für Bilder befindet sich auch die Unterstützung für Klänge in den Klassen Applet und AWT. Die Verwendung von Klängen ist also ebenso einfach wie das Laden und Verwenden von Bildern.

Vor Java 1.2 wurde nur ein Klangformat unterstützt: 8 kHz Mono AU mit mu-law-Codierung von Sun. AU-Dateien sind kleiner als andere Klangdateien in anderen Formaten, aber die Tonqualität ist nicht besonders gut. Wenn Sie Sounds nutzen wollten, die in anderen Formaten vorlagen, mußten Sie diese in das AU-Format konvertieren, was oft mit einem Qualitätsverlust verbunden war.

Java 1.2 bietet eine weitaus umfassendere Audio-Unterstützung. Sie können digitalisierte Klänge der folgenden Formate laden und abspielen: AIFF, AU und WAF. Zusätzlich werden drei Formate auf MIDI-Basis unterstützt: Typ 0 MIDI, Typ 1 MIDI und RMF. Die stark erweiterte Unterstützung von Klängen kann mit Audio Daten in acht- und 16 Bit, Mono oder Stereo, und Sampling-Raten von 8 kHz bis 48 kHz umgehen.

Die einfachste Möglichkeit, einen Klang zu laden und abzuspielen, bietet die play()- Methode. Diese bildet einen Teil der Applet-Klasse und steht deshalb in Applets für Sie zur Verfügung. Die play()-Methode ist der getImage()-Methode sehr ähnlich. Auch sie kann in folgenden beiden Formen verwendet werden:

Die folgende Codezeile lädt beispielsweise die Datei meow.au und spielt den darin enthaltenen Klang ab. Die Datei befindet sich im Verzeichnis audio, welches wiederum im selben Verzeichnis wie das Applet plaziert ist:

play(getCodeBase(), "audio/meow.au");

Die play()-Methode lädt die Klangdatei und spielt den Ton sobald wie möglich ab, nachdem der Aufruf erfolgt ist. Wenn der Klang nicht gefunden wird, erscheint keine Fehlermeldung, der Ton ist dann nur einfach nicht zu hören.

Wenn Sie einen bestimmten Klang wiederholt abspielen möchten, starten und stoppen Sie die Klangdatei oder führen diese als Schleife aus (um sie immer wieder abzuspielen). In diesem Fall verwenden Sie die Applet-Methode getAudioClip(), um die Klangdatei in eine Instanz der AudioClip-Klasse (Teil von java.applet) zu laden. Vergessen Sie nicht, diese zu importieren. Im Anschluß daran können Sie direkt mit diesem AudioClip-Objekt arbeiten.

Angenommen, Sie haben eine Klangschleife erstellt, die permanent im Hintergrund des Applets ausgeführt werden soll. Im Initialisierungscode können Sie folgende Zeile für eine solche Klangdatei verwenden:

AudioClip clip = getAudioClip(getCodeBase(),
"audio/loop.wav");

Die Methode getAudioClip() kann nur in einem Applet aufgerufen werden. Unter Java 1.2 können Applikationen Sound-Dateien über die Methode newAudioClip() der Klasse Applet laden. Im Anschluß wird das vorige Beispiel umgeschrieben für die Verwendung in einer Applikation:

AudioClip clip = newAudioClip("audio/loop.wav");

Um den Clip einmal abzuspielen, verwenden Sie die play()-Methode:

clip.play();

Um einen aktuell ablaufenden Soundclip anzuhalten, verwenden Sie die stop()-Methode:

clip.stop();

Um für den Clip eine Schleife zu definieren (ihn wiederholt abzuspielen), verwenden Sie die loop()-Methode:

clip.loop();

Wenn die Methode getAudioClip() oder newAudioClip() den angegebenen Klang nicht findet oder diesen aus einem bestimmten Grund nicht laden kann, wird null zurückgegeben. Es ist sinnvoll, den Code für diesen Fall zu testen, ehe Sie die Klangdatei abzuspielen versuchen, da ein versuchter Aufruf der play()-, stop()- und loop()- Methoden für ein null-Objekt einen Fehler zur Folge hat (eine Ausnahme).

In einem Applet lassen sich beliebig viele Klangdateien abspielen; alle Klänge werden genau so miteinander vermischt, wie sie im Applet abgespielt werden.

Beachten Sie, daß bei der Verwendung von Hintergrundklängen mit Wiederholungsschleifen die Klangdatei nicht automatisch angehalten wird, wenn der Thread des Applets gestoppt wird. Das heißt, wenn ein Leser zu einer anderen Seite wechselt, wird der Klang des ersten Applets weiterhin abgespielt. Sie können dieses Problem lösen, indem Sie den Hintergrundklang des Applets mit der stop()-Methode anhalten:

public void stop() {
    if (runner != null) {
        if (bgsound != null)
        bgsound.stop();
        runner.stop();
        runner = null;
    }
}

Listing 10.6 zeigt eine einfache Grundstruktur für ein Applet, das zwei Klänge abspielt: Der erste, ein Hintergrundklang namens loop.au, wird wiederholt abgespielt. Der zweite, ein Piepsignal (beep.au), wird alle fünf Sekunden abgespielt. (Auf das Bild für dieses Applet habe ich verzichtet, denn es zeigt ausschließlich einen einfachen String am Bildschirm.)

Listing 10.6: Applet, das Klänge abspielt.

 1: import java.awt.Graphics;
 2: import java.applet.AudioClip;
 3:
 4: public class AudioLoop extends java.applet.Applet
 5:     implements Runnable {
 6:
 7:     AudioClip bgSound;
 8:     AudioClip beep;
 9:     Thread runner;
10:
11:     public void start() {
12:          if (runner == null) {
13:              runner = new Thread(this);
14:              runner.start();
15:          }
16:     }
17:
18:     public void stop() {
19:         if (runner != null) {
20:             if (bgSound != null)
21:                 bgSound.stop();
22:             runner = null;
23:         }
24:     }
25:
26:     public void init() {
27:         bgSound = getAudioClip(getCodeBase(),"loop.au");
28:         beep = getAudioClip(getCodeBase(), "beep.au");
29:     }
30:
31:     public void run() {
32:         if (bgSound != null)
33:             bgSound.loop();
34:         Thread thisThread = Thread.currentThread();
35:         while (runner == thisThread) {
36:             try {
37:                 Thread.sleep(5000);
38:             } catch (InterruptedException e) { }
39:             if (beep != null)
40:                 beep.play();
41:         }
42:    }
43:
44:     public void paint(Graphics screen) {
45:         screen.drawString("Playing Sounds ...", 10, 10);
46:     }
47: }

Um das AudioLoop-Applet zu testen, erzeugen Sie eine Webseite mit einem Applet- Fenster, das eine Höhe von 100 Pixeln und eine Breite von 200 Pixeln hat. Die Audio Dateien loop.au und beep.au sollten Sie von der CD-ROM zum Buch in den Ordner \J21Work auf Ihrem System kopieren. Wenn Sie das Applet ausführen, ist ein String die einzige visuelle Ausgabe. Sie sollten zwei verschiedene Klänge hören, während das Applet läuft.

Die init()-Methode in den Zeilen 26 und 29 lädt die beiden Klangdateien loop.au und beep.au. Hier wurde kein Versuch unternommen, sicherzustellen, daß Dateien auch tatsächlich wie erwartet geladen werden. Es besteht also die Möglichkeit, daß die Instanzvariablen bgsound und beep den Wert null haben, wenn die jeweilige Datei nicht geladen werden kann. In diesem Fall könnte die start()-Methode oder eine beliebige andere Methode nicht aufgerufen werden. Es sollte daher an anderer Position im Applet ein Test dafür ausgeführt werden.

Ein entsprechender Test wurde deshalb an anderen Stellen eingefügt, nämlich in der run()-Methode in den Zeilen 32 und 39. Hier werden die Methoden loop() und play() für die AudioClip-Objekte aufgerufen - allerdings nur, wenn die Variablen bgsound und beep einen anderen Wert als null enthalten.

Schließlich sollten Sie einen Blick auf Zeile 20 werfen, welche den Hintergrundklang explizit abschaltet, wenn der Thread angehalten wird. Da das Abspielen von Hintergrundklängen nicht automatisch mit dem Beenden des Thread aufhört, muß dies explizit eingefügt werden.

Zusammenfassung

Heute haben Sie einiges über diverse Methoden gelernt, die Sie verwenden und überschreiben können - start(), stop(), paint(), repaint(), run() und update() - und Sie haben eine der elementaren Grundlagen für die Erzeugung und Verwendung von Threads kennengelernt. Sie haben ebenfalls gelernt, wie Sie Bilder in Ihren Applets verwenden -, das Auffinden und Laden von Bildern und die Verwendung der Methode drawImage(), um Bilder auszugeben und zu animieren.

Eine Animationstechnik, die Sie jetzt verwenden können, ist die doppelte Pufferung, die Flimmern in Ihren Animationen praktisch völlig eliminiert, allerdings auf Kosten der Effizienz und der Geschwindigkeit. Über ein Image-Objekt und einen Graphics- Kontext können Sie einen Offscreen-Puffer erzeugen, auf den Sie zeichnen. Das Ergebnis der ganzen Operationen wird zuletzt auf dem Bildschirm angezeigt.

Sie haben gelernt, Klänge zu verwenden, die Sie in Ihre Applets integrieren können, wann immer Sie diese benötigen - in bestimmten Situationen oder als Hintergrund- Sound, der wiederholt abgespielt wird, solange das Applet läuft. Sie haben gelernt, wie Sie Klänge sowohl mit der Methode play() als auch mit der Methode getAudioClip() auffinden, laden und abspielen.

Fragen und Antworten

Frage:
Im Neko-Programm fügen Sie den Ladevorgang für die Bilder in die init()-Methode ein. Mir scheint, Java benötigt eine sehr lange Zeit für das Laden der Bilder, und da init() nicht im Haupt-Thread des Applet liegt, findet hier eine deutliche Pause statt. Warum läßt sich der Ladevorgang nicht am Anfang der run()-Methode einfügen?

Antwort:
Hinter den Kulissen spielen sich auch noch andere Dinge ab. Die getImage()- Methode lädt das Bild nämlich nicht wirklich, sondern gibt beinahe unmittelbar ein Image-Objekt zurück, damit während der Initialisierung keine langen Verarbeitungszeiten anfallen. Die Bilddaten, auf die getImage() verweist, werden nicht geladen, solange das Bild nicht benötigt wird. Auf diese Art muß Java keine riesigen Bilder im Arbeitsspeicher aufbewahren, wenn das Programm nur ein kleines Stück davon benötigt. Statt dessen bleibt lediglich die Referenz auf diese Daten erhalten, während das Laden der notwendigen Bereiche später stattfindet.

Frage:
Ich habe das Neko-Applet kompiliert und ausgeführt. Dabei geschehen merkwürdige Dinge: Die Animation beginnt in der Mitte und läßt Einzelbilder aus. Es scheint, also ob nur einige Bilder geladen worden sind, wenn das Applet ausgeführt wird.

Antwort:
Ja, genau das ist der Fall. Da das Laden der Bilder das Bild nicht tatsächlich lädt, animiert das Applet sozusagen den leeren Bildschirm, während die Bilder noch geladen werden. Das Applet scheint dann in der Mitte zu starten, Einzelbilder zu erstellen und überhaupt nicht zu funktionieren.

Für dieses Problem gibt es drei Lösungen. Die erste besteht darin, eine Animationsschleife zu verwenden (d.h. von vorne zu beginnen, sobald es angehalten wird). Eventuell werden die Bilder dann geladen, und die Animation funktioniert korrekt. Als zweite Lösung, die allerdings nicht sehr gut ist, können Sie eine kleine Pause vor der Ausführung definieren, damit die Bilder geladen werden können, ehe die Animation ausgeführt wird. Die dritte und beste Möglichkeit ist, Image-Observer zu verwenden, mit deren Hilfe sich sicherstellen läßt, daß kein Bereich der Animation abgespielt wird, ehe nicht die notwendigen Bilder geladen sind. Nähere Erläuterungen hierzu erhalten Sie in der Dokumentation zur Schnittstelle ImageObserver.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Java 2 in 21 Tagen, ISBN: 3-8272-5578-3