Strona wykorzystuje ciasteczka by usprawnić komfort z jej korzystania. Korzystając ze strony akceptujesz naszą Politykę Ciasteczek. X

Pasek Wyszukiwania we Flash'u [podstawowy]

Szczęśliwego Nowego Roku 2012!

Często w Internecie widuje pytanie o to jak stworzyć pasek wyszukiwania w filmiku (podobnie jak ma to większość standardowych odtwarzaczy wideo, np. na YouTube) jednak ostatecznie niewiele z tych osób go implementuje - dlaczego tak jest? Okazuje się, że problem leży w sposobie obsługi MovieClip'ów przez Flash'a, dlatego za nim przejdę dalej, poświęcę parę słów właśnie nim.
Może trochę trudno to sobie wyobrazić, ale każdy MovieClip w animacje to tak naprawdę osobny filmik który ma swoją linię czasu (i ustawienia) i kontrolowany jest całkowicie nie zależnie od pozostałych, co najlepiej zobrazuje poniższy przykład (wybierz dowolny moment, naciśnij prawy klawisz myszki i odhacz "odtwarzaj"):

Jak można byłoby się tego spodziewać, cała animacja zostanie natychmiast zatrzymana... poza piłką która jest MovieClip'em. Nie ma co się zagłębiać dlaczego tak jest - po prostu jest i nigdy się nie zmieni. Pozostaje nam jedynie szukać alternatywnych rozwiązań, a ponieważ poradzenie sobie z zatrzymywaniem MovieClip'ów bezpośrednio pomoże nam przy tworzeniu paska wyszukiwania dobrze jest przyjrzeć dwóm możliwym obejściom tego problemu:
1. Pozbycie się wszystkich MovieClip'ów w animacji.
2. Ręczne zatrzymanie każdego z MóvieClip'ów za pomocą ActionScript.
Pierwsze rozwiązanie jest idealna, o ile oczywiście dopiero zaczynasz tworzyć swoją animacje, bo wtedy łatwiej jest ją odpowiednio przygotować niż później konwertować wszystkie MovieClipy na obiekty Graphics. A mając wszystko w graphics można zwyczajnie wywołać metodę gotoAndPlay by przenieść się w dowolne miejsce w animacji, co jest dokładnie tym czego potrzebujemy w pasku wyszukiwania. Drugie rozwiązanie ma sens tylko przy niewielkiej ilości MovieClip'ów, ponieważ wyszukanie i zatrzymanie dużej ich liczby może być zbyt obciążające dla procesora, szczególnie, że w przypadku paska wyszukiwania oznaczało by to przeszukanie CAŁEGO filmiku. Ale cóż, czasem po prostu nie ma innego wyjścia.
Kod odpowiedzialny za zatrzymanie wszystkich MovieClip'ów jest właściwie całkiem prosty:
stopMovies(root as MovieClip);
function stopMovies(mc:MovieClip):void {
	for(var i:int = 0; i<mc.numChildren; i++) {
		var d:DisplayObject = mc.getChildAt(i);
		if(d is MovieClip) {
			(d as MovieClip).stop();
			stopMovies(d as MovieClip);
		}
	}
}
Teraz mogłoby się wydawać, że wystarczy jeszcze dopisać gotoAndPlay i będziemy ustawieni. Niestety, nie ma tak łatwo, ponieważ MovieClip'y mają swoją własną linię czasu, w ogóle nie będą reagować na zmianę położenia głównej linii czasu (w przeciwieństwie do obiektów grafik). Z kolei zamiana wszystkich stop'ów na gotoAndPlay mija się z celem bo chęć przejścia na 10 klatkę głównej animacji nie oznacza, że wszystkie MovieClip'y również mają zostać ustawione na 10 klatce - w końcu jeśli MovieClip we Flash'u pojawi się na 6 klatce, na 10 klatce filmiku będzie dopiero w 4 klatce własnej linii czasu. Najlepiej będzie to pokazać na przykładzie gdzie główna linia czasu ma 30 klatek, a MovieClip pojawiający się na 10 klatce ma ich 20 (wszystkie ponumerowane):
Jak widać przy przewijaniu MovieClip'y zdają się żyć własnym życiem.
Dobra, ale dlaczego by nie odjąć od docelowej pozycji klatki w której pojawia się MovieClip? Jeśli chcemy przejść do 15 klatki animacji a MovieClip pojawia się na 10 to odejmujemy te 10 od 15 i wywołujemy gotoAndPlay(5) - główna linia czasu byłaby na 15 klace a MovieClip na 5-tej. Zgadza się, takie rozwiązanie byłoby idealne, ale haczyk w tym, że nie mamy skąd wziąć początkowej pozycji MovieClip'a. Tutaj właśnie pojawia się owe wyzwanie dla procesora o którym wspomniałem wcześniej, ponieważ kolejnym krokiem byłoby wyszukanie i spisanie tych wszystkich początkowych pozycji MovieClip'ów; tylko problem w tym, że przy standardowej animacji trwającej minutę i odtwarzanej przy prędkości 20 klatek na sekundę, do sprawdzenia będzie 1200 klatek! A jeśli do tego jeszcze dodamy np. 10 MovieClip'ów na klatkę... Dość powiedzieć, że jest to mało praktyczne rozwiązanie, dlatego w dalszej części artykułu zakładam, że animacje są w całości przygotowane na obiektach grafik.
Tyle teorii, czas stworzyć pasek wyszukiwania - zakładając absolutne minimum, będziemy potrzebować dwa MovieClipy: pasek reprezentujący długość animacji oraz wskaźnik aktualnej pozycji. Dla ułatwienia oba połóż na głównej linii czasu osobnego MovieClip'a (cały pasek będzie w jednym miejscu co ułatwi np. jego kopiowanie między projektami) i nazwij je mcBar oraz mcMarker (nazwy instancji). Pozostaje jeszcze tylko wstawić poniższy kod na główną linię czasu MovieClip'a przechowującego nasz pasek wyszukiwania:
import flash.events.MouseEvent;
import flash.events.Event;
import flash.display.MovieClip;

var bSeek:Boolean = false;
var nLength:Number = mcBar.width-mcMarker.width;
mcMarker.mouseEnabled = false;
mcMarker.mouseChildren = false;

if( !mcBar.hasEventListener(MouseEvent.MOUSE_DOWN) ) {
	//by nie dodawac zbednie event'ow gdy animacja zrobi petle
	mcBar.addEventListener(MouseEvent.MOUSE_DOWN,onSeekStart);
	mcBar.stage.addEventListener(MouseEvent.MOUSE_MOVE,onSeekDrag);
	mcBar.stage.addEventListener(MouseEvent.MOUSE_UP,onSeekStop);
	mcBar.stage.addEventListener(Event.ENTER_FRAME,onRender);
}

function onRender(e:Event):void {
	if(bSeek) return;
	mcMarker.x = ((root as MovieClip).currentFrame/(root as MovieClip).totalFrames)*nLength;
}
function onSeekStart(e:MouseEvent):void {
	bSeek = true;
	onSeekDrag(e);
}
function onSeekDrag(e:MouseEvent):void {
	if(!bSeek) return;
	
	mcMarker.x = mcBar.mouseX;
	if(mcMarker.x > nLength) {
		mcMarker.x = nLength;
	} else if(mcMarker.x<0) {
		mcMarker.x = 0;
	}
	
	var frame:int = int((mcMarker.x/nLength)*(root as MovieClip).totalFrames);
	
	(root as MovieClip).gotoAndStop(frame);
	mcMarker.x = (frame/(root as MovieClip).totalFrames)*nLength;
}
function onSeekStop(e:MouseEvent):void {
	bSeek = false;
	(root as MovieClip).play();
}
Nie jest to szczególnie skomplikowane - kliknięcie myszka na pasek przenosi w to miejsce znacznik i tłumaczy położenie X na pozycje w animacji.
Całość można pobrać stąd: seekbar.zip
A dla tych wszystkich którzy chcą spróbować rozwiązania z przeszukiwaniem MovieClip'ów przygotowałem specjalną klasę oraz przykład jak ją wykorzystać: seekbarEX.zip. W tym wypadku, żeby nie wpłynąć na filmik, MovieClip w którym umieściliśmy pasek i znacznik będzie eksportowany do ActionScript'u klasą ForusSeekBar.