Inhaltsverzeichnis

Java Tutorial #2 - "Splashscreen mit Java"

Autor: 'Kriz'

Wichtiger Hinweis!!!

Die Grundlagen über die Java-Programmierung ist nicht Bestandteil der Tutorialreihe! Zum Lernen von Java empfehle ich das hervorragende Online-Tutorial von Guido Krüger, welches man sich kostenlos von www.javabuch.de runterladen kann.

Zuerst einmal etwas graue Theorie:

Es gibt in Java mehrere Möglichkeiten, ein Bild in einem Fenster anzeigen zu lassen. Dabei ist der schnellste Weg auch gleichzeitig der effektivste von allen. Es gibt meiner Erkenntnis nach noch einen zweiten Weg, welcher aber langsamer ist und zudem noch nur in Swing (JFC Fenstermodell von Java) möglich ist. Der erste Weg ist dagegen auch mit den normalen Mitteln der AWT (Abstract Windowing Toolkit) möglich.

Java kann von Haus aus JPEG, GIF und PNG behandeln, wobei PNG erst mit den aktuellsten Versionen der JRE einwadnfrei funktioniert. Zudem ermöglicht Java die Benutzung von 8 bzw. 24 Bit colorierten PNGs inkl. Transparenz. Als guter Programmierer will man eigentlich immer nur das Beste erwarten, daher tendiere ich persönlich bei allen anzuzeigenden Grafiken zum PNG-Format. Das hat natürlich den Nachteil, daß das Java-Programm auf älteren JREs nicht optimal oder schlichtweg garnicht mehr funktioniert. Wenn man weiß, daß seine Applikation nicht nur von modernen JREs interpretiert wird, sollte man stattdessen auf PNGs verzichten und die anderen beiden Grafikformate benutzen:

24 Bit PNGJPEG
8 Bit PNG mit Transparenz → GIF

Falls einige es vielleicht noch nicht wissen sollten, ist bei den Programmierern heute der Einsatz des Grafikformats GIF verpönt oder bereits geächtet! Das liegt daran, daß GIF in der LZW-Komprimierung nicht mehr frei, sondern patentiert worden ist. Dabei ist nicht das Grafikformat GIF patentiert worden, sondern der dahinterstehende Komprimierungsalgorithmus LZW (Lempel Ziv Welch). Alle Programme, die GIFs kodieren und erzeugen, müssen daher Lizenzgebühren an die Fa. UNISYS zahlen. Programme, die nur GIFs dekodieren und anzeigen, sind von dieser Lizenzpolitik ausgenommen. Ich persönlich verzichte - soweit es mir möglich ist - auf GIFs. Da aber in nicht allzuferner Zukunft das PNG Grafikformat auch Animationsequenzen unterstützen soll, kann dann praktisch gänzlich auf GIFs verzichtet werden.

Nun gut, soviel dazu. Java kann also diese drei Grafikformate behandeln und anzeigen. Im Prinzip muß man jetzt nur ein Bild mit einem Bildbearbeitungsprogramm erstellen und als JPEG, GIF oder PNG abspeichern. Dabei sollte das Bild nicht allzu klein, aber auch nicht allzu groß ausfallen. Ich für meinen Teil erzeuge daher immer Bilder, die maximal 400*300 Pixel groß sind. Auf einem modernen System mit 1024*768 Pixeln Auflösung ist diese Maximalgröße genau richtig. Bei kleineren Auflösungen empfiehlt sich eine 100er-Reduzierung des Bildes:

1024*768 und größer → 400*300
800*600 → 300*200
640*480 → 200*100

Man sollte daher von seinem Bild diese drei Versionen erzeugen, wobei als kleiner Hinweis gelten sollte, die 400*300er Version zu erstellen und dann später in die beiden anderen Größen runterskalieren zu lassen. Mit Hilfe diverser Abfragen in Java kann man dann das passende Bild zur passenden Auflösung anzeigen lassen. Bevor wir aber jetzt zu solchen Zusatzschmankerln kommen, sollten wir uns erstmal das einfache Codegerüst für das Anzeigen eines einzelnen Bilds zusammenbasteln:

Das Codegerüst

Wir erstellen eine neue Datei für unsere SplashScreen Klasse und implementieren folgenden Java Code:

// SplashScreen.java
 
/* Eventuell hier erst das Paket definieren, falls erforderlich!
 
import java.awt.*;
 
public class SplashScreen
extends Window
{
    private Image        m_imageSplash = null;
    private MediaTracker m_mediaTracker = null;
 
    public SplashScreen(String strImagePath, Dimension dimImageSize)
    {
        super(new Frame());
        setLocation((getToolkit().getScreenSize().width - dimImageSize.width) / 2, 
                    (getToolkit().getScreenSize().height - dimImageSize.height) / 2);
        setSize(dimImageSize);
 
        if(strImagePath!=null && dimImageSize!=null)
        {
            m_imageSplash = getToolkit().getImage(strImagePath);
            m_mediaTracker = new MediaTracker(this);
            m_mediaTracker.addImage(m_imageSplash, 0);
 
            try
            {
                m_mediaTracker.waitForAll();
            }
            catch(InterruptedException e)
            {
            }
        }
    }
 
    public void paint(Graphics g)
    {
        g.drawImage(m_imageSplash, 0, 0, getSize().width, getSize().height, this);
    }
 
    public void start()
    {
        setVisible(true);
    }
 
    public void destroy()
    {
        setVisible(false);
        dispose();
    }
}

Unsere Klasse SplashScreen ist demnach von Window abgeleitet. Window definiert ein rahmen- und inhaltsloses Fenster, welches mit dem BorderLayout als Standardlayout angezeigt wird. Als Klassenelemente haben wir zwei private Objekte vom Typ Image und MediaTracker. Image definiert das zu ladende Bild und MediaTracker ist eine Spezialklasse zum Precachen von Bildern.

Als erstes wird im Konstruktor der SplashScreen-Klasse der Superkonstruktor der übergeordneten Klasse Window aufgerufen. Als einzigstes Argument erwartet Window ein Frame-Objekt. Da wir aber kein übergeordnetes Frame-Objekt besitzen, erzeugen wir einfach ein neues. Der Konstruktor von SplashScreen erwartet dagegen zwei Argumente: Einen String mit dem Pfad zum Bild und ein Dimension-Objekt mit der Größe des Bildes. Beides müssen wir später beim Erzeugen von SplashScreen-Objekten mit auf dem Weg geben!


Hinweis: Normalerweise lädt eine SplashScreen-Klasse immer nur aus einem fest definierten Pfad und meist auch nur ein Bild mit fest definierter Größe. Aber im Rahmen allgemein gehaltener Toolklassen belasse ich es mir dieser Lösung der SplashScreen-Klasse.



anach werden Position und Größe des Fenster definiert. Da wir unser Bild ja zentriert am Bildschirm haben wollen, müssen wir also über die JRE einige Abfragen starten über die derzeitige Bildschirmauflösung. Zugriffsschlüssel für eine Abfrage von Systemeigenschaften ist die Methode getToolkit(). Jedes Fenster hat zwar sein eigenes Toolkit, aber die meisten Fenster greifen rückbindend auf das DefaultToolkit() von Java zu. getToolkit() liefert ein Objekt vom Typ Toolkit zurück, welches selbst eine Methode namens getScreenSize() implementiert hat. getScreenSize() dagegen liefert selbst wieder ein Objekt vom Typ Dimension zurück und dieses enthält u.a. als öffentliche Elemente zwei int-Variablen namens width (Breite) und height (Höhe).

Wenn man nun die Auflösungsgrößen hat und zusätzlich noch die Größen des Bildes, dann kann man damit den Startpunkt des Fensters links oben auf dem Bildschirm definieren, indem man einfach von der jeweiligen Auflösungsgröße die dazugehörige Bildgröße subtrahiert und das Ergebnis nochmals durch 2 dividiert. Bei der Fenstergröße dagegen übergeben wir einfach die Bildgröße. Dann paßt nämlich das ganze Bild auch pixelgenau in das Fenster rein.

Danach fragen wir erstmal ab, ob die beiden an unsere SplashScreen-Klasse übergebenen Argumente ungleich null sind, also überhaupt existieren.

Falls ja, deklarieren wir als erstes das Bild. Dazu benutzen wir wieder die Methode getToolkit() und die aus Toolkit kommende Methode getImage(). getImage() benötigt einen String mit der Pfadangabe zu dem Bild, welches deklariert werden soll. Ist der Pfad korrekt, liefert getImage() das Bild in Form eines Image-Objekts, welches wir dann in unserem Klassenelement m_imageSplash speichern. Falls die Deklaration fehlschlägt, liefert die Methode null zurück. Wichtig zu wissen ist, daß getImage() definitiv kein Bild lädt, sondern nur deklariert. Das Laden von Bildern übernehmen in Java ganz andere Methoden.

Danach instanzieren wir unser Klassenelement m_mediaTracker. Der Konstruktor von MediaTracker erwartet ein Objekt vom Typ Component, zu dem der MediaTracker gehören soll. Da unser Fenster irgendwann einmal von Component abgeleitet worden ist, übergeben wir dem Konstruktor einfach einen simplen this Zeiger auf unser Fenster.

Der MediaTracker ist wie bereits erwähnt eine Spezialklasse zum Precachen von Bildern. Manchmal ist es nämlich sinnvoll, erst alle notwendigen Bilder zu laden, ehe das Programm weiterläuft. Dabei kann man elegant das Precachen steuern, indem man jedes zu precachende Bild einen Index mit auf den Weg gibt. Durch diese Indizierung ist es dem MediaTracker möglich, sowohl alle als auch nur einzelnde Bilder gleichzeitig oder nach und nach zu precachen. Wir beschränken uns aber auf das sofortige Precachen unserer Splashbilds.

Dazu rufen wir die Methode addImage() der Klasse MediaTracker auf und übergeben ihr als Argumente unser Bild und den Index 0. Mehr brauchen wir nicht. Danach wird es knifflig. Die Precaching-Methode von MediaTracker soll ja solange mit der Programmfortsetzung warten, bis alle angegebenen Bilder vorab geladen sind. Das geschieht dadurch, daß MediaTracker einen eigenen Hintergrundthread startet und dort die Ladevorgänge steuert. Ein Thread in Java wirft dagegen unter Umständen eine Ausnahme vom typ InterruptedException, da der Thread durch übergeordnete Prozesse, dem Betriebssystem oder der Hardware selber durchaus mal unterbrochen (interruptiert) werden kann. Zu diesem Zweck muß das Precachen innerhalb eines try/catch Blocks stattfinden, um die Ausnahme eventuell behandeln zu können. Innerhalb des Versuchsblocks rufen wir nun einfach die Methode waitForAll() der Klasse MediaTracker auf. Nun wartet Java solange mit der Fortsetzung des Programms, bis das Bild geladen worden ist.

Das waren die Vorgänge innerhalb des Konstruktors unserer Klasse. Darüberhinaus habe ich folgende Klassenmethoden definiert:

public void paint(Graphics g)
{
    g.drawImage(m_imageSplash, 0, 0, m_dimImageSize.width, m_dimImageSize.height, this);
}
 
public void start()
{
    setVisible(true);
}
 
public void destroy()
{
    setVisible(false);
    dispose();
}

Die erste Methode paint() zeichnet nun nach dem Anzeigen des Fensters das Bild mit Hilfe der Methode drawImage() der als Argument gelieferten Klasse Graphics. Die erste 0 definiert den horizontalen Startpunkt innerhalb des Fensters, die zweite 0 den vertikalen Startpunkt, m_dimImageSize.width die Bildbreite, m_dimImageSize.height die Bildgröße und der this-Zeiger deutet auf das Fenster selbst hin.

Die zweite Methode start() zeigt das Fenster explizit an (erst danach wird paint() aufgerufen!) und die dritte Methode destroy() baut das Fenster wieder ab.

Das war's schon =)

Das Testprogramm

In Aktion kann man unsere neue SplashScreen-Klasse nun mit folgendem Testprogramm sehen. Dabei liegt die SplashScreen-Klasse im selben Verzeichnis wie die Hauptfunktion, d.h. wir benötigen keinen expliziten Import der SplashScreen-Klassen:

// Testprogramm für SplashScreen-Klassen
 
import java.awt.*; // Für Klasse Dimension
 
public class SplashTest
{
    private SplashScreen    m_splashScreen;
    private final String    m_strImagePath = "intro.png";
    private final Dimension m_dimImageSize = new Dimension(400, 300);
 
    public static void main(String[] strArgs)
    {
        SplashTest splash = new SplashTest();
    }
 
    public SplashTest()
    {
        m_splashScreen = new SplashScreen(m_strImagePath, m_dimImageSize);
        m_splashScreen.start();
 
        try
        {
            Thread.sleep(5000);   // 5 Sekunden anzeigen lassen
        }
        catch(InterruptedException e)
        {
        }
 
        m_splashScreen.destroy();
 
        System.exit(0);
    }
}

Zur Verzögerung bis zum Abbau des Bildes benutze ich hier eine externe Verzögerungsroutine der Klasse Thread. Das Bild wird angezeigt und nach 5 Sekunden wieder abgebaut.

Tipps

Wie gesagt kann man auch auflösungsabhängig mehrere Bilder mit dem MediaTracker precachen und je nach verwendeter Auflösung das jeweils passende Bild anzeigen lassen.

Die Verzögerungsroutine kann man auch direkt in die Methode start() implementieren und danach das Fenster abbauen lassen. Dadurch entfällt die Methode destroy().

Anmerkung

Dieses Tutorial stammt aus der ehemaligen Sammlung des resourcecode.de und konnte dank der freundlichen Zustimmung des Autors in das thewall-Wiki übertragen werden.