Previous Page TOC Index Next Page See Page

Grafik, Schrift und Multimedia

Gestern haben Sie gelernt, wie Applets funktionieren. Heute werden wir auf diesem Verständnis aufbauen, um Ihnen zu zeichnen, was Sie mit Applets und den Java- und JBuilder-Klassenbibliotheken tun können und wie Sie damit interessante Effekte erzielen. Sie beginnen damit, auf den Bildschirm zu zeichnen - d.h., wie Linien zu Formen mit Hilfe der eingebauten Grafikelemente erzeugt werden, wie Text in verschiedenen Schriften ausgegeben wird, und wie die Farbe Ihres Applets verändert wird. Heute lernen Sie die folgenden Dinge kennen:

Heute lernen Sie außerdem die Grundlagen der Animation in Java kennen: wie die verschiedenen Teile des Systems zusammenarbeiten, so daß Sie bewegte Figuren und dynamisch aktualisierbare Applets erzeugen können. Insbesondere werden Sie folgendes lernen:

In Java ist es ganz einfach, Animationen zu erzeugen, und Sie können sehr viel mit den bereitgestellten Methoden für Grafik, Schrift und Farbe machen. Für wirklich interessante Animationen müssen Sie jedoch für jedes Einzelbild der Animation eigenen Bilder bereitstellen. Die Verwendung von Sounds ist ebenfalls ganz nett. Heute werden Sie erfahren, wie man solche Animationen mit Grafik, Bildern und Sound in Java-Applets erzeugt.

Um ein neues Projekt für die heutigen Listings zu erzeugen, wählen Sie Neu | Neues Projekt und ändern das Datei-Feld wie folgt:

C:\JBUILDER\myprojects\GraphicsFontsEtc.jpr

Klicken Sie auf die Schaltfläche Fertigstellen. Diesem Projekt werden Sie mit Hilfe der Schaltfläche Dem Projekt hinzufügen alle der heute erstellten Listings hinzufügen.

Grafik

Mit den Grafikfunktionen von Java können Sie Linien, Formen, Zeichen und Bilder auf den Bildschirm zeichnen. Ein Großteil der Grafikmethoden von Java sind in der Klasse Graphics definiert. Sie müssen nicht explizit eine Instanz von Graphics anlegen, um etwas in Ihr Applet zeichnen zu können. Einem Ihrer Applets von gestern wurde einfach ein Graphics-Objekt übergeben, indem das Graphics-Argument für die paint()-Methode verwendet wurde - mit anderen Worten, die Instanz wurde von der paint()-Methode automatisch für Sie erzeugt. Wenn Sie auf dieses Objekt zeichnen, zeichnen Sie in Ihr Applet, und das Ergebnis erscheint auf dem Bildschirm.

Die Klasse Graphics ist Teil des Pakets java.atw, wenn Ihr Applet also etwas zeichnet (was normalerweise der Fall ist), achten Sie darauf, die Klasse am Anfang Ihrer Java-Datei zu importieren (nach der package-Anweisung, falls eine solche vorhanden ist):

import java.awt.Graphics;

public class MyClass extends java.applet.Applet {...}

Das Grafik-Koordinatensystem

Um ein Objekt auf den Bildschirm zu zeichnen, rufen Sie eine der Zeichenmethoden der Klasse Graphics auf. Alle Zeichenmethoden haben Argumente, die Endpunkte, Ecken oder Startpositionen des Objekts als Werte des Applet-Koordinatensystems darstellen. Eine Linie kann beispielsweise am Punkt 10,10 beginnen und am Punkt 20,20 enden.

Das Koordinatensystem von Java hat den Ursprung (0,0) in der oberen linken Ecke. Positive x-Werte gehen nach rechts, positive y-Werte nach unten. Alle Pixel-Werte sind Integer, es gibt keinen Bruchteil eines Pixels.

Abbildung 9.1 zeigt, wie Sie mit Hilfe dieses einfachen Koordinatensystems ein einfaches Quadrat zeichnen.

Abbildung 9.1: Das Grafik-Koordinatensystem von Java

Das Koordinatensystem von Java unterscheidet sich von einigen der Mal- und Zeichenprogrammen, die ihren x- und y-Ursprung in der linken unteren Ecke anlegen. Wenn Sie nicht daran gewöhnt sind, mit dieser Darstellung zu arbeiten, kann es etwas dauern, bis Sie sich daran gewöhnt haben.

Zeichnen und Ausfüllen

Die Klasse Graphics stellt mehrere elementare Grafikfunktionen zum Zeichnen bereit, unter anderem für Linien, Rechtecke, Polygone, Ovale und Bögen.


Bitmap-Bilder, wie etwa GIF-Dateien, können ebenfalls mit Hilfe von Graphics gezeichnet werden. Mehr darüber erfahren Sie morgen.

Linien

Mit der Methode drawLine() zeichnen Sie gerade Linien. Diese Methode nimmt vier Argumente entgegen: die x- und y-Koordinaten des Anfangspunkts und die x- und y-Koordinaten des Endpunkts.

public void paint(Graphics g) {
g.drawLine(25,25,75,75);
}

Rechtecke

Die Java-Grafikfunktionen ermöglichen die Ausgabe von dreierlei Arten von Rechtecken:

Für diese Rechtecke gibt es je zwei Methoden: eine, die den Umriß zeichnet, und eine, die das Rechteck mit Farbe füllt.

Um ein Rechteck mit eckigen Ecken zu zeichnen, verwenden Sie drawRect() oder fillRect(). Beide Methoden nehmen vier Argumente entgegen, die x- und y-

public void paint(Graphics g) {
g.drawRect(20,20,60,60);
g.fillRect(120,20,60,60);
}

Abgerundete Rechtecke werden mit abgerundeten Ecken gezeichnet. Die Methode drawRoundRect() und fillRoundRect() sind ähnlich den Methoden zum Zeichnen von normalen Rechtecken, aber sie nehmen zwei zusätzliche Argumente entgegen, arcWidth und arcHeigth, die die horizontalen und vertikalen Ausmaße des Bogens für die Ecke angeben. Diese beiden Argumente legen fest, wo die Ecken für das Rechteck beginnen: arcWidth für den Bogen entlang der x-Achse, arcHeight für die y-Achse.

Hier folgt eine paint()-Methode, die die abgerundeten Ecken zeichnet: eine als Umriß mit abgerundeten Ecken von 10 Pixel im Quadrat, das andere ausgefüllt und mit abgerundeten Ecken von 20 Pixel im Quadrat:

public void paint(Graphics g) {
g.drawRoundRect(20,20,60,60,10,10);
g.fillRoundRect(120,20,60,60,20,20);
}

Schließlich gibt es noch dreidimensionale Rechtecke. Sie sind nicht wirklich 3D. Statt dessen haben sie einen schattierten Rahmen, der so tut, als würde ein Licht von der oberen linken Ecke des Bildschirms scheinen, so daß sie entweder hervorgehoben oder abgesenkt erscheinen. Dreidimensionale Rechtecke verwenden vier Argumente: x und y für den Ausgangspunkt sowie die Höhe und Breite des Rechtecks. Das fünfte Argument ist ein Boolescher Wert, der angibt, ob das Rechteck hervorgehoben (true) oder abgesenkt (false) dargestellt werden soll. Wie bei den anderen Rechtecken gibt es auch hier verschieden Methoden zum Zeichnen und Ausfüllen: draw3DRect() und fill3DRect(). Hier der Code, der den Umriß eines dreidimensionalen Rechtecks zeichnet - einmal hervorgehoben, einmal abgesenkt:

public void paint(Graphics g) {
g.draw3DRect(20,20,60,60,true);
g.draw3DRect(120,20,60,60,false);
}


Weil Sie keine Kontrolle über den schattierten Rahmen haben, kann es sehr schwierig sein, diesen 3D-Effekt zu sehen, weil die Standard-Linienstärke nur ein Pixel beträgt. Wenn Sie Probleme damit haben, ist das also verständlich. Wenn Sie eine andere Farbe als Schwarz für die 3D-Rechtecke verwenden, sehen Sie den Effekt deutlicher.

Polygone

Polygone sind Formen mit einer unbeschränkten Anzahl Seiten. Um ein Polygon zu zeichnen, brauchen Sie x,y-Koordinatenpaare. Die Zeichenmethode beginnt bei dem ersten Koordinatenpaar und zeichnet eine Linie zum zweiten, dann eine Linie zum dritten usw.

Der Umriß eines Polygons wird mit drawPolygon() gezeichnet, mit fillPolygon() wird es ausgefüllt. Beide Methoden schließen das Polygon automatisch für Sie, wenn Start- und Endpunkt sich unterscheiden. Wenn Sie nicht wollen, daß Ihr Polygon geschlossen wird, verwenden Sie die Methode drawPolyline(). Sie können auch angeben, wie die Koordinaten aufgelistet werden - entweder als Array mit x- und y-Werten oder durch Übergabe einer Instanz der Klasse Polygon.

Für die erste Technik verwenden drawPolyline(), drawPolygon() und fillPolygon() drei Argumente:

Die x- und y-Arrays sollten gleich viele Elemente enthalten. Hier ein Beispiel für das Zeichnen eines Polygonumrisses unter Verwendung dieser Technik.

public void paint(Graphics g) {
int xCoordArr[] = { 39,94,97,142,53,58,26 };
int yCoordArr[] = { 33,74,36,70,108,80,106 };
int numPts = xCoordArr.length;
g.drawPolyline(xCoordArr, yCoordArr, numPts);
}

Die zweite Technik verwendet ein Polygon-Objekt, um das Polygon zu erzeugen, und übergibt das ganze der Methode zum Zeichnen. Die Klasse Polygon ist praktisch, wenn Sie dem Polygon Punkte hinzufügen wollen, oder wenn Sie das Polygon dynamisch erzeugen. Die Klasse Polygon ermöglicht Ihnen, das Polygon als Objekt zu behandeln und Punkte einzufügen. Um ein Polygon-Objekt zu instantiieren, erzeugen Sie ein leeres Polygon: p>Polygon poly = new Polygon();

Nachdem Sie das Polygon-Objekt haben, können Sie ihm mit Hilfe der Methode addPoint() der Polygon-Klasse beliebig Punkte hinzufügen. Hier die sieben Punkte aus dem ersten Polygon-Beispiel, die dort als Koordinatenpaare angegeben wurden:

poly.addPoint(39,33);
poly.addPoint(94,74);
poly.addPoint(97,36);
poly.addPoint(142,70);
poly.addPoint(53,108);
poly.addPoint(58,80);
poly.addPoint(26,106);

Um das Polygon zu zeichnen, verwenden Sie das Polygon-Objekt als Argument für die Zeichenmethoden. Hier das obige Beispiel für die zweite Technik:

public void paint(Graphics g) {
Polygon poly = new Polygon();
poly.addPoint(39,33);
poly.addPoint(94,74);
poly.addPoint(97,36);
poly.addPoint(142,70);
poly.addPoint(53,108);
poly.addPoint(58,80);
poly.addPoint(26,106);
g.drawPolygon(poly);
}

Wie Sie sehen, ist die Array-Technik viel kürzer, wenn Sie viele Punkte haben. Sie können die beiden Techniken auch kombinieren, indem Sie die beiden Arrays unter Verwendung eines der Konstruktoren der Polygon-Klasse erzeugen, um das Polygon-Objekt mit den Koordinaten-Arrays zu erzeugen, und übergeben dieses Objekt dann der entsprechenden Zeichenmethode, etwa wie folgt:

public void paint(Graphics g) {
int xCoordArr[] = { 39,94,97,142,53,58,26 };
int yCoordArr[] = { 33,74,36,70,108,80,106 };
int numPts = xCoordArr.length;
Polygon poly = new Polygon(xCoordArr, yCoordArr, numPts);
g.fillPolygon(poly);
}

Ovale

Mit Hilfe von Ovalen zeichnen Sie Ellipsen oder Kreise. Ovale sind eigentlich nur Rechtecken mit ganz runden Ecken, die mit vier Argumenten angelegt werden: x- und y-Wert der oberen linken Ecke und Höhe und Breite des Ovals. Weil Sie ein Oval zeichnen, liegen die Anfangskoordinaten etwas weiter oben und links als das eigentliche Oval; es handelt sich dabei um die obere linke Ecke des umschließenden Rechtecks.

Die Methode drawOval() zeichnet den Umriß eines Ovals, fillOval() ein gefülltes Oval. Hier folgt ein Beispiel für zwei Ovale: ein Kreis und eine Ellipse..

public void paint(Graphics g) {
g.drawOval(20,20,60,60);
g.fillOval(120,20,100,60);
}

Bögen

Bögen sind relativ aufwendig zu konstruieren. Ein Bogen ist ein Abschnitt eines Ovals. Am einfachsten stellt man sich also einen Bogen als Teil eines vollständigen Ovals vor.

Die Zeichenmethoden für Bögen nehmen sechs Argumente entgegen: Die Anfangskoordinaten x und y, Breite und Höhe des umschließenden Ovals, startAngel (Grad, an dem der Bogen beginnt) und arcAngel (Grad, über die sich der Bogen erstreckt). DrawArc() zeichnet einen Umriß, fillArc() füllt den Bogen aus. Gefüllten Bögen werden wie Tortenstücke gezeichnet. Statt die beiden Endpunkte zu verbinden, werden beide Endpunkte mit dem Mittelpunkt des Ovals verbunden.

Sie sollten sich klarmachen, daß der Bogen als Oval formuliert wird, und nur ein Ausschnitt davon gezeichnet wird. Die x,y-Koordinaten, die Höhe und die Breite sind nicht diejenigen des Bogens, wie er auf den Bildschirm gezeichnet wird, sondern die der vollständigen Ellipse, aus der der Bogen ermittelt wird.

Hier folgt der Code für ein Beispiel. Sie zeichnen den Umriß für ein C und ein ausgefülltes C.

public void paint(Graphics g) {
g.drawArc(20,20,60,60,90,180);
g.fillArc(120,20,60,60,90,180);
}

Ein einfaches Grafikbeispiel

Dieses Beispiel verwendet viele der eingebauten Grafikfunktionen, die Sie bisher kennengelernt haben, um einen einfachen Umriß zu zeichnen. In diesem Fall handelt es sich um eine Lampe mit einem gepunkteten Schirm (oder um einen kubistischen Champignon, wie Sie es sehen wollen). Listing 9.1 zeigt den vollständigen Code für das Projekt. Abbildung 9.2 zeigt das resultierende Applet.

Listing 9.1: SpottedLamp.java

1: import java.awt.Graphics;
2:
3: public class SpottedLamp extends java.applet.Applet {
4:
5: public void paint(Graphics g) {
6: // hier steht die Lampe
7: g.fillRect(0,250,290,290);
8:
9: // Lampenfuß
10: g.drawLine(125,160,125,250);
11: g.drawLine(175,160,175,250);
12:
13: // Schirm, obere und untere Kanten
14: g.drawArc(85,87,130,50,62,58);
15: g.drawArc(85,157,130,50,-65,312);
16:
17: // Seiten des Schirms
18: g.drawLine(119,89,85,177);
19: g.drawLine(181,89,215,177);
20:
21: // Die Punkte auf dem Schirm
22: g.fillArc(78,120,40,40,63,-174);
23: g.fillOval(120,96,40,40);
24: g.fillArc(173,100,40,40,110,180);
25: }
26:
27:

Speichern Sie die .html-Datei, bevor Sie versuchen, das Applet auszuführen.

Hier werden die WIDTH- und HEIGHT-Parameter der HTML-Datei nicht angegeben. Betrachten Sie es als Übung. Experimentieren Sie mit verschiedenen Parametern, um ein Gefühl für eigene HTML-Dateien zu entwickeln.

Abbildung 9.2: Die Lampe

Kopieren und Löschen

Nachdem Sie etwas auf den Bildschirm gezeichnet haben, wollen Sie es vielleicht verschieben oder das gesamte Applet löschen. Die Klasse Graphics stellt Methoden für beides zur Verfügung.

Die Methode copyArea() kopiert einen rechteckigen Bereich des Bildschirms in einen anderen Bereich. Sie nimmt sechs Argumente entgegen: die x- und y-Koordinaten der oberen linken Ecke des zu kopierenden Bereichs sowie die Höhe und Breite, und außerdem dx und dy, das Delta, um das der Startpunkt verschoben wird. Die folgende Codezeile kopiert einen rechteckigen Bereich von 90x70 Pixeln beginnend von den Koordinaten 5,10 in ein Rechteck, das 100 Pixel rechts vom Original beginnt.

g.copyArea(5,10,90,70,100,0);

Die dx- und dy-Argumente können positiv oder negativ sein, abhängig davon, in welche Richtung das Rechteck kopiert werden soll.

Mit clearRect() löschen Sie einen rechteckigen Bereich. Diese Methode nimmt die vier Argumente wie drawRect() und fillRect() entgegen: Anfangskoordinaten x und y, Breite und Höhe. Diese Methode füllt das angegebene Rechteck mit der aktuellen Hintergrundfarbe des Applets. (Mehr über die Hintergrundfarbe erfahren Sie später.)

Um den ganzen Zeichenbereich des Applets zu löschen, verwenden Sie getSize(), was ein Dimeinsion-Objekt zurückgibt, das die Breite und Höhe des Applets angibt. Daraus ermitteln Sie die Höhe und Breite des Bereichs, indem Sie der clearRect()-Methode die Instanzvariablen width und heigth des Dimension-Objekts übergeben.

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

Schrift und Text

Die Graphics-Klasse ermöglicht Ihnen außerdem, Text auf dem Bildschirm auszugeben, in Kombination mit den Klassen Font und FontMetrics. Die Klasse Font stellt eine bestimmte Schrift dar - ihren Namen, ihren Stil und die Punktgröße. FontMetrics gibt Ihnen Informationen über diese Schrift (beispielsweise die Höhe und Breite eines bestimmten Zeichens), so daß Sie den Text in Ihrem Applet genau auslegen können.

Beachten Sie, daß hier von Text gesprochen wird, der auf den Bildschirm gezeichnet wird und dort bleibt. Mehr über die Eingabe und Anzeige von Text von der Tastatur erfahren sie später.

Font-Objekte erzeugen

Um Text auf den Bildschirm zu zeichnen, brauchen sie eine Instanz der Font-Klasse. Font-Objekte stellen eine bestimmte Schrift dar - d.h. ihren Namen, den Stil und die Punktgröße. Schriftnamen stellen die Familie der Schrift dar, etwa TimesRoman, Courier oder Arial. Stile sind Konstanten, die in der Klasse Font definiert sind: Font.PLAIN, Font.BOLD oder Font.ITALIC. Die Punktgröße gibt an, wie groß die Schrift ist. Dabei kann es sich um die tatsächliche Größe der Schrift handeln, muß aber nicht. Ein Inch hat 72 Punkte vertikaler Schrifthöhe. Um also ein Zeichen mit ½ Inch Höhe zu erzeugen, geben Sie für die Punktgröße 36 an.

Um ein Font-Objekt zu erzeugen, übergeben Sie dem Konstruktor der Font-Klasse die Argumente:

Font f = new Font("TimesRoman", Font.BOLD, 24);

Dieses Beispiel erzeugt ein Font-Objekt in TimesRoman, fett, 24 Punkt (1/3 Inch). Auch diese Klasse müssen Sie importieren, damit Sie sie verwenden können:

import java.awt.Font;

Schriftstile sind Integer-Konstanten, die zu kombinierten Stilen zusammengefaßt werden können. Die folgende Anweisung erzeugt eine Schrift, die fett und kursiv ist:

Font f = new Font("TimesRoman", Font.BOLD + Font.ITALIC, 24);

Welche Schriften Ihnen in Ihren Applets zur Verfügung stehen, hängt davon ab, welche Schriften auf dem ausführenden System installiert sind. Wenn Sie eine Schrift verwenden, die auf dem Client-System nicht vorhanden ist, verwendet Java einen Ersatz (normalerweise Courier). Die besten Ergebnisse erhalten Sie, wenn Sie bei den Standardschriften bleiben, etwa TimesRoman oder Courier.

Mit der Methode getFontList() aus der Klasse java.awt.Toolkit erhalten Sie eine Liste aller auf dem Client-System verfügbaren Schriften (als String-Array). Hier können Sie dynamisch wählen, welche Schriften verwendet werden sollen. (Vergessen Sie nicht, java.awt.Toolkit zu importieren, wenn Sie diese Methode in Ihrem Code einsetzen wollen.)

Zeichen und Strings zeichnen

Mit dem Font-Objekt können Sie mit Hilfe der Methoden drawChars() und drawString() auf den Bildschirm zeichnen. Zuerst setzen Sie mit setFont() die aktuelle Schrift.

Die aktuelle Schrift ist Teil des Grafikstatus, den das Graphics-Objekt verwaltet. Immer wenn Sie ein Zeichen oder einen String auf den Bildschirm ausgeben, wird dieser Text mit der aktuellen Schrift gezeichnet.

public void paint(Graphics g) {
Font f = new Font("TimesRoman", Font.PLAIN, 72);
g.setFont(f);
g.drawString("This is a big font.", 10, 100);
}

Das sollte Ihnen vertraut vorkommen - so wurden die Hello-Applets in diesem Buch angelegt.

Die beiden letzten Argumente in drawString() geben den Punkt an, wo der String beginnt. Der x-Wert ist der der Anfang der linken Textkante, y ist die Grundlinie für den String.

Analog zu drawString() ist drawChars(), die statt eines Strings ein Zeichen-Array entgegennimmt. Die Methode drawChars() nimmt fünf Argumente entgegen. Die ersten drei Argumente sind das Zeichen-Array, ein Integer, der das erste Zeichen im Array angibt, das gezeichnet werden soll, sowie ein Integer für das letzte Zeichen, das gezeichnet werden soll. Alle Zeichen zwischen dem ersten und letzten werden gezeichnet. Die beiden letzten Argumente sind die x,y-Anfangskoordinaten.

Listing 9.2 zeigt ein Applet, das mehrere Textzeilen in verschiedenen Schriften zeichnet. Abbildung 9.3 zeigt das Ergebnis.

Listing 9.2: ManyFonts.java

1: import java.awt.Font;
2: import java.awt.Graphics;
3:
4: public class ManyFonts extends java.applet.Applet {
5:
6: public void paint(Graphics g) {
7: Font f = new Font("TimesRoman", Font.PLAIN, 18);
8: Font fb = new Font("TimesRoman", Font.BOLD, 18);
9: Font fi = new Font("TimesRoman", Font.ITALIC, 18);
10: Font fbi = new Font("TimesRoman", Font.BOLD + Font.ITALIC, 18);
11:
12: g.setFont(f);
13: g.drawString("Normale Schrift.", 10, 25);
14: g.setFont(fb);
15: g.drawString("Fett.", 10, 50);
16: g.setFont(fi);
17: g.drawString("Kursiv.", 10, 75);
18: g.setFont(fbi);
19: g.drawString("Fett kursiv.", 10, 100);
20: }
21:
22: }

Abbildung 9.3: Das Applet ManyFonts

Schrift-Information

Manchmal muß man Entscheidungen in Java-Programmen treffen, die auf den Attributen der aktuellen Schrift basieren - ihrer Punktgröße oder der Gesamthöhe der Zeichen. Grundlegende Informationen über Schriften und Schriftobjekte erhalten Sie, indem Sie eine Variable erzeugen, die das Ergebnis aufnimmt, und dieser dann die Attribute der aktuellen Schrift zuweisen. Dazu werden die in Tabelle 9.1 vorgestellten Methoden verwendet.

Tabelle 9.1: Methoden für Schrift-Informationen

Methodenname

Objekt

Rückgabewert

getFont()

Graphics

Das durch setFont gesetzte aktuelle Schrift-Objekt

getName()

Font

Name der aktuellen Schrift, ein String

getSize()

Font

Schriftgröße, ein Integer

getStyle()

Font

Schriftstil, eine Integer-Konstante (0 ist normal, 1 ist fett, 2 ist kursiv, 3 ist fett kursiv)

isBold()

Font

true, wenn die Schrift fett ist, andernfalls false

isItalic()

Font

true, wenn die Schrift kursiv ist, andernfalls false

isPlain()

Font

true, wenn die Schrift normal ist, andernfalls false

Um den Namen der aktuellen Schrift zu ermitteln, deklarieren Sie eine String-Variable, die die Information entgegennimmt und weisen ihr das Ergebnis der Methode getName() zu. Diese Methoden geben die aktuellen Schriftattribute zurück. Wenn Sie Informationen über eine bestimmte Schrift brauchen, übergeben Sie der Methode ein Font-Objekt. Hier ein Beispiel für beide Verwendungszwecke:

Font fcb = new Font("Courier", Font.BOLD, 12);
Font ftb = new Font("TimesRoman", Font.BOLD, 12);
g.setFont(fcp);
...
String currFontName = getName();
String ftbFontName = getFontName(ftb);

Hier wird currFontName auf Courier gesetzt, weil das aktuelle Font-Objekt fct ist, während ftbFontName auf TimesRoman gesetzt wird, weil das angegeben Font-Objekt ftb ist.

Detailliertere Informationen über die Attribute der aktuellen Schrift, etwa die Länge oder die Höhe von Zeichen, ermitteln Sie mit Hilfe der Klasse FontMetrics. Sie beschreibt Informationen für eine bestimmte Schrift: Zeilenabstand, Höhe und Breite der einzelnen Zeichen usw. Um mit diesen Werten arbeiten zu können, erzeugen Sie ein FontMetrics-Objekt und weisen ihm die Attribute der aktuellen Schrift zu Dazu verwenden Sie die Methode getFontMetrics() der Klasse Graphics:

Font f = new Font("TimesRoman", Font.BOLD, 36);
g.setFont(f);
FontMetrics fmetrics = g.getFontMetrics();

Wenn der Methode getFontMetrics() keine Argumente übergeben werden, gibt sie die Maße der aktuellen Schrift zurück. Wenn Sie die Maße für eine bestimmte Schrift ermitteln wollen übergeben Sie der Methode ein Font-Objekt:

g.getFontMetrics(fbi);

Tabelle 9.2 zeigt einige der Informationen, die Sie mit Hilfe der Schriftmaße ermitteln können. Alle Rückgabewerte sind in Pixeln dargestellt.

Tabelle 9.2: Methoden zur Ermittlung von Schriftmaßen

Methodenname

Rückgabewert

charWidth(char)

Breite des angegebenen Zeichens

getAscent()

Oberlänge der Schrift

getDescent()

Unterlänge der Schrift

getHeight()

Gesamthöhe der Schrift aus Oberlänge, Unterlänge und Zeilenabstand

getLeading()

Zeilenabstand

getMaxAscent()

Maximale Oberlänge der Schrift

getMaxDescent()

Maximale Unterlänge der Schrift

stringWidth(string)

Breite des vorgegebenen Strings

Als Beispiel für die Verwendung dieser Methoden zeigt Listing 9.3 den Java-Code für ein Applet, das einen String automatisch horizontal und vertikal zentriert. Mit Hilfe der Schriftgrößen wird die Größe eines Strings ermitteln, womit die Startposition angegeben werden kann, so daß er richtig zentriert und an der korrekten Position ausgegeben wird.

Listing 9.3: CenterString.java

1: import java.awt.Font;
2: import java.awt.FontMetrics;
3: import java.awt.Graphics;
4:
5: public class CenterString extends java.applet.Applet {
6:
7: public void paint(Graphics g) {
8: Font f = new Font("TimesRoman", Font.PLAIN, 36);
9: g.setFont(f);
10: FontMetrics fm = g.getFontMetrics();
11:
12: String str = "Stuck in the middle with you.";
13: int xStart = ( getSize().width - fm.stringWidth(str) ) / 2;
14: int yStart = ( getSize().height + fm.getHeight() ) / 2;
15:
16: g.drawString(str, xStart, yStart);
17: }
18:
19: }

Abbildung 9.4: Das Applet CenterString

Farben

Es wäre langweilig, schwarz auf einen grauen Hintergrund zu zeichnen. Die Verwendung verschiedener Farben ist viel interessanter. Java stellt durch die Klasse Color Methoden und Verhalten bereit, die die aktuellen Vordergrund- und Hintergrundfarben setzen, so daß Sie mit den erzeugten Farben zeichnen können.

Java verwendet 24-Bit-Fraben, wo eine Farbe als Kombination aus Rot-, Grün- und Blauwerten dargestellt wir d(RGB-Modell). Jede Komponente der Farbe kann eine Zahl zwischen 0 und 255 sein, 0,0,0 ist schwarz, 255,255,255 ist weiß. Dieses Modell kann Millionen von Farben darstellen.

Dieses abstrakte Farbmodell von Java kann auf die Plattform abgebildet werden, auf der Java läuft, die möglicherweise nur 256 Farben oder weniger verwendet. Wenn eine Farbe in einem Farbobjekt nicht zur Verfügung steht, kann die resultierende Farbe auf eine andere Farbe abgebildet werden, abhängig davon, wie der jeweilige Browser das handhabt und welche Plattform verwendet wird. Java ermöglicht Ihnen zwar die Verwendung von Millionen Farben, aber nicht alle stehen Ihnen im wirklichen Leben zur Verfügung.

Color-Objekte

Um ein Objekt in einer bestimmten Farbe zu zeichnen, erzeugen Sie eine Instanz der Color-Klasse, die diese Farbe repräsentiert. Die Klasse Color stellt Standard-Color-Objekte bereit, die in Klassenvariablen gespeichert werden, und die Ihnen ermöglichen, einige der gebräuchlichsten Farben zu verwenden. Color.red beispielsweise ist das Color-Objekt für Rot (RGB-Wert 255,0,0). Tabelle 9.3 zeigt die Standardfarben, die die Klasse Color definiert.

Tabelle 9.3: Standardfarben

Farbname

RGB-Wert

Color.black

0,0,0

Color.blue

0,0,255

Color.cyan

0,255,255

Color.darkGray

64,64,64

Color.gray

128,128,128

Color.green

0,255,0

Color.lightGray

192,192,192

Color.magenta

255,0,255

Color.orange

255,200,0

Color.pink

255,175,175

Color.red

255,0,0

Color.white

255,255,255

Color.yellow

255,255,0

Wenn die gewünschte Farbe hier nicht bereitgestellt wird, erzeugen Sie ein Color-Objekt für eine beliebige Kombination aus Rot, Grün und Blau:

Color c = new Color(140,140,140);

Alternativ können Sie ein Color-Objekt auch unter Verwendung von drei Fließkommawerten zwischen 0.0 und 1.0 anlegen, die die Prozentsätze der Farbattribute darstellen. Die folgende Zeile erzeugt dieselbe Farbe wie mit dem Farbwert 140,140,140:

Color c = new Color(0.55,0.55,0.55);

Farben abfragen und setzen

Um ein Objekt oder Text in einer Farbe zu zeichnen, setzen Sie das entsprechende Color-Objekt, so wie Sie die aktuelle Schrift angegeben haben. Dazu verwenden Sie die Methode setColor() aus der Klasse Graphics:

g.setColor(Color.green);

Jetzt erfolgen alle Zeichenoperationen in dieser Farbe.

Neben der aktuellen Farbe für den Grafikkontext können Sie auch die Hintergrund- und Vordergrundfarben für das Applet setzen: setBackground() und setForeground(). Beide Methoden sind in der Klasse java.awt.Component definiert, von der Applet abgeleitet ist.

setBackground() setzt die Hintergrundfarbe des Applets, normalerweise grau. Sie nimmt ein Argument entgegen, ein Color-Objekt:

setBackground(Color.white);

Die Methode setForeground() nimmt ebenfalls ein Color-Objekt als Argument entgegen und wirkt sich auf alles aus, was bisher auf dem Applet gezeichnet wurde, unabhängig von der vorherigen Farbe. Mit setForeground() ändern Sie die Farbe von allem, was im Applet gezeichnet wurde:

setForeground(Color.black);

Es gibt entsprechende Methoden, die die aktuelle Farbe ermitteln, getColor(), getBackground() und getForeground(). Anhand dieser Methoden können Sie Farben wählen, die den bereits im Applet existierenden Farben entsprechen:

setForeground(g.getColor());

Ein einfaches Beispiel

Listing 9.4 zeigt den Code für ein Applet, das den Zeichenbereich mit Quadraten füllt, die jeweils eine zufällige Farbe haben. Es kann jede Größe verarbeiten und füllt den Bereich automatisch mit der richtige Anzahl von Feldern.


Listing 9.4: ColorBoxes.java

1: import java.awt.Color;
2: import java.awt.Graphics;
3:
4: public class ColorBoxes extends java.applet.Applet {
5:
6: public void paint(Graphics g) {
7: int rval, gval, bval;
8:
9: for (int j = 30; j < (getSize().height - 25); j += 30)
10: for (int i = 5; i < (getSize().width - 25); i += 30) {
11: rval = (int)Math.floor(Math.random() * 256);
12: gval = (int)Math.floor(Math.random() * 256);
13: bval = (int)Math.floor(Math.random() * 256);
14: g.setColor(new Color(rval,gval,bval));
15: g.fillRect(i, j, 25, 25);
16: g.setColor(Color.black);
17: g.drawRect(i-1, j-1, 25, 25);
18: }
19:
20: }
21:
22: }

Abbildung 9.5: Das Applet ColorBoxes

Einfache Animation

Animation in Java umfaßt zwei Schritte: Das Erzeugen eines Einzelbildes und die Ausgabe dieses Einzelbildes. Diese beiden Schritte werden so oft wiederholt, bis der Eindruck der Bewegung auf dem Bildschirm entsteht. Die grundlegenden statischen Applets, die Sie gestern erzeugt haben, habe Ihnen gezeigt, wie ein solcher »Frame« angelegt wird. Jetzt lernen Sie, wie Sie Java anweisen, einen Frame zu zeichnen.

Zeichnen und Neuzeichnen

Die Methode paint() wird immer dann aufgerufen, wenn das Applet neu gezeichnet werden soll - wenn es aufgerufen wird, wenn das Fenster verschoben oder wiederhergestellt wird, oder wenn es verdeckt und anschließend wieder freigegeben wurde. Sie können Java aber auch sonst jederzeit auffordern, das Applet neu zu zeichnen. Um die Ausgabe auf Ihrem Applet zu ändern, konstruieren Sie den Frame (Einzelbild), den Sie darstellen wollen, und fordern Java auf, ihn zu zeichnen. Wenn Sie das wiederholt und schnell genug tun und sich die Bilder nur wenig unterscheiden, erhalten Sie eine Animation in Ihrem Java-Applet. Das ist alles.

Aber wo findet all das statt? Nicht in der paint()-Methode - paint() bringt das Ganze nur auf den Bildschirm. Mit anderen Worten, es zeigt nur den aktuellen Frame der Animation an. Die eigentliche Änderung erfolgt an anderer Stelle in der Definition Ihres Applets.

An dieser »anderen Stelle« erzeugen Sie den Frame und rufen dann die Methode repaint() auf. repaint() fordert Java auf, paint() aufzurufen und damit Ihren Frame zu zeichnen.

Anfang und Ende

Sie erinnern sich an start() und stop() aus Lektion 8? Diese Methoden sorgen dafür, daß Ihr Applet läuft und beendet wird. Sie haben start() und stop() gestern nicht verwendet, weil diese Applets nur einmal gezeichnet haben. Bei Animationen und anderen Java-Applets, die wirklich eine Verarbeitung vornehmen, brauchen Sie start() und stop(), um die Ausführung auszulösen oder sie zu beenden, wenn der Leser die Seite mit dem Applet verläßt. Für viele Applets müssen Sie start() und stop() aus diesem Grund überschreiben.

Die Methode start() löst die Ausführung des Applets aus. Sie können entweder die Arbeit des Applets in dieser Methode unterbringen oder die Methoden eines anderen Objekts aufrufen. Normalerweise wird start() verwendet, um die Ausführung eines Threads zu veranlassen, so daß es eigenständig ausgeführt werden kann.

Die Methode stop() unterbricht die Ausführung eines Applets, so daß es keine weiteren Systemressourcen verbraucht, wenn der Leser die Seite verläßt. Größtenteils sollten Sie für jede start()-Methode auch eine entsprechende stop()-Methode bereitstellen.

Eine kaputte digitale Uhr

Einige Beispiele sollen Ihnen helfen, die Beziehung zwischen diesen Methoden zu verdeutlichen. Listing 9.4 zeigt ein Beispiel-Applet, das versucht, mit Hilfe von Animation Datum und Zeit auszugeben, um so durch Aktualisierungen im Sekundenabstand eine sehr einfache animierte digitale Uhr zu erzeugen. Ein Einzelbild aus dieser Animation sehen Sie in Abbildung 9.6.

Abbildung 9.6: Ein Einzelbild aus dem Applet DigiClock


Das Wort »versucht« in der obigen Beschreibung ist sehr wichtig: Dieses Applet funktioniert nicht! Dennoch können Sie daraus viele Informationen über Animation gewinnen. In den folgenden Abschnitten lernen Sie, was dabei nicht stimmt. Versuchen Sie auch, das selbst herauszufinden.

Listing 9.5: DigiClock.java (diese Version funktioniert nicht)

1: import java.awt.Font;
2: import java.awt.Graphics;
3: import java.util.Date;
4:
5: public class DigiClock extends java.applet.Applet {
6:
7: Font theFont = new Font("TimesRoman", Font.BOLD, 24);
8: Date theDate;
9:
10: public void start() {
11: while (true) {
12: theDate = new Date();
13: repaint();
14: try { Thread.sleep(1000); }
15: catch (InterruptedException e) { }
16: }
17: }
18:
19: public void paint(Graphics g) {
20: g.setFont(theFont);
21: g.drawString(theDate.toString(), 10, 50);
22: }
23:
24: }


Haben Sie es erkannt? Betrachten wir den Code genauer.

Die Zeilen 7 und 8 definieren zwei Instanzvariablen, theFont und theDate, die Objekte enthalten, welche die aktuelle Schrift bzw. das Datum enthalten.

Die Methode start() löst die Ausführung des Applets aus. Beachten Sie, daß der Test in der while-Schleife immer true ergibt, die Schleife also nie beendet wird. In dieser while-Schleife wird jeweils ein Frame erzeugt:

Jetzt zur paint()-Methode. Her wird die aktuelle Schrift auf theFont (Zeile 20) gesetzt und der Inhalt von theDate wird auf den Bildschirm ausgegeben (Zeile 21). Beachten Sie, daß Sie toString() aufrufen müssen, um das Date-Objekt in einen ausdruckbaren String umzuwandeln. (Die meisten Objekte definieren diese praktische Methode.) Weil paint() wiederholt aufgerufen wird (über repaint() in Zeile 13), wir der String alle Sekunde mit dem Wert aktualisiert, der sich gerade in theDate befindet, um die aktuelle Zeit zu reflektieren.

Es gibt noch etwas zu sagen. Sie denken vielleicht, es wäre einfacher, das neue Date-Objekt in der paint()-Methode zu erzeugen. Auf diese Weise könnten Sie eine lokale Variable verwenden und bräuchten keine Instanzvariable für die Übergabe von Date. Das würde jedoch zu einem weniger effizienten Programm führen, weil die paint()-Methode immer aufgerufen wird, wenn ein Frame geändert werden muß. In diesem Fall ist das nicht kritisch, aber in einer Animation, wo die Frames sehr schnell abfolgen müssen, würde paint() Zeit brauchen, um dieses neue Objekt zu erzeugen. Indem Sie paint() das überlassen, was es am besten kann - auf den Bildschirm zu zeichnen - und die neuen Objekte schon vorher zu erzeugen, können Sie das Zeichnen so effizient wie möglich machen. Und genau deshalb wird auch das Font-Objekt in einer Instanzvariablen angelegt.

Das Problem

Warum aber funktioniert DigiClock nicht? ? Der einfache Grund dafür: es verwendet keine Threads. Statt dessen setzen Sie eine while-Schleife, die die Animation direkt durchläuft, bis Sie den appletviewer oder den Browser verlassen. Das scheint zwar ein geeigneter Ansatz zu sein, aber weil die while-Schleife in der Methode start() die gesamten Systemressourcen einnimmt - auch für das Neuzeichnen -, funktioniert das Ganze nicht. Wenn Sie versuchen, DigiClock.java in seiner aktuellen Version zu kompilieren und zu starten, erhalten Sie einfach nur einen leeren Bildschirm. Und Sie können das Applet nicht normal beenden, weil es keine Möglichkeit gibt, die geerbte stop()-Methode jemals aufzurufen.

Die Lösung ist, das Applet mit Threads zu entwickeln. Threads ermöglichen dem Applet, seine Animation auszuführen, ohne andere Systemoperationen zu beeinflussen.

Applets und Threads

Multithreading ist ein notwendiger Teil der Animation in Applets. Abhängig von Ihrer Erfahrung mit Betriebssystemen und mit Umgebungen auf diesen Systemen kennen Sie vielleicht das Konzept der Threads bereits. Es folgt eine sehr kurze Einführung in einfache Threads.

Wenn ein typisches Programm ausgeführt wird, startet es, führt seine Initialisierung aus, ruft Methoden auf und setzt die Ausführung oder Verarbeitung fort, bis es entweder fertig ist oder beendet wird. Diese Art Programm verwendet einen einzelnen Thread zur Steuerung des Programms. Wie ein Einzelkind beansprucht es das gesamte System, bis es fertig mit seinem Spiel ist. Multithreading, wie es in Java realisiert ist, ermöglicht die Verwendung vieler verschiedener Ausführungs-Threads, die jeweils gleichzeitig im selben Programm laufen können, und die sich nicht gegenseitig stören. Wie wohlerzogene Kinder teilen Threads die Systemressourcen, so daß alle spielen können.

Threads sind insbesondere dann praktisch, wenn Sie mehrere Applets auf derselben Seite haben. Ohne diese Threads müßte jedes Applet erst beendet werden, bevor das nächste ausgeführt werden könnte. Mit Hilfe von Threads können Sie veranlassen, daß viele Applets gleichzeitig auf derselben Seite laufen. Abhängig davon, wie viele Threads Sie haben, wird das System vielleicht ein bißchen langsamer, aber dennoch scheinen sie alle gleichzeitig zu laufen. (Das Wort »scheine« wird verwendet, weil sie in Wirklichkeit nicht parallel laufen. Sie werden in einzelnen Zeitscheiben ausgeführt.)

Auch wenn Sie nicht viele Applets auf derselben Seite haben, ist es sinnvoll, Threads zu verwenden. Die Faustregel für korrekte Applets ist, jede Animationsschleife und alles, dessen Ausführung lange dauern kann, in einem Thread abzulegen.

Applet-Threads verwenden

Wie erzeugen Sie ein Applet, das Threads verwendet? Es gibt einige Dinge zu berücksichtigen. Glücklicherweise sind sie alle sehr einfach. Vieles davon kann einfach aus einem anderen Applet einkopiert werden. Weil es so einfach ist, gibt es fast keinen Grund, keine Threads in Ihren Applets zu verwenden, wenn man die Vorteile betrachtet.

Damit ein Applet Threads verwendet, müssen Sie fünf Änderungen vornehmen:

Als erstes ändern Sie die erste Zeile Ihrer Klassendefinition. Sie haben etwa folgendes:

public class MyAppletClass extends java.applet.Applet {...}

Und Sie brauchen folgendes:

public class MyAppletClass extends java.applet.Applet
implements Runnable {...}

Was bewirkt das? Die Runnable-Schnittstelle wird in Ihrem Applet unterstützt. In Kapitel 4 haben Sie gesehen, daß eine Schnittstelle aus mehreren abstrakten Methodendeklarationen besteht, die in jeder Klasse implementiert werden können, die dieses Verhalten benötigt. Hier definiert die Schnittstelle Runnable das Verhalten, das Ihr Applet braucht, um einen Thread auszuführen. Insbesondere erhalten Sie dadurch eine Standarddefinition für die Methode run(). Durch Implementierung von Runnable teilen Sie anderen mit, daß sie die run()-Methode für Ihre Instanzen ausführen können.

Im zweiten Schritt führen Sie eine Instanzvariable ein, die den Thread für dieses Applet aufnimmt. Sie können ihr einen beliebigen Namen geben und sieh hat den Typ Thread. (Weil Thread eine Klasse in java.lang ist, müssen Sie sie nicht explizit importieren.) Weil der Thread im Applet ausführt wird, wollen wir ihn hier runner nennen.

Thread runner;

Jetzt führen Sie eine start()-Methode ein oder ändern eine existierende, so daß sie einfach nur einen neuen Thread erzeugt und ihn startet. Hier ein typisches Beispiel für eine start()-Methode:

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

Dieses Beispiel weist der Instanzvariablen runner einen neuen Thread zu und ruft dann die start()-Methode der Thread-Klasse auf, um die Ausführung von runner zu starten. Das bezeichnet man auch als Aufspannen des Threads.

Wenn Sie die start()-Methode des Applets ändern, so daß sie nur einen Thread aufspannt, wohin kommt dann der Rumpf Ihres Applets? Darum kümmern wir uns im vierten Schritt, bei der Deklaration der run()-Methode:

public void run() {
... // Der Rumpf Ihres Applet-Codes
}

Die run()-Methode kann alles enthalten, was im neuen Thread ausgeführt werden soll: Initialisierung, die Schleife für Ihr Applet oder irgend etwas anderes, das einen eigenen Thread braucht. Sie können auch neue Objekte erzeugen und Methoden aufrufen, die dann auch in diesem Thread ausgeführt werden. run() ist das eigentliche Herz Ihres Applets.

Schließlich sollten Sie noch eine stop()-Methode einführen, um die Ausführung des Threads abbrechen zu können (und damit das Applet), wenn der Leser die Seite verläßt. Die stop()-Methode könnte wie folgt aussehen:

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

Hier erledigt stop() zwei Dinge: sie beendet die Ausführung des Threads und setzt die Variable runner auf null. Dadurch steht das Thread-Objekt für die Speicherbereinigung an, so daß das Applet nach einer bestimmten Zeit aus dem Speicher entfernt werden kann. Wenn der Leser auf die Seite zurückkommt, erzeugt die start()-Methode einen neuen Thread und startet das Applet neu.

Das ist alles! Fünf einfache Änderungen, und Sie haben ein wohlerzogenes Applet, das seinen eigenen Thread ausführt!

Die reparierte Uhr

Nachdem Sie wissen, wie Threads in Applets eingesetzt werden, wollen wir DigiClock.java korrigieren, indem wir die fünf Änderungen aus dem letzten Abschnitt einfügen. Der resultierende Code ist in Listing 9.6 gezeigt.

public class DigiClock extends java.applet.Applet

implements Runnable {

...

}

Thread runner;

public void run() {

while (true) {

...

}

}
public void start() {

if (runner == null) {

runner = new Thread(this);

runner.start();

}

}


public void stop() {

if (runner != null) {

runner.stop();

runner = null;

}

}

Fertig! Und das in weniger als einer Minute! Den neuen Code sehen Sie in Listing 9.6.

Listing 9.6: DigiClock.java (Thread-Version)

1: import java.awt.Font;
2: import java.awt.Graphics;
3: import java.util.Date;
4:
5: public class DigiClock extends java.applet.Applet
6: implements Runnable {
7: Font theFont = new Font("TimesRoman", Font.BOLD, 24);
8: Date theDate;
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: runner.stop();
21: runner = null;
22: }
23: }
24:
25: public void run() {
26: while (true) {
27: theDate = new Date();
28: repaint();
29: try { Thread.sleep(1000); }
30: catch (InterruptedException e) { }
31: }
32: }
33:
34: public void paint(Graphics g) {
35: g.setFont(theFont);
36: g.drawString(theDate.toString(), 10, 50);
37: }
38:
39: }

Bilder laden und verwenden

Bei der Arbeit mit Animation liegen Ihnen in der Regel vorgefertigte Bilder vor, die Sie als Einzelbilder (Frames) in Ihrem Applet einsetzen können. Die Verwendung solcher Bilder ist in Java sehr einfach. Die Klasse Image in java.awt stellt abstrakte Methoden zur Darstellung eines allgemeinen Bildverhaltens zur Verfügung, und mit speziellen Methoden, die in Applet und Graphics definiert sind, können Sie Bilder ganz einfach laden und verwenden. In diesem Abschnitt erfahren Sie, wie Bilder geladen und gezeichnet werden, und wie Bilder in Java-basierten Animationen implementiert werden.


Momentan unterstützt Java nur GIF- und JPEG-Formate. Achten Sie darauf, daß Ihre Bilder in einem dieser Formate vorliegen.

Bilder laden

Um ein Bild in Ihrem Applet anzuzeigen, müssen Sie dieses über das Internet in Ihr Java-Programm herunterladen. Bilder werden als separate Dateien gespeichert, deshalb müssen Sie Java mitteilen, wo auf dem Server es sie findet.


Wie die Klassendateien Ihres Applets müssen auch Bilddateien vom Server auf das Client-System heruntergeladen werden. Weil das Applet nicht auf das Dateisystem des Clients schreiben kann, speichern Sie diese Bilddateien lokal, d.h., immer wenn Ihr Applet paint() aufruft, muß das Bild über das Internet heruntergeladen werden. Wenn Sie es mit sehr großen oder komplexen Bildern zu tun haben, kann Ihr Applet deshalb an Effizienz (also Geschwindigkeit) verlieren, während Sie die Bilder laden. Das sollten Sie berücksichtigen, wenn Sie Bilder in Ihren Java-Applets verwenden.

Die Klasse Applet stellt die Methode getImage() bereit, die ein Bild lädt und automatisch eine Instanz der Klasse Image für Sie erzeugt. Um sie zu verwenden, brauchen Sie nur die Klasse java.awt.Image zu importieren und getImage() die URL des gewünschten Bildes zu übergeben. Es gibt zwei Möglichkeiten, diesen zuletzt beschriebenen Schritt vorzunehmen:

Die erste Vorgehensweise scheint einfacher zu sein (die URL als URL-Objekt zu übergeben), aber die zweite ist flexibler, denn Sie müssen .java-Dateien neu kompilieren, wenn Sie eine hart-codierte URL eines Bildes verwenden und dann Ihre Dateien an eine andere Position auf dem Server verschieben.

Das letztere Format ist deshalb in der Regel besser geeignet. Die Klasse Applet stellt zwei Methoden bereit, die helfen, das Basis-URL-Argument für die Methode getImage() zu ermitteln:

Ob Sie getDocumentBase() oder getCodeBase() verwenden, hängt davon ab, ob Ihre Bilder relativ zu Ihren HTML-Dateien oder relativ zu Ihren Java-Klassendateien abgelegt sind. Verwenden Sie die Methode, die am besten für Sie geeignet ist. Beachten Sie, daß jede dieser Methode flexibler ist, als hart-codierte URLs oder Pfadnamen in der getImage()-Methode anzugeben. Wenn Sie diese Methoden verwenden, können Sie Ihre HTML-Dateien und Applets verschieben, und Java wird sie weiterhin finden.

Hier folgen einige Beispiele für getImage(), damit Sie sich besser vorstellen können, wie es verwendet wird. Der erste Aufruf von getImage() lädt die Datei an einer bestimmten URL (http://www.server.com/files/image.gif). Wenn sich ein Teil dieser URL ändert, müßten Sie Ihr Java-Applet neu kompilieren, um den neuen Pfad berücksichtigen zu können.

Image img = getImage(new URL("http://www.server.com/files/image.gif"));

In der folgenden Form von getImage() befindet sich die Datei image.gif im selben Verzeichnis wie die HTML-Dateien für das Applet, das das Bild braucht:

Image img = getImage(getDocumentBase(), "image.gif");

Aufähnliche Weise befindet sich hier die Datei image.gif im selben Verzeichnis wie das Applet selbst:

Image img = getImage(getCodeBase(), "image.gif");

Wenn Sie sehr viele Bilddateien haben, sollten Sie sie in einem eigenen Unterverzeichnis ablegen. Die folgende Form von getImage() sucht im Verzeichnis myimages nach der Datei image.gif. Dabei handelt es sich um ein Unterverzeichnis des Verzeichnisses, in dem das Java-Applet abgelegt ist.

Image img = getImage(getCodeBase(), "myimages/image.gif");

Wenn getImage() die angegebene Datei nicht findet, gibt es null zurück. Der Versuch, ein null-Bild zu zeichnen, gibt einfach »nichts« aus. Eine andere Verwendung des null-Bildes würde einen Fehler verursachen.


In URLs sehen Sie den Schrägstrich (/) in Pfadnamen, das ist Standard im World Wide Web. Auch wenn auf Windows-Plattformen der Backslash (\) verwendet wird, ist JBuilder intelligent genug, den Schrägstrich (/) in relativen Pfadnamen in Java-Code einzusetzen, so daß Sie die Pfadnamen für Ihren Code nicht ändern müssen, wenn er im Internet korrekt ausgeführt werden soll.

Bilder zeichnen

Alle diese Aufrufe von getImage() tun nichts, außer das Bild zu laden und es in einer Instanz der Klasse Image abzulegen. Jetzt müssen Sie etwas damit machen. Sehr wahrscheinlich wollen Sie es anzeigen. Die Klasse Graphics stellt sechs Methoden genau dafür bereit, die alle drawImage() heißen. Zwei davon werden im folgenden gezeigt.

Die erste Form von drawImage() nimmt vier Argumente entgegen: eine Instanz des anzuzeigenden Bildes, die x- und y-Position der oberen linken Ecke sowie this:

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

Diese erste Form macht genau, was Sie erwarten: sie zeichnet das Bild img mit seinen Originalmaßen mit der oberen linken Ecke an den vorgegebenen x,y-Koordinaten. Listing 9.7 zeigt den Code für ein sehr einfaches Applet, das das Bild ladybug.gif lädt und anzeigt. Abbildung 9.7 zeigt den resultierenden Frame.

Listing 9.7: Ladybug.java

1: import java.awt.Graphics;
2: import java.awt.Image;
3:
4: public class Ladybug extends java.applet.Applet {
5:
6: Image bugImg;
7:
8: public void init() {
9: bugImg = getImage(getCodeBase(), "myimages/ladybug.gif");
10: }
11:
12: public void paint(Graphics g) {
13: g.drawImage(bugImg, 10, 10, this);
14: }
15:
16: }

Abbildung 9.7: Das Applet Ladybug


In diesem Beispiel haben Sie die Instanzvariable bugImg, die das Bild von dem Marienkäfer enthält, und die sich in der Methode init() befindet. Die Methode paint() zeichnet das Bild auf den Bildschirm.

Die zweite Form von drawImage() nimmt sechs Argumente entgegen: das zu zeichnende Bild, die x- und y-Koordinaten, Breite und Höhe eines Rechtecks, das das Bild umschließt, und this. Wenn die Argumente für Breite und Höhe kleiner oder größer als das aktuelle Bild sind, wird es automatisch skaliert. Mit Hilfe dieser zusätzlichen Argumente können Sie Bilder vergrößern oder verkleinern. Beachten Sie jedoch, daß das Bild dabei vielleicht verzerrt wird.

Am besten ist es, die Größe des geladenen Bildes zu ermitteln, so daß Sie es mit einem bestimmten Prozentsatz skalieren können und somit Verzerrungen vermeiden. Dazu stellt die Klasse Image zwei Methoden bereit: getWidth() und getHeigth(). Beide nehmen ein Argument entgegen, eine Instanz von ImageObserver, mit der das Laden des Bildes verfolgt wird (mehr darüber in der Analyse zu Listing 9.8). Größtenteils können Sie getWidth() oder getHeight() this als Argument übergeben.

Wenn Sie das Marienkäferbild in der Variablen bugImg ablegen, würde die folgende Codezeile seine Breite zurückgeben, dargestellt in Pixeln:

theWidth = bugImg.getWidth(this);

Listing 9.8 zeigt, wie das Marienkäferbild in verschieden Größen skaliert wird. Abbildung 9.8 zeigt die resultierende Bildschirmausgabe.

Listing 9.8: Ladybugs.java

1: import java.awt.Graphics;
2: import java.awt.Image;
3:
4: public class Ladybugs extends java.applet.Applet {
5:
6: Image bugImg;
7:
8: public void init() {
9: bugImg = getImage(getCodeBase(), "myimages/ladybug.gif");
10: }
11:
12: public void paint(Graphics g) {
13:
14: int iWidth = bugImg.getWidth(this);
15: int iHeight = bugImg.getHeight(this);
16: int xPos = 10;
17:
18: // 25%
19: g.drawImage(bugImg, xPos, 10, iWidth/4, iHeight/4, this);
20:
21: // 50%
22: xPos += iWidth/4 + 15;
23: g.drawImage(bugImg, xPos, 10, iWidth/2,
24: iHeight/2, this);
25:
26: // 100%
27: xPos += iWidth/2 + 15;
28: g.drawImage(bugImg, xPos, 10, this);
29:
30: // 150% x, 25% y
31: g.drawImage(bugImg, 10, iHeight + 30,
32: (int)(iWidth*1.5), iHeight/4, this);
33: }
34:
35: }


Was hat es mit diesem letzten Argument von drawImage() auf sich, diesem mysteriösen this, das auch als Argument für getWidth() und getHeight() verwendet wird? Wozu braucht man es? Offiziell übergibt es ein Objekt, das als ImageObserver dient (d.h. ein Objekt, das die ImageObserver-Schnittstelle implementiert). ImageObserver-Objekte ermöglichen Ihnen, den Fortschritt des Ladeprozesses für ein Bild zu beobachten, um feststellen zu können, ob ein Bild vollständig oder nur teilweise geladen ist. Die Klasse Applet, von der Ihr Applet erbt, enthält das Standardverhalten für die Beobachtung von Bildern, das in der Regel ausreichend sein sollte - deshalb das Argument this für die Methoden drawImage(), getWidth() und getHeight(). Der einzige Grund, warum Sie ein alternatives Argument verwenden könnten, ist, wenn Sie sehr viele Bilder asynchron laden. Weitere Informationen finden Sie in der Dokumentation zu java.awt.image.ImageObserver.

Abbildung 9.8: Das Applet Ladybugs

Es gibt noch vier weitere drawImage()-Signaturen. Eine davon führt komplexere Skalierungen aus und nimmt zehn Argumente entgegen: das Bild, acht Koordinaten und this. Die letzten drei sind analog zu den ersten drei, mit einem zusätzlichen Argument, das eine alternative Hintergrundfarbe für die nicht-deckenden Teil des Bildes darstellt. Weitere Informationen über diese Varianten von drawImage() finden Sie in der Dokumentation zur Klasse Graphics.

Bilder ändern

Neben dem Laden und Zeichnen von Bildern stellt das Paket java.awt.Image auch Klassen und Schnittstellen zur Veränderung von Bildern und ihrer Farben oder zum Anlegen von Bitmaps bereit. Die meisten dieser Klassen machen ein Hintergrundwissen zur Bildverarbeitung erforderlich, unter anderem hinsichtlich der Farbmodelle und bitweisen Operationen. Alle diese Dinge können in diesem Einführungsbuch nicht erklärt werden, aber wenn Sie diesen Hintergrund einmal haben, werden die Klassen in java.awt.Image sehr interessant für Sie sein. Betrachten Sie den Beispielcode zum Erzeugen von Bildern, der im JDK enthalten ist. Dort finden Sie Beispiele, wie diese Klassen verwendet werden. Und auch JBuilder beinhaltet Beispiele zur Bildverarbeitung.

Animation mit Bildern

Filme bestehen aus Einzelbildern, die so schnell gezeigt werden, daß der Eindruck der Bewegung entsteht. Animationen nutzen dasselbe Prinzip. Jedes Bild unterscheidet sich ein kleines bißchen vom vorherigen, so daß der Eindruck der Bewegung entsteht.

In Java werden zum Erzeugen von Animationen mit Bildern dieselben Methoden verwendet wie zum Erzeugen von Grafikelementen. Der Unterschied ist, daß Sie mehrere Bilder haben, die Sie durchlaufen, und nicht mehrere Zeichenmethoden.

Am besten erkennen Sie, wie Bilder für eine Animation verwendet werden, indem Sie ein Beispiel durcharbeiten. Der folgende Abschnitt beschreibt die Animation einer kleinen Katze, Neko.

Ein Beispielprojekt

Neko war eine kleine Macintosh-Animation, die Kenji Gotoh 1989 geschrieben hat. »Neko« ist Japanisch und heißt »Katze«. Die Animation zeigt eine kleine Katze, die den Mauszeiger jagt, schläft, kratzt und auch sonst entzückend anzusehen ist. Das Programm Neko wurde auf fast jede mögliche Plattform portiert und ist außerdem als beliebter Bildschirmschoner auferstanden.

Für dieses Beispiel implementieren Sie eine kleine Animation, die auf der Neko-Grafik basiert. Das Original war autonom - es »erkannte« die Kanten des Fensters und lief dann in eine andere Richtung, wobei es weiterhin seine komischen Bewegungen machte. In diesem Applet betritt Neko die Szene links, hält in der Mitte an, gähnt, kratzt sich am Ohr, macht ein kleines Nickerchen und verläßt die Szene rechts wieder.


Das ist das bei weitem größte Applet, das in diesem Buch beschrieben wird. Statt es Zeile für Zeile aufzubauen, werden die einzelnen Abschnitte unabhängig voneinander beschrieben, wobei die bereits bekannten Dinge übergangen werden. Am Ende dieses Abschnitts finden Sie das vollständige Neko-Listing.

Jetzt zu dem Applet. Das Konzept der Animation mit Hilfe von Bildern ist, daß Sie mehrere Bilder haben und schnell hintereinander anzeigen, so daß sie den Eindruck der Bewegung vermitteln. Am einfachsten speichern Sie die Bilder dazu in einem Array der Klasse Image und führen eine spezielle Variable ein, die einen Verweis auf das jeweils aktuelle Bild enthält.


Technischer Hinweis
Die Klasse java.util enthält die Klasse HashTable, die eine Hashtable implementiert. Wenn sehr viele Bilder vorliegen, ist die Verwendung einer Hashtable zum Laden schneller und effizienter als ein Array. Hier wird jedoch ein Array verwendet, weil nur wenige Bilder vorliegen und weil Arrays besser für Animationen fester Länge geeignet sind.

Für das Applet Neko führen Sie Instanzvariablen für die folgenden Dinge ein: ein Array, das die Bilder aufnimmt, nekopics, und eine Variable vom Typ Image, die das aktuelle Bild aufnimmt.

Image nekopics[] = new Image[9];
Image currentimg;

Weil Sie die Position des aktuellen Bildes in den Methoden dieses Applets bekannt machen müssen, müssen Sie auch die aktuellen x- und y-Positionen verfolgen. Y bleibt hier konstant, x variiert. Wir brauchen zwei Instanzvariablen für diese Positionen:

int xpos;
int ypos = 50;

Jetzt zum Rumpf des Applets. Bei der Initialisierung haben Sie alle Bilder eingelesen und im Array nekopics abgelegt. Diese Operation wird am besten in einer init()-Methode ausgeführt.

Angenommen, Sie haben neun Bilder mit neun verschiedenen Dateinamen, könnten Sie für jeden davon getImage() aufrufen. Sie können sich ein bißchen Schreibarbeit sparen, indem Sie ein Array für die Dateinamen anlegen (nekosrc, ein Array mit Strings), und diese dann in einer for-Schleife durchlaufen. Hier die init()-Methode für das Applet Neko, die alle Bilder in das Array nekopics lädt:

public void init() {
Image
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(), "myimages/" + nekosrc[i]);
}
}

Beachten Sie, daß im Aufruf von getImage() das Verzeichnis, in dem diese Bilder abgelegt sind, als Teil des Pfads angegeben ist. Mit den geladenen Bildern können Sie die Animation beginnen. Das erfolgt in der run()-Methode des Threads für das Applet. In diesem Applet macht Neko fünf wichtige Dinge:

Sie hätten dieses Applet auch animieren können, indem Sie einfach zur richtigen Zeit das richtige Bild am Bildschirm anzeigen, aber es ist sinnvoller, das Applet so anzulegen, daß die einzelnen Aktivitäten von Neko in einzelnen Methoden bereitgestellt werden. Auf diese Weise können Sie einige der Aktivitäten wiederholen, wenn Neko die Dinge in anderer Reihenfolge ausgeführt werden sollen.

In dieser Anwendung wird schon viel geschlafen, aber Sie brauchen auch eine Methode, die eine Unterbrechung für verschiedene Zeitintervalle realisiert. Wir nennen sie pause() - und hier ihre Definition:

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

Jetzt können wir die Methode anlegen, um Neko laufen zu lassen. Weil Sie das mindestens zweimal brauchen, ist es sinnvoll, die Methode generisch anzulegen. Wir erzeugen die Methode nekorun(), die zwei Argumente entgegennimmt: die x-Position zum starten und die x-Position für das Ende. Neko läuft zwischen diesen beiden Positionen (y bleibt konstant).

Es gibt zwei Bilder, die Neko laufen lassen, um also den Effekt zu erzeugen, müssen Sie zwischen diesen beiden Bildern umschalten (sie sind an den Positionen 0 un 1 des Bildarrays abgelegt) und sie über den Bildschirm schieben. Das Verschieben erfolgt in einer einfachen for-Schleife von der Anfangs- zur Endposition. Für jedes Bild rufen Sie jeweils repaint() und pause() auf. Hier die Definition von nekorun():

void nekorun(inst start, int end) {
currentimg = nekopics[0];
for (int i = start; i < end; i+=10) {
xpos = i;
// Bilder vertauschen
if (currentimg == nekopics[0])
currentimg = nekopics[1];
else currentimg = nekopics[0];
repaint();
pause(150);
}
}

In der zweiten Zeile inkrementieren Sie die Schleife um 10, also die x-Position um 10 Pixel. Warum nicht um 5 oder 8? Zehn scheint einfach für diese Animation am besten geeignet zu sein. Wenn Sie Ihre eigenen Animationen schreiben, müssen Sie ein bißchen mit den Abständen experimentieren, um den gewünschten Effekt zu erzielen.

Jetzt kommen wir zur paint()-Methode für die Frames. Sie ist hier trivial. paint() gibt das aktuelle Bild an der aktuellen x- und y-Position aus. Alle Informationen werden in Instanzvariablen abgelegt. Bevor Sie drawImage() aufrufen können, brauchen Sie ein Bild, das Sie zeichnen können, prüfen Sie also, daß currentimg nicht null ist:

public void paint(Graphics g) {
if currentimg != null
g.drawImage(currentimg, xpos, ypos, this);
}

Und jetzt zur run()-Methode, wo die eigentliche Verarbeitung der Animation passiert. Sie haben die Methode nekorun() erzeugt, die Sie in run() mit den entsprechenden Werten aufrufen, damit Neko von links in die Mitte läuft:

// von links in die Mitte
nekorun(0, getSize().width / 2);

Jetzt hält Neko an und gähnt. Sie haben für diese Dinge jeweils einen einzelnen Frame (Bild 2 und 3 im Array). Sie brauchen also keine separaten Methoden dafür. Sie setzen einfach das richtige Bild, rufen repaint() auf und unterbrechen die Ausführung die richtige Zeitspanne lang. Dieses Beispiel wählt eine Verzögerung von je einer Sekunde zwischen dem Gähnen. Hier der Code:

// anhalten und warten
currentimg = nekopics[2]
repaint();
pause(1000);

// gähnen
currentimg = nekopics[3]
repaint();
pause(1000);

Jetzt kratzt sich Neko. Es gibt hier keine horizontale Bewegung. Sie wechseln einfach zwischen den beiden Kratzbildern (an den Positionen 4 und 5 im Array). Weil das Kratzen wiederholt wird, wollen wir jedoch eine separate Methode dafür einführen.

Die Methode nekoscratch() nimmt ein Argument entgegen: wie oft gekratzt werden soll. Mit diesem Argument können Sie in eine Schleife eintreten und zwischen den beiden Bildern wechseln, wobei jeweils repaint() aufgerufen wird:

void nekoscratch(int numtimes) {
for (int i = numtimes; i > 0; i--) {
currentimg = nekopics[4];
repaint();
pause(150);
currentimg = nekopics[5];
repaint();
pause(150);
}
}

In der run()-Methode rufen Sie nekoscratch() mit dem Argument 4 auf:

// 4mal kratzen
nekoscratch(4);

Nach dem Kratzen schläft Neko ein bißchen. Auch dafür gibt es zwei Bilder, an den Positionen 6 und 7 im Array, die Sie abwechselnd anzeigen. Hier die Methode nekosleep(), die ein Argument entgegennimmt, das angibt, wie oft »geschnarcht« werden soll:

void nekosleep(int numtimes) {
for (int i = numtimes; i > 0; i--) {
currentimg = nekopics[6];
repaint();
pause(250);
currentimg = nekopics[7];
repaint();
pause(250);
}
}

Rufen Sie nekosleep() im Rumpf der run()-Methode auf:

// 5mal schlafen
nekosleep(5);

Jetzt wacht Neko auf und verläßt uns. Die Methode wakeup() verwendet das letzte Bild aus dem Array (Position 8) und Sie können die Methode nekorun() ausführen, um zum Ende zu kommen:

// aufwachen und weglaufen
currentimg = nekopics[8];
repaint();
pause(500);
nekorun(xpos, getSize().width + 10);

Es gibt noch etwas, was im Applet erledigt werden muß. Die Bilder für die Animation haben alle einen weißen Hintergrund. Wenn sie auf dem Standardhintergrund eines Web-Browsers ausgegeben werden (mittelgrau), erscheint ein störendes weißes Feld um das Bild. Um das zu lösen, setzen Sie den Applet-Hintergrund auf weiß, wenn Sie die run()-Methode starten:

setBackground(Color.white);

Sie erkennen den Unterschied im appletviewer möglicherweise nicht, weil dort Ihr Windows-Farbschema verwendet wird, aber es macht einen großen Unterschied, wenn Sie das Applet in Ihrem Web-Browser ansehen. Es ist immer sinnvoll, den Applet-Hintergrund auf dieselbe Farbe wie den Hintergrund Ihrer Bilder zu setzen.

Das ist alles. Dieses Applet enthält viel Code, um diese relativ einfache Animation zu realisieren, aber er ist nicht all zu kompliziert. Das wichtigste dabei ist, wie bei allen Java-Animationen, den Frame einzurichten und dann repaint() aufzurufen, um den Bildschirm zu zeichnen. Listing 9.9 zeigt den vollständigen Code für das Applet Neko.

Listing 9.9: Neko.java

1: import java.applet.*;
2: import java.awt.*;
3:
4: public class Neko extends Applet implements Runnable {
5:
6: Image nekopics[] = new Image[9];
7: Image currentimg;
8: Thread runner;
9: int xpos;
10: int ypos = 50;
11:
12: public void init() {
13: String nekosrc[] = { "right1.gif", "right2.gif",
14: "stop.gif", "yawn.gif",
15: "scratch1.gif", "scratch2.gif",
16: "sleep1.gif", "sleep2.gif",
17: "awake.gif" };
18: for (int i=0; i < nekopics.length; i++) {
19: nekopics[i] = getImage(getCodeBase(),
20: "myimages/" + nekosrc[i]);
21: }
22: setBackground(Color.white);
23: }
24:
25: public void start() {
26: if (runner == null) {
27: runner = new Thread(this);
28: runner.start();
29: }
30: }
31:
32: public void stop() {
33: if (runner != null) {
34: runner.stop();
35: runner = null;
36: }
37: }
38:
39: public void run() {
40:
41: // rvon einer Seite in die Mitte laufen
42: nekorun(0, getSize().width / 2);
43:
44: // Anhalten und warten
45: currentimg = nekopics[2];
46: repaint();
47: pause(1000);
48:
49: // gähnen
50: currentimg = nekopics[3];
51: repaint();
52: pause(1000);
53:
54: // viermal kratzen
55: nekoscratch(4);
56:
57: // 5 Sekunden warten
58: nekosleep(5);
59:
60: // Aufwachen und davonlaufen
61: currentimg = nekopics[8];
62: repaint();
63: pause(500);
64: nekorun(xpos, getSize().width + 10);
65:
66: }
67:
68: void nekorun(int start, int end) {
69: currentimg = nekopics[0];
70: for (int i = start; i < end; i+=10) {
71: xpos = i;
72: // Bilder vertauschen
73: if (currentimg == nekopics[0])
74: currentimg = nekopics[1];
75: else currentimg = nekopics[0];
76: repaint();
77: pause(150);
78: }
79: }
80:
81: void nekoscratch(int numtimes) {
82: for (int i = numtimes; i > 0; i--) {
83: currentimg = nekopics[4];
84: repaint();
85: pause(150);
86: currentimg = nekopics[5];
87: repaint();
88: pause(150);
89: }
90: }
91:
92: void nekosleep(int numtimes) {
93: for (int i = numtimes; i > 0; i--) {
94: currentimg = nekopics[6];
95: repaint();
96: pause(250);
97: currentimg = nekopics[7];
98: repaint();
99: pause(250);
100: }
101: }
102:
103: void pause(int time) {
104: try { Thread.sleep(time); }
105: catch (InterruptedException e) { }
106: }
107:
108: public void paint(Graphics g) {
109: if (currentimg != null)
110: g.drawImage(currentimg, xpos, ypos, this);
111: }
112:
113: }

Das Flackern der Animation reduzieren

Wenn Sie die Beispiele in diesem Buch ausprobiert haben, haben Sie vielleicht bei CurrentData bemerkt, daß die Animation flackert. Das ist kein Programmfehler. Es ist ein Nebeneffekt beim Erzeugen von Animationen. Es gibt jedoch Möglichkeiten, das Flackern zu reduzieren, so daß Ihre Animationen sauberer ablaufen und besser aussehen.

Flackern ist das Ergebnis der Art und Weise, wie Java ein Einzelbild zeichnet. Am Anfang der heutigen Lektion haben Sie erfahren, daß beim Anfordern des Neuzeichnens repaint() die Methode paint() aufruft. Das ist nicht ganz richtig. Eigentlich passiert folgendes.

Schritt 2 erzeugt dasFlackern. Weil der Bildschirm zwischen den Einzelbildern gelöscht wird, entsteht durch das aufeinanderfolgende Löschen und Anzeigen ein Flackern. Es gibt drei Möglichkeiten, das Flackern zu vermeiden:

Wenn Ihnen die dritte Möglichkeit zu kompliziert erscheint - genau das ist sie. Beim doppelten Puffern wird auf eine Offscreen-Oberfläche gezeichnet, die dann als Ganzes auf den Bildschirm kopiert wird. Aber weil das so kompliziert ist, werden wir als letztes betrachten. Wir beginnen mit dem Überschreiben der update()-Methode.

Hier die Standardversion von update(), die Sie überschreiben:

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

Überzeichnen: Das Applet wird nicht gelöscht

Die erste Lösung zum Reduzieren des Flackerns ist, das Applet überhaupt nicht zu löschen. Das funktioniert aber nur für einige Applets. Das ColorSwirl-Applet gibt einen String auf den Bildschirm aus, aber dieser String wird in unterschiedlichen Farben angezeigt, die dynamisch ineinander übergeblendet werden. Dieses Applet flackert schrecklich. Listing 9.10 zeigt den Quellcode, Abbildung 9.9 das Ergebnis.

Listing 9.10: ColorSwirl.java

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

Abbildung 9.9: Das Applet ColorSwirl


Es gibt einige neue Dinge in diesem Applet, die erklärt werden sollen:


Weil das Flackern durch die update()-Methode verursacht wird, die das Applet löscht, ist die Lösung einfach. Sie überschreiben update() und löschen das Applet nicht mehr, weil das gar nicht nötig ist - nur die Farbe ändert sich. Jetzt ruft update() nur noch paint() auf. Und so sieht die update()-Methode für dieses Applet aus:

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

Mit dieser kleinen Ergänzung haben Sie das Flackern besiegt! War doch einfach! Fügen Sie dem ColorSwirl-Applet den Code hinzu und beobachten Sie seine Ausführung.

Clipping: Neuzeichnen, wenn das nötig ist

Für andere Applets ist es nicht ganz so einfach. Hier folgt ein anderes Beispiel Checkers zeigt ein rotes Oval an, das von einem schwarzen Quadrat in ein weißes verschoben wird. Listing 9.11 zeigt den Code für das Applet.

Listing 9.11: Checkers.java

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

Abbildung 9.10 zeigt einen Frame des Checkers-Applet.

Abbildung 9.10: Das Checkers-Applet

Hier ein Schnelldurchlauf durch die Arbeitsweise des Applets:


Die Lösung ist komplizierter als für das Applet ColorSwirl, weil Sie hier das Applet löschen müssen, bevor Sie den nächsten Frame zeichnen können. Andernfalls würde sich der rote Spielstein nicht bewegen, sondern über das Applet wachsen.

Was tun? Sie wollen die Animiation ausführen, aber statt den ganzen Applet-Berech zu löschen, löschen Sie nur den Teil, den Sie wirklich ändern. Durch diese Beschränkung des Neuzeichnens auf einen kleinen Bereich können Sie das Flakkern reduzieren.

Um zu begrenzen, was neugezeichnet wird, sind einige Dinge erforderlich. Zuerst brauchen Sie eine Möglichkeit, den Zeichenbereich festzulegen, so daß nur dieser Teil durch paint() neugezeichnet ist. Glücklicherweise ist das ganz einfach: durch Clipping.


Clipping ermöglicht Ihnen, den Zeichenbereich auf einen kleinen Bereich des Applets zu beschränken.


Jetzt müssen Sie eine Möglichkeit finden, den neu zu zeichnenden Bereich zu beobachten. Die rechte und linke Kante ändern sich für jeden Frame in der Animation Sie brauchen also Instanzvariablen, in denen Sie diese beiden x-Werte beobachten können.

Nach diesem Konzept wollen wir das Checkers-Applet so ändern, daß es nur das neu zeichnet, was notwendig ist. Zuerst fügen Sie die Instanzvariablen für die linke und rechte Kante des neu zu zeichnenden Bereichs ein. Wir nennen sie ux1 und ux2, ux1 für die linke Seite, ux2 für die rechte.

int ux1, ux2;

Jetzt ändern wir die run()-Methode, so daß sie den Bereich beobachtet. Sie glauben, das ist einfach, für jede Iteration der Animation sei einfach eine Aktualiserung auszuführen. Aber hier wird es kompliziert, und zwar aufgrund der Art und Weise, wie Java paint() und repaint() verwendet.

Das Problem bei der Aktualisierung der Kanten des neu zu zeichnden Bereichs jedes Frames der Animation ist, daß es nicht für jeden Aufruf von repaint() ein entsprechendes paint() geben muß. Wenn die Systemressourcen knapp werden, wird paint() möglicherweise nicht sofort ausgeführt, und mehrere Aufrufe von paint() stehen zur Ausführung an. In diesem Fall wird nicht versucht, alle paint()-Aufrufe auszuführen, sondern es wird nur der neueste Aufruf von paint() ausgeführt und die Warteschlange wird gelöscht (alle anderen Aufrufe von paint() werden verworfen).

Wenn Sie die Kanten des zu zeichnenden Bereichs mit repaint() aktualisieren und einige Aufrufe von paint() verworfen werden, werden einige Teile des neu zu zeichnenden Bereichs nicht aktualisiert und Teile des Ovals bleiben zurück. Es gibt eine Lösung: Aktualisieren Sie die vordere Kante des Ovals bei jeder Aktualisierung des Frames, aber die hintere Kante nur, wenn das letzte paint() wirklich erfolgt ist. Auf diese Weise wir der Zeichenbereich, wenn einige Aufrufe von paint() übersprungen werden, für jeden Frame größer, und wenn paint() schließlich aufgerufen wird, wird alles korrekt gezeichnet.

Ja, das ist furchtbar kompliziert und nicht gerade elegant. Aber ohne diesen Mechanismus wird das Applet nicht korrekt gezeichnet. Durchlaufen wir den Code, damit Sie verstehen, was passiert.

Wir beginnen mit run(), wo die Frames der Animation erzeugt werden. Hier berechnen Sie die Kanten des Zeichenbereichs anhand der alten und der neuen Position des Ovals. Wenn sich das Oval nach rechts bewegt, ist es ganz einfach. Der Wert von ux1 (linke Seite des neu zu zeichnenden Bereichs) ist die vorherige x-Position des Ovals (xpos), und der Wert von ux2 ist die x-Position das aktuelle Ovalposition plus die Breite des Ovals (hier sind das 90 Pixel).

Hier noch einmal die Original-Methode:

public void run() {
while (true) {
for (xpos = 5; xpos <= 105; xpos+=4) {
repaint();
try { Thread.sleep(100); }
catch (InterruptedException e) { }
}
for (xpos = 105; xpos > 5; xpos -=4) {
repaint();
try { Thread.sleep(100); }
catch (InterruptedException e) { }
}
}
}

In der ersten for-Schleife, wo das Oval nach rechts bewegt wird, aktualisieren Sie ux2:
ux2 = xpos + 90;

Nach dem repaint() aktualisieren Sie ux1, um die alte x-Position des Ovals zu reflektieren. Sie wollen diesen Wert jedoch nur aktualisieren, wenn paint() wirklich ausgeführt wurde. Wie können Sie das erkennen? Sie können ux1 am Ende der paint()-Methode auf einen bestimmten Wert zurücksetzen (0) und diesen dann abtesten:

if (ux1 == 0) ux1 = xpos;

Hier die neue for-Schleife, die das Oval nach rechts verschiebt:

for (xpos = 5; xpos <= 105; xpos+=4) {
ux2 = xpos + 90;
repaint();
try { Thread.sleep(100); }
catch (InterruptedException e) { }
if (ux1 == 0) ux1 = xpos;
}

Wenn das Oval nach links verschoben wird, kehrt sich alles um. Die linke Seite (ux1) ist die vordere Seite des Ovals, die immer aktualisiert wird, und die rechte Seite (ux2) wartet, ob sie aktualisiert werden muß. In der zweiten for-Schleife wird zuerst ux1 mit der x-Position des aktuellen Ovals aktualisiert:

ux1 = xpos;

Nach dem repaint() prüfen Sie, ob die paint()-Methode ausgeführt wurde, und aktualisieren ux2:

if (ux2 == 0) ux2 = xpos + 90;

Hier die neue Version der for-Schleife in run():

for (xpos = 105; xpos > 5; xpos -=4) {
ux1 = xpos;
repaint();
try { Thread.sleep(100); }
catch (InterruptedException e) { }
if (ux2 == 0) ux2 = xpos + 90;
}

Mehr muß in run() nicht geändert werden. Sie müssen jedoch update() aktualisieren, um den Bereich einzuschränken, der neu gezeichnet werden soll, den Sie in run() ermittelt haben. Dazu verwenden Sie die Methode clipRect(). Diese Methode ist in der Graphics-Klasse definiert und nimmt vier Argumente entgegen: die Anfangskoordinaten x und y sowie die Breite und Höhe des Bereichs.

Jetzt kommen ux1 und ux2 wieder ins Spiel. Die Variable ux1 stellt den x-Punkt der oberen Ecke des Bereichs dar. Durch Subtraktion von ux1 von ux2 erhalten Sie die Breite des Bereichs. Die y-Werte sind für die Höhe des Ovals gleich (5 und 95). Um die überschriebene update()-Methode zu vervollständigen, rufen Sie paint() auf:

public void update(Graphics g) {
g.clipRect(ux1, 5, ux2-ux1, 95);
paint(g);
}

Listing 9.12 zeigt den korrigierten Code des Checkers-Programms.

Listing 9.12: Checkers2.java

1: import java.applet.*;
2: import java.awt.*;
3:
4: public class Checkers2 extends Applet implements Runnable {
5:
6: Thread runner;
7: int xpos;
8: int ux1, ux2;
9:
10: public void start() {
11: if (runner == null); {
12: runner = new Thread(this);
13: runner.start();
14: }
15: }
16:
17: public void stop() {
18: if (runner != null) {
19: runner.stop();
20: runner = null;
21: }
22: }
23:
24: public void init() {
25: setBackground(Color.blue);
26: }
27:
28: public void run() {
29:
30: while (true) {
31:
32: for (xpos = 5; xpos <= 105; xpos+=4) {
33: ux2 = xpos + 90;
34: repaint();
35: try { Thread.sleep(100); }
36: catch (InterruptedException e) { }
37: if (ux1 == 0) ux1 = xpos;
38: }
39:
40: for (xpos = 105; xpos > 5; xpos -=4) {
41: ux1 = xpos;
42: repaint();
43: try { Thread.sleep(100); }
44: catch (InterruptedException e) { }
45: if (ux2 == 0) ux2 = xpos + 90;
46: }
47:
48: }
49:
50: }
51:
52: public void update(Graphics g) {
53: g.clipRect(ux1, 5, ux2-ux1, 95);
54: paint(g);
55: }
56:
57: public void paint(Graphics g) {
58: // Hintergrund zeichnen
59: g.setColor(Color.black);
60: g.fillRect(0, 0, 100, 100);
61: g.setColor(Color.white);
62: g.fillRect(101, 0, 100, 100);
63: // Spielstein zeichnen
64: g.setColor(Color.red);
65: g.fillOval(xpos, 5, 90, 90);
66: ux1 = ux2 = 0;
67: }
68:
69: }

Durch das Clipping reduzieren Sie nicht nur das Flackern, sondern Sie sparen auch Systemressourcen. Ein gutes Applet sollte immer so wenig Systemressourcen wie möglich verbrauchen. Es ist also auch dann sinnvoll, ein Clipping einzusetzen, wenn Sie kein Problem mit dem Flackern haben.

Doppeltes Puffern: Offscreen Zeichnen

Es gibt noch eine andere Möglihckeit, das Flackern zu reduzieren: doppeltes Puffern.

Dabei erzeugen Sie eine zweite Oberfläche (offscreen), wo das gesamte Zeichnen eroflgt, und dann zeichnen Sie die gesamte Oberfläche auf einmal auf den Applet-Bereich. Weil all die Arbeit hinter den Kulissen passiert, kann nichts den Zeichenprozeß unterbrechen und die Animation läuft glatt ab.

Das doppelte Puffern ist nicht immer die beste Lösung. Wenn Ihr Applet flackert, versuchen Sie es zuerst mit einer der beiden anderen Lösungen - vielleicht sind sie ausreichend, was sehr viel weniger Overhead bedeutet. Das doppelte Puffern ist weniger effizient als das normale Puffern und verbraucht Speicher und Platz. Wenn Sie es also vermeiden können, dann tun Sie das. Das Flackern wird jedoch sehr gut damit eliminiert.

Um ein Applet zu erzeugen, das eine doppelte Pufferung verwendet, brauchen Sie zwei Dinge: ein Offscreen-Bild, auf das Sie zeichnen, und einen Grafikkontext für dieses Bild. Dadurch wird die Zeichenfläche des Applets nachgebildet: Der Grafikkontext, stellt die Zeichemethoden bereicht, Image nimmt die zu zeichnenden Punkte auf.

Um ein doppeltes Puffern in Ihr Applet aufzunehmen, sind fünf Schritte erforderlich:

Zuerst müssen Ihr Offscreen-Bild und der Grafikkontext in den Instanzvariablen abgelegt werden, damit Sie sie der paint()-Methode übergeben können. Deklarieren Sie die folgenden Instanzvariablen in Ihrer Klassendefinition:

Image offscreenImage;
Graphics offscreenGraphics;

Erzeugen Sie bei der Initialisierung des Applets ein Image- und ein Graphics-Objekt und weisen Sie sie diesen Variablen zu. (Sie müssen warten, bis die Initialisierung erfolgt ist, so daß Sie wissen, wie groß sie sein müssen). Die Methode createImage() gibt Ihnen eine Instanz von Image, die Sie dann an die getGraphics()-Methode senden können, um einen neuen Grafikkontext für das Bild zu ermitteln:

offscreenImage = createImage(getSize().width, getSize().height);
offscreenGraphics = offscreenImage.getGraphics();

Wenn Sie auf den Bildschirm zeichnen müssen, dann zeichnen Sie jetzt in den Offscreen-Zeichenbereich. Mit der folgenden Codezeile beispielsweise würden Sie ein Quadrat von 100x100 Pixel in der aktuellen Farbe zeichnen:

offscreenGraphics.fillRect(0, 0, 100, 100);

Am Ende der paint()-Methode geben Sie den fertigen Offscreen-Puffer in die Zeichenfläche des Applets aus:

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

Jetzt müssen Sie update() überschreiben, so daß es den zeichenbereich zwischen den Zeichenvorgängen nicht mehr löscht:

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

Das ist alles ! Und sehr viel eleganter als die zweite Lösung. Beachten Sie aber, daß dadurch viel mehr Systemressourcen verbraucht werden, verwenden Sie diese Möglichkeit also nur, wenn es unbedingt nötig ist. Listing 9.13 zeigt das Applet Checkers3, eine Version des Checkers-Applets, das die doppelte Pufferung realisiert, damit Sie den Effekt vergleichen können.

Listing 9.13: Checkers3.java

1: import java.applet.*;
2: import java.awt.*;
3:
4: public class Checkers3 extends Applet implements Runnable {
5:
6: Thread runner;
7: int xpos;
8: int ux1, ux2;
9: Image offscreenImage;
10: Graphics offscreenGraphics;
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.stop();
22: runner = null;
23: }
24: }
25:
26: public void init() {
27: setBackground(Color.blue);
28: offscreenImage = createImage(getSize().width,
29: getSize().height);
30: offscreenGraphics = offscreenImage.getGraphics();
31: }
32:
33: public void run() {
34:
35: while (true) {
36:
37: for (xpos = 5; xpos <= 105; xpos+=4) {
38: ux2 = xpos + 90;
39: repaint();
40: try { Thread.sleep(100); }
41: catch (InterruptedException e) { }
42: if (ux1 == 0) ux1 = xpos;
43: }
44:
45: for (xpos = 105; xpos > 5; xpos -=4) {
46: ux1 = xpos;
47: repaint();
48: try { Thread.sleep(100); }
49: catch (InterruptedException e) { }
50: if (ux2 == 0) ux2 = xpos + 90;
51: }
52:
53: }
54:
55: }
56:
57: public void update(Graphics g) {
58: g.clipRect(ux1, 5, ux2-ux1, 95);
59: paint(g);
60: }
61:
62: public void paint(Graphics g) {
63: // Hintergrund zeichnen
64: offscreenGraphics.setColor(Color.black);
65: offscreenGraphics.fillRect(0, 0, 100, 100);
66: offscreenGraphics.setColor(Color.white);
67: offscreenGraphics.fillRect(101, 0, 100, 100);
68: // Spielstein zeichnen
69: offscreenGraphics.setColor(Color.red);
70: offscreenGraphics.fillOval(xpos, 5, 90, 90);
71: ux1 = ux2 = 0;
72: g.drawImage(offscreenImage, 0, 0, this);
73: }
74:
75: }

Wie Sie sehen, basiert diese Technik auf den zuvor gezeigten Techniken, es ist also sinnvoll, die Reichenfolge einzuhalten, und die Lösungen nacheinander zu probieren, und die »kleinste« zu verwenden. Auf diese Weise gehen Sie sicher, daß Sie die effizienteste Lösung mit zufriedenstellenden Ergebnissen haben.

Multimedia

Multimedia besagt im allgemeinen, daß Informationen mit Hilfe mehrere Medien weitergegeben wird, also beispielsweise durch Bild und Ton. Sie haben bereits erfahren, wie Sie in Ihren Animationen Bilder ausgeben. Jetzt erfahren Sie, wie Sie Sound einfügen, um Multimedia-Animationen zu schaffen.

Java unterstützt die Ausgabe von Sounds in Animationen. Momentan ist das einzige unterstützte Sound-Format Sun's AU, auch als m-law-Format bezeichnet. Die AU-Dateien sind in der Regel kleiner als Sound-Dateien anderer Formate, aber die Soundqualität ist häufig nicht sehr gut. Wenn Sie Qualität brauchen, sollten Sie besser die HTML-Methode verwenden (Links auf externe Dateien).

Sounds

Die einfachste Möglichkeit, Sound zu laden und abzuspielen erfolgt durch die play()-Methode der Klasse Applet, die in allen Ihren Applets zur Verfügung steht. Diese Methode ist der Methode getImage() ähnlich, auch sie kann zwei Formen annehmen:

Die folgende Codezeile würde den Sound meow.au abspielen, der im Verzeichnis mysounds im Applet-Verzeichnis abgelegt ist.

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

Die Methode play() lädt den Sound und spielt ihn ab. Wenn sie den Sound nicht findet, wird kein Fehler erzeugt; es wird einfach kein Sound abgespielt.

Wenn Sie einen Sound wiederholt abspielen wollen oder den Sound-Clip anhalten und wieder starten wollen, wird das ganze interessanter. In diesem Fall verwenden Sie die Methode getAudioClip(), um den Sound-Clip in eine Instanz der Klasse AudioClip zu laden (definiert in java.applet) und dann direkt mit dem AudioClip-Objekt zu arbeiten.

Angenommen, Sie haben eine Sound-Schleife, die Sie im Hintergrund Ihres Applets abspielen wollen. Dazu vewenden Sie den folgenden Code:

AudioClip clip = getAudioClip(getCodeBase(), "mysounds/loop.au");

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

clip.play();

Um das Abspielen zu unterbrechen, verwenden Sie stop():

clip.stop();

Um den Clip zu wiederholen, verwenden Sie loop():

clip.loop();

In Ihrem Applet können Sie beliebig viele Audio-Clips abspielen. Wenn die Methode getAudioClip() einen Sound nicht findet, gibt sie null zurück. Sie sollten diesen Fall abtesten, bevor Sie versuchen, den Clip abzuspielen, weil der Versuch, play(), stop() oder loop() aufzrufen, sonst eine Ausnahme aufwirft.

Beachten Sie bei der Verwendung eines (wiederholten) Hintergrund-Sounds, daß der Cliß das Abspielen nicht automatisch beendet, wenn Sie den Applet-Thread beenden. Das bedeutet, wenn Ihr Leser die Seite verläßt, werden die Sounds des ersten Applets weitergespielt, auch wenn ein zweites Applet geladen wird. Das lösen Sie durch eine weitere Codezeiole in Ihrer stop()-Methode: if (bgsound != null) bgsound.stop();

Listing 9.14 zeigt das Applet AudioLoop, das zwei Sounds abspielt. Der erste, ein Hintergrund-Sound, loop.au, wird wiederholt abgespielt. Der zweite, beep.au, wird alle fünf Sekunden abgespielt. Die Grafik ist unwichtig und wird hier nicht gezeigt; sie gibt nur den Namen des Applets auf dem Bildschirm aus.

Listing 9.14: AudioLoop.java

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

Das Animator-Applet von Sun

Weil die meisten Java-Animationen sehr viel Code gemeinsam haben, ist es am einfachsten, diesen Code so weit als möglich wiederzuverwenden. Aus diesem Grund stellt Sun als Teil des Standard-JDK die Klasse Animator bereit.

Animator stellt eine einfache, allgemeine Animations-Schnittstelle bereit. Der Code wird kompiliert und in einer HTML-Datei werden die entsprechenden Parameter für die Animation bereitgestellt. Mit dem Animator-Applet können Sie folgendes tun:

Auch wenn Sie den Animator nicht verwenden wollen, stellt er ein gutes Beispiel für Animationen in Java und möglicheTricks dar. Die Klasse Animator ist Teil des JDK und im Standard-JBuilder enthalten, nämlich unter:

C:\JBuilder\samples\java\demo\Animator

Zusammenfassung

Sie geben etwas auf dem Bildschirm aus, indem Sie es in Ihrem Applet zeichnen: Formen, Grafik, Text oder Bilder. Heute haben Sie die Grundlagen des Zeichnens mit elementaren Grafikfunktionen kennengelernt und wie Sie mit Hilfe von Color-Objekten der Bildschirmausgabe Farbe verleihen. Mit diesen Grundlagen können Sie Anmiationen in Ihrem Applet verwirklichen und mit Bildern arbeiten.

Sie haben viel über Animation und ihre Methoden erfahren und haben die Verwendung von Threads kennengelernt. Außerdem wissen Sie jetzt, wie man Bilder in Applets lädt und anzeigt, und wie Animationen mit Hilfe von Bildern erzeugt werden.

Sie haben drei Techniken zum Reduzieren des Flackerns kennengelernt: Überzeichnen, Clipping und doppeltes Puffern. Außerdem haben Sie gesehen, Wie Sie Multimedia-Animationen schaffen, indem Sie Audio einführen. Das Animator-Applet von Sun wurde kurz vorgestellt, das als Schablone für Ihre eigenen Animationen dienen kann, ebenso wie als Beispiel für komplexe Techniken, die Sie in Animations-Applets einsetzen können.

F&A

F In allen Beispielen wurden hier Linien erzeugt, die nur ein Pixel breit sind. Wie kann ich die Linien breiter zeichnen?

A In der aktuellen Implementierung von Graphics ist das nicht möglich. Es gibt keine Möglichkeit, die Standardbreite der Linie zu ändern. Wenn Sie eine dickere Linie brauchen, müssen Sie mehrere Linien nebeneinander zeichnen, das erzielt denselben Effekt.

F Ich habe ColorBoxes ausprobiert, aber viele Rechtecke haben immer dieselbe Farbe. Dasselbe passiert bei ColorSwirl. Warum?

A Wahrscheinlich stehen in Ihrem Browser oder auf Ihrem System nicht genug Farben zur Verfügung. Wenn Ihr System bestimmte Farben nicht erzeugen kann, die Color bereitstellt, oder wenn Ihr Browser zu viele Farben für andere Dinge reserviert hat, werden im Applet doppelte Farben angezeigt, abhängig von der Einstellung von Browser und System. Normalerweise verwendet Ihr Applet nicht so viele Farben, deshalb wird Ihnen dieses Problem in echten Programmen kaum begegnen.

F Warum der Umweg mit paint(), repaint() und update()? Warum verwendet man nicht einfach eine paint()-Methode, die alles auf den Bildschirm zeichnet, wo man es haben will?

A Der Grund dafür ist, daß Java verschachtelte Oberflächen zum Zeichnen erlaubt. Wenn ein Neuzeichnen erfolgt, werden alle Teile des Systems neugezeichnet, von der äußersten bis zur inneren Oberfläche. Weil das Zeichnen Ihres Applets zur selben Zeit erfolgt, erhält es keine Sonderbehandlung - es wird wie alles andere gezeichnet. Sie opfern zwar die Direktheit, aber dadurch ist es möglich, daß Ihr Applet friedlich mit dem restlichen System koexistiert.

F Wenn ein Applet Threads verwendet, kann ich diesem dann einfach sagen, daß er das Applet starten und beenden soll? Muß ich nichts in meinen Schleifen testen oder den Thread-Status beobachten?

A Das stimmt. Wenn Sie ein Applet in einen Thread einbinden, kann Java die Ausführung besser kontrollieren. Wenn der Thread angehalten wird, wird auch Ihr Applet angehalten, und es wird fortgesetzt, wenn der Thread wieder fortgesetzt wird. Praktisch, was?

Workshop

Der Workshop bietet zwei Möglichkeiten, zu überprüfen, was Sie in diesem Kapitel gelernt haben. Der Quiz-Teil stellt Ihnen Fragen, die Ihnen helfen sollen, Ihr Verständnis für den vorgestellten Stoff zu vertiefen. Die Antworten auf die Fragen finden Sie in Anhang A. Der Übungen-Teil ermöglicht Ihnen, Erfahrungen in der Anwendung der Dinge zu sammeln, die Sie hier kennengelernt haben. Versuchen Sie, diese Dinge durchzuarbeiten, bevor Sie mit der nächsten Lektion weitermachen.

Quiz

g.fillRect(20,20,60,60);
setForeground(getBackground());

Übung

Abbildung 9.11: Das Applet PacIsBack


© 1997 SAMS
Ein Imprint des Markt&Technik Buch- und Software- Verlag GmbH
Elektronische Fassung des Titels: JBuilder in 14 Tagen, ISBN: 3-87791-895-6

Previous Page Page Top TOC Index Next Page See Page