Andreas Rozek  

Eine "Preloader"-Anzeige für eigene Flex-Anwendungen

Mit Adobe Flex [1] erstellte Anwendungen sind eigentlich nichts anderes als Flash [2] "Movies" mit zwei "Frames" - und für Flash Movies hat Adobe vorgesehen, daß mit dem Abspielen bereits begonnen werden kann, noch bevor die gesamte Datei geladen wurde. Da Flex-Anwendungen meist mehrere 100kB umfassen, empfiehlt sich der Einsatz eines "Preloader" mit einer Anzeige, die den Benutzer über den aktuellen Ladefortschritt informiert.

Dieser Artikel beschreibt anhand eines konkreten Beispieles, wie eine Anzeige für den Flex-Preloader erstellt und eingesetzt wird.

Die Klasse selbst sowie ein kleiner Demonstrator können für alle unterstützten Plattformen heruntergeladen und nach Belieben in eigenen (kommerziellen wie nicht-kommerziellen) Projekten eingesetzt werden - beide stehen unter der "MIT License" [3] mit der zusätzlichen Einschränkung, daß jegliche Änderungen an dem vom Autor herausgegebenen Original deutlich gekennzeichnet werden müssen - dergestalt, daß der Autor nicht mit diesen Änderungen in Verbindung gebracht werden kann!

Inhaltsübersicht

Um direkt zu einem bestimmten Abschnitt zu gelangen, klicken Sie einfach auf die entsprechende Überschrift:

Preloader in Flex-Anwendungen

Flex-Anwendungen sind eigentlich nichts anderes als Flash "Movies" mit zwei "Frames" [4]: das erste Bild (Frame) enthält den Preloader, das zweite die eigentliche Flex-Anwendung [5]. Sobald das erste Frame vollständig geladen ist, kann es angezeigt (d.h. der Preloader ausgeführt) werden. Sobald das zweite Frame geladen ist, kann mit dem Ausführen der Anwendung begonnen werden - die Zeit dazwischen sollte eine Preloader-Anzeige möglichst sinnvoll überbrücken.

Glücklicherweise packt Flex alle für den Preloader (samt Anzeige) benötigten Komponenten in das erste Frame - wenn man also dafür sorgt, daß die Preloader-Anzeige nicht zuviele zusätzliche Klassen erfordert [6], kann das erste Frame zügig geladen werden. Auf diese Weise bleibt die Zeit vom Aufruf der SWF-Datei bis zur ersten visuellen Rückmeldung durch den Preloader möglichst kurz.

Noch eine Anmerkung: in der Literatur [z.B. 4,5,6] wird das Wort "Preloader" häufig auch dann verwendet, wenn nur von der Anzeige des Ladefortschritts die Rede ist. Dabei ist der eigentliche Preloader fest in Flex integriert und kann vom Programmierer nicht verändert werden - nur die Anzeige läßt sich durch eine Eigenentwicklung ersetzen. Unglücklicherweise ist auch Adobe in seiner Nomenklatur nicht konsequent: in der MXML-Direktive für die Flex-Anwendung muß die Anzeigeklasse als "preloader"-Attribut angegeben werden, selbst wenn die verwendete Klasse (korrekterweise) das Interface "IPreloaderDisplay" implementiert.

Diese Präzisierung ist mehr als nur eine Spitzfindigkeit - sie ist wichtig für das Verständnis (und ggfs. die Erwartungshaltung des Programmierers): anders als der wirkliche Preloader kann ein "IPreloaderDisplay" den Ladevorgang nicht beeinflussen. Stattdessen wird es vom Preloader instanziert und anschließend mit allen erforderlichen Informationen (Größe und Aussehen der "stage" sowie Ladefortschritt) versorgt. Die hier vorgestellte Klasse ist also nichts mehr als ein "Sklave" des Preloader...

PreloaderDisplay - eine eigene Fortschrittsanzeige

Nachdem eine Recherche keine passende Preloader-Anzeige lieferte, blieb dem Autor nichts anderes übrig als selbst ein "IPreloaderDisplay" zu entwickeln. Gefragt war eine schlichte, aber aussagekräftige Anzeige mit einem ganz bestimmten Zeitverhalten:

  • für Projekte mit kurzen Ladezeiten (< 0.5 Sekunden) sollte gar keine Preloader-Anzeige erscheinen
  • nach 0.5 Sekunden sollte nur dann eine Anzeige erscheinen, falls die prognostizierte Ladezeit insgesamt über 1 Sekunde liegt;
  • nach einer Sekunde sollte der Benutzer allerdings auf jeden Fall eine visuelle Rückmeldung erhalten.

Außerdem sollte die Anzeige mindestens für zwei Sekunden auf dem Bildschirm verbleiben - ein kurzes Aufflackern war (und ist) nicht akzeptabel.

Das fertige Ergebnis sieht wie folgt aus:

Vorschau auf die Preloader-Anzeige des Autors
Abb. Vorschau auf die Preloader-Anzeige des Autors

Die Anzeige ist einfach, enthält aber alle für den Betrachter wichtigen Informationen - insbesondere auch eine Abschätzung der verbleibenden Wartezeit.

Funktionsweise im Überblick

Ausgehend von den bereits genannten Beispielen (vor allem [5]) war die Entwicklung nicht sonderlich schwierig - die komplette Logik konnte in eine einzige Klasse "PreloaderDisplay" ausgelagert werden, die sich leicht in andere Projekte integrieren läßt.

Wie in [5] vorgeschlagen, implementiert "PreloaderDisplay" das Interface "IPreloaderDisplay", integriert aber zugleich auch die tatsächliche Anzeige des Ladefortschrittes. Für die Textanzeige wurden (wie in [6] empfohlen) "TextField"s verwendet (die nicht zum Flex Framework gehören, sondern von Flash bereitgestellt werden), der eigentliche Ladebalken wird mit Grafik-Befehlen direkt auf die Anzeige gezeichnet.

Der Ladefortschritt wird von Flash mitgeteilt, das "PreloaderDisplay" führt außerdem Buch über die (gemittelte) Ladegeschwindigkeit und erechnet daraus eine Abschätzung für die noch ausstehende Ladezeit.

Wenn Sie ein Flash PlugIn (ab Version 9) installiert haben, können Sie sich die Optik des "PreloaderDisplay" vorführen lassen:

Bitte installieren Sie das Flash-PlugIn (ab Version 9), um den Preloader-Demonstrator benutzen zu können

Vermutlich sehen Sie bereits den Text 'Click for a demonstration of the "PreloaderDisplay"', da die verwendete SWF-Datei längst geladen ist. Nach Anklicken der Anzeige wird Ihnen aber eine Demonstration vorgeführt, die dem eigentlichen "PreloaderDisplay" recht nahe kommt.

Funktionsweise im Detail

Der Quelltext des "PreloaderDisplay" ist nicht sonderlich komplex und deshalb schnell erklärt: wie bereits erwähnt, implementiert die Klasse das "IPreloaderDisplay"-Interface - folglich müssen auch alle get/set-Methoden implementiert werden:

  public function set backgroundAlpha (newAlpha:Number):void {BackgroundAlpha = newAlpha;};
public function get backgroundAlpha ():Number {return BackgroundAlpha;};

public function set backgroundColor (newColor:uint):void {BackgroundColor = newColor;};
public function get backgroundColor ():uint {return BackgroundColor;};

public function set backgroundImage (newImage:Object):void {BackgroundImage = newImage;};
public function get backgroundImage ():Object {return BackgroundImage;};

public function set backgroundSize (newSize:String):void {BackgroundSize = newSize;};
public function get backgroundSize ():String {return BackgroundSize;};

public function set preloader (actualDisplay:Sprite):void {
Display = actualDisplay;

Display.addEventListener(ProgressEvent.PROGRESS, onDownloadProgress);
Display.addEventListener(Event.COMPLETE, onDownloadComplete);
Display.addEventListener(FlexEvent.INIT_PROGRESS, onInitializationProgress);
Display.addEventListener(FlexEvent.INIT_COMPLETE, onInitializationComplete);
};

public function set stageHeight (newHeight:Number):void {StageHeight = newHeight;};
public function get stageHeight ():Number {return StageHeight;};

public function set stageWidth (newWidth:Number):void {StageWidth = newWidth;};
public function get stageWidth ():Number {return StageWidth;};

Die meisten Eigenschaften werden im PreloaderDisplay gespeichert und bei der Anzeige berücksichtigt, lediglich "backgroundImage" und "backgroundSize" werden derzeit noch ignoriert. Sobald die "preloader"-Eigenschaft bekannt ist, werden auch die benötigten EventListener registriert - auf diese Weise wird das PreloaderDisplay über den Ladefortschritt informiert.

Da der Konstruktor empfehlungsgemäß leer ist, übernimmt die Methode "initialize()" die eigentliche Initialisierung:

  public function initialize ():void {
...

lastProgress = 0;
lastTime = getTimer();
lastSpeed = 0;

DisplayStartTime = 0; // not really necessary

DisplayTimer = new Timer(100);
DisplayTimer.addEventListener(TimerEvent.TIMER, onTimerTick);
DisplayTimer.start();
};

Zunächst werden die Anzeigefläche gelöscht und alle benötigten Anzeigekomponenten angelegt (aber noch nicht wirklich angezeigt, die Details sind hier nicht dargestellt), danach folgt die Initialisierung aller Variablen für die Prognoseberechnung. Die (etwas unglücklich benannte) Methode "getTimer()" stammt aus dem Paket "flash.utils" und liefert die seit dem Aufruf der Anwendung verstrichene Zeit (in Millisekunden). Da nicht vorhersehbar ist, wann die oben erwähnten Ereignisse des Preloader eintreffen, wird die Anzeige durch einen Timer periodisch aktualisiert.

Während des Ladevorganges wird der Fortschritt (hier in Prozent gemessen) regelmäßig aktualisiert, anschließend folgt eine Initialisierungsphase bis die Anwendung tatsächlich startbereit ist (im PreloaderDisplay gekennzeichnet durch "ReadyToRun == true"):

  private function onDownloadProgress (theEvent:ProgressEvent):void {
Progress = Math.round(theEvent.bytesLoaded/theEvent.bytesTotal*100);
};

private function onDownloadComplete (theEvent:Event):void {
Progress = 100;
};

private function onInitializationProgress (theEvent:FlexEvent):void {
/* nop */
};

private function onInitializationComplete (theEvent:FlexEvent):void {
ReadyToRun = true;
};

Die eigentliche Arbeit wird in der Methode "onTimerTick" erledigt, die etwa alle 100ms einmal aufgerufen wird. Zunächst wird für die Prognose ein (konservativer) Mittelwert der Ladegeschwindigkeit errechnet:

  if (Progress < 100) {                   // calculate current loading speed
var ProgressDelta:Number = Progress-lastProgress;
var TimeDelta:Number = getTimer()-lastTime;

if (TimeDelta > 0) { // just to be on the safe side
lastSpeed = lastSpeed*0.9 + ProgressDelta/TimeDelta*0.1;
};

lastProgress = Progress;
lastTime = lastTime+TimeDelta;
};

Anschließend wird entschieden, ob überhaupt eine Anzeige erfolgen soll:

  if (DisplayStartTime == 0) {                 // indicates a hidden display
if (
(lastTime < minHideTime) || // it's too early to show anything
((lastTime < maxHideTime) && ((100-Progress)/lastSpeed < 500))
) {// it's not useful to show something (works even when lastSpeed == 0)
if (ReadyToRun) {
DisplayTimer.stop();
dispatchEvent(new Event(Event.COMPLETE)); // VERY important!
};

return; // leave without displaying anything
};

DisplayStartTime = lastTime; // we start showing something now
};

Die Variable "DisplayStartTime" merkt sich den Zeitpunkt der ersten Anzeige und sorgt so später dafür, daß - wenn eine Anzeige erfolgt ist - diese mindestens 2 Sekunden lang auf dem Schirm verbleibt.

Die konkrete Anzeige des Ladefortschritts ist hier nicht dargestellt - interessant ist am Ende wieder die Erkennung der Startbereitschaft der eigentlichen Anwendung:

  if ((getTimer() >= DisplayStartTime+minDisplayTime) && ReadyToRun) {
DisplayTimer.stop();
dispatchEvent(new Event(Event.COMPLETE)); // VERY important!
};

Wichtig ist in diesem Zusammenhang der Versand des "Event.COMPLETE"-Ereignisses - erst hierdurch wird der Lade- (und Initialisierungs-)Vorgang beendet unddie Anwendung gestartet. Die Arbeit des "PreloaderDisplay" ist damit beendet...

Verwendung des PreloaderDisplay in eigenen Anwendungen

Die hier vorgestellte Preloader-Anzeige läßt sich problemlos in eigene Flex-Anwendungen integrieren. Wenn die Optik des "PreloaderDisplay" nicht verändert werden soll, ist es ausreichend

  1. die Quelltextdatei "PreloaderDisplay.as" in das "src"-Verzeichnis des eigenen "Flex Builder"-Projektes [7] zu kopieren und
  2. die MXML-Anweisung für die Flex-Anwendung um die Einträge
      usePreloader="true" preloader="PreloaderDisplay"
    zu erweitern.

Ansonsten sind vor allem in den Methoden "initialize()" und "handleTimerTick()" zusätzliche Anpassungen erforderlich. Der Quelltext ist kommentiert, so daß die erforderlichen Modifikationen leicht von der Hand gehen sollten.

Wichtig ist nach Möglichkeit aber stets der Verzicht auf Klassen des Flex-Framework, auch wenn dadurch die Programmierung etwas komplizierter wird. Denn je mehr Klassen geladen werden müssen, bevor der Preloader starten kann, desto länger muß der Benutzer auf die Ladeanzeige warten und desto fraglicher wird der Sinn des zusätzlichen Aufwandes.

Um das Austesten zu vereinfachen, können in den eben genannten Methoden drei als "uncomment for testing" markierte Zeilen auskommentiert werden - dadurch wird zum einen die Mindestzeit für die Anzeige des Preloader auf fünf Sekunden erhöht und zum anderen der Fortschritt an der verstrichenen Zeit (und nicht dem tatsächlichen Ladefortschritt) festgemacht.

Verfügbare Dateien

Folgende Dateien können von hier aus auf den eigenen Rechner heruntergeladen werden:

Bekannte Probleme

Dem Autor sind bislang keine Probleme bekannt.

Literaturhinweise

[1]
Adobe Flex 3
(siehe http://www.adobe.com/de/products/flex/)

Adobe Flex ist ein (kostenloses) "Framework" für die Erstellung von Anwendungen auf der Basis von Adobe Flash. Im Gegensatz zu Flash selbst (welches eher für Grafiker und Web-Designer gedacht ist), richtet sich Flex explizit an Entwickler und Programmierer.

[2]
Adobe Flash Player
(siehe http://get.adobe.com/de/flashplayer/)

Adobe Flash ist eine Plattform für Multimedia-Inhalte und Anwendungen, die innerhalb eines HTML-Browser angezeigt und ausgeführt werden. Die Laufzeitumgebung (der "Player") kann kostenlos installiert und benutzt werden, die Umgebungen für die Entwicklung von Flash-Inhalten sind z.T. kostenpflichtig.

[3]
Open Source Initiative OSI - The MIT License
(siehe http://www.opensource.org/licenses/mit-license.php)

Die (manchmal auch "X11-Lizenz" genannte) "MIT-Lizenz" ist eine äußerst einfach gehaltene Lizenz, die die freie Verwendung von Software sicherstellt und den Autor gleichzeitig von jeglicher Haftung freistellt.

[4]
Ted Patrick
Flex 2 Preloaders - SWF, PNG, GIF Examples
(siehe http://www.onflex.org/ted/2006/07/flex-2-preloaders-swf-png-gif-examples.php)

Dieser Artikel ist die Grundlage vieler anderer Preloader-Implementierungen, benötigt jedoch eine zusätzliche Loader-Klasse für das Einbetten von Grafiken und stellt keine Texte dar.

[5]
Andrew
Implementing A Flex 2 Custom Preloader
(siehe https://defiantmouse.com/yetanotherforum.net/Default.aspx?g=posts&t=82)

Dieser Artikel erweitert den in [4] beschriebenen Preloader um die Anzeige von Texten und kommt zugleich ohne eine zusätzliche Loader-Klasse aus. Stattdessen implementiert er das für eine Fortschrittsanzeige vorgesehene Interface und macht daraus so etwas wie eine "abstrakte" Klasse (ohne eigene Anzeige), die in anderen Projekten eingesetzt werden kann.

[6] Sasha Dzeletovic
Custom Flex 3 Lightweight Preloader with source code
(siehe http://www.pathf.com/blogs/2008/08/custom-flex-3-lightweight-preloader-with-source-code/)

Dieser Artikel erläutert ein paar Randbedingungen für den Entwurf eines (sinnvollen) Preloader und liefert ein hübsches Beispiel für die in [5] beschriebene "abstrakte" Preloader-Klasse.

[7]
Adobe Flex Builder
(siehe http://www.adobe.com/de/products/flex/)

Der "Flex Builder" ist eine auf Eclipse aufsetzende Entwicklungsumgebung für Flex- (und Flash-)Anwendungen von Adobe. Obwohl nicht kostenlos, ist der "Flex Builder" (vor allem für Adobe-Verhältnisse) erstaunlich preisgünstig.


http://www.Rozek.de/Flex/PreloaderDisplay/index_de.html Stand: 04.11.2009