W tym wpisie przedstawię jak w prosty sposób dodać własny preloader do naszej aplikacji, pozbywając się domyślnego – moim zdaniem brzydkiego :) – preloadera.
Na początek spójrzmy jak będzie wyglądał nasz preloader.
Aby osiągnąć taki cel musimy:
- Stworzyć klasę która dziedziczy po DownloadProgressBar lub implementuje IPreloaderDisplay
- Zaimplementować w niej odpowiednie metody
- Dodać nasz preloader do aplikacji
Najłatwiej będzie zacząć od końca. Aby dodać nasz preloader do aplikacji, należy go wpisać w jej tagu.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" preloader="my.loader.MyPreloader">
Jak widać mój preloader znajduje się w pakiecie my.loader i nazywa się MyPreloader. W moim przypadku dziedziczy on po klasie Sprite, tak żebym mógł po nim rysować i implementuje interfejs IPreloaderDisplay.
package my.loader {
public class MyPreloader extends Sprite implements IPreloaderDisplay {
}
}
Gdy tworzymy klasę dziedziczącą po Sprite i implementującą IPreloaderDisplay, to otrzymujemy pakiet wygenerowanych funkcji. Wiekszość z nich możemy usunąć i zostawić:
private var _backgroundColor : uint = 0x000000;
private var _stageHeight : Number = 1;
private var _stageWidth : Number = 1;
public function set backgroundAlpha(alpha:Number):void{}
public function get backgroundAlpha():Number { return 1; }
public function set backgroundColor(color:uint):void { _backgroundColor = color; }
public function get backgroundColor():uint { return _backgroundColor; }
public function set backgroundImage(image:Object):void {}
public function get backgroundImage():Object { return null; }
public function set backgroundSize(size:String):void {}
public function get backgroundSize():String { return "auto"; }
public function set stageHeight(height:Number):void { _stageHeight = height; }
public function get stageHeight():Number { return _stageHeight; }
public function set stageWidth(width:Number):void { _stageWidth = width; }
public function get stageWidth():Number { return _stageWidth; }
Dodatkowo musimy zmodyfikowac metody set preloader oraz initialize. W metodzie set preloader musimy ustawić eventy dla preloadera.
private var _preloader : Sprite;
public function set preloader(value:Sprite):void {
_preloader = value;
value.addEventListener(ProgressEvent.PROGRESS, progressHandler);
value.addEventListener(Event.COMPLETE, completeHandler);
value.addEventListener(FlexEvent.INIT_PROGRESS, initProgressHandler);
value.addEventListener(FlexEvent.INIT_COMPLETE, initCompleteHandler);
}
W metodzie initialize wypełniam tło białym prostokątem, wczytuje plik loga oraz inicjuje Timer, który będzie wywoływany co 50ms, aby update’ować stan preloadera.
public function initialize():void {
//Wypełnienie tła
graphics.beginFill( _backgroundColor, 1);
graphics.drawRect( 0, 0, stageWidth, stageHeight);
graphics.endFill();
loadingImage = new flash.display.Loader();
loadingImage.contentLoaderInfo.addEventListener( Event.COMPLETE, loader_completeHandler); //metoda wywołana jak obrazek sie wczyta
loadingImage.load(new URLRequest("../assets/logo.jpg")); // Scieżka do obrazka
}
Po wczytaniu obrazka umieszczam go na środku preloadera, zaś label z wartością procentową (początkowo równą 0) umieszczam zaraz pod nim.
private var _timer : Timer;
private function loader_completeHandler(event:Event):void
{
addChild(loadingImage);
loadingImage.x = stage.fullScreenWidth / 2 - loadingImage.width / 2;
loadingImage.y = stage.fullScreenHeight / 3 - loadingImage.height / 2;
loadingText = new TextField();
loadingText.width = 20;
loadingText.height = 20;
loadingText.autoSize = TextFieldAutoSize.CENTER;
loadingText.text = "0";
loadingText.textColor = 0x0000ff;
loadingText.x = stage.fullScreenWidth / 2 - loadingText.width / 2;
loadingText.y = loadingImage.y + loadingImage.height + 20;
addChild(loadingText);
_timer = new Timer(50);
_timer.addEventListener(TimerEvent.TIMER, timerHandler);
_timer.start();
}
Timer, który zainicjowaliśmy w metodzie initialize, ma za zadanie odświeżenie labela oraz obrysowanie go paskami widocznymi na przykładzie, do którego link podałem powyżej.
private var _fractionLoaded : Number = 0;
private var _IsInitComplete : Boolean = false;
private function timerHandler(event:Event):void {
if ( _IsInitComplete ) {
_timer.stop();
_timer.removeEventListener(TimerEvent.TIMER,timerHandler);
dispatchEvent(new Event(Event.COMPLETE));
}
else {
loadingText.text = Math.ceil(_fractionLoaded*100).toString();
loadingText.x = stage.fullScreenWidth / 2 - loadingText.width / 2;
// Tutaj następuje przerysowanie pasków otaczających label, kod prezentuje na końcu wpisu
draw();
}
}
Preloader dostarcza nam 4 zdarzenia, które wywołuje w odpowiednim czasie. Są to:
- progressHandler – wywoływany za każdym razem, gdy przeglądarka ściągnie kolejną część aplikacji
- completeHandler – wywoływany, gdy aplikacja skończy się ściągać
- initProgressHandler – wywoływana podczas postępu inicjalizacji
- initCompleteHandler – wywoływana, gdy inicjalizacja się zakończy
Naszym zadaniem jest implementacja metod progressHandler oraz initCompleteHandler. W progressHandler updatujemy pola odpowiedzialne za procent wczytania aplikacji:
private var _bytesLoaded : uint = 0;
private var _bytesExpected : uint = 1;
private function progressHandler(event:ProgressEvent):void {
_bytesLoaded = event.bytesLoaded;
_bytesExpected = event.bytesTotal;
_fractionLoaded = Number(_bytesLoaded) / Number(_bytesExpected);
}
W metodzie initCompleteHandler ustawiamy pole odpowiedzialne za zakończenie działania preloadera na true. Dzięki temu nasz Timer, może zakończyć działanie. Robi to poprzez wywołanie zdarzenia Event.COMPLETE, tak jak to widać w metodzie timerHandler.
private function initCompleteHandler(event:Event):void {
_IsInitComplete = true;
}
Teraz zaprezentuje metody, które używam to obrysowania labela paskami, które tworzą koło. Niestety nie można we Flexie rysować częściowych kół (coś a’la pizza bez kawałka). Mój sposób polega na tym, żeby rysować od trójkąty z jednym punktem wspólnym w środku, zaś kolejnymi parami punktów dookoła labela.
private function draw():void {
graphics.clear();
fillAct(graphics, stage.fullScreenWidth / 2, loadingText.y + loadingText.height/2, _fractionLoaded*360 , 25);
graphics.beginFill( 0xffffff , 1);
graphics.drawCircle(stage.fullScreenWidth / 2, loadingText.y + loadingText.height/2, 20);
graphics.endFill();
}
private function fillAct(g:Graphics, x:Number, y:Number, angle:Number, radius:Number):void {
var trCounts:Number = Math.ceil(angle / 5);
var radFrom:Number, radTo:Number;
g.beginFill(0x000000);
for (var i:int = 0; i < trCounts; i+=2) {
radFrom = -i*5*Math.PI / 180;
radTo = (-i*5 + 5)*Math.PI / 180;
g.moveTo(x,y);
g.lineTo(x + Math.sin(radFrom)*radius, y + Math.cos(radFrom)*radius);
g.lineTo(x + Math.sin(radTo)*radius, y + Math.cos(radTo)*radius);
g.lineTo(x,y);
}
g.endFill();
}
Ten kod po złączeniu daje efekt zaprezentowany w przykładzie :) Mam nadzieje, że wszystko jest zrozumiałe. W razie pytań, proszę komentować :)