Interfaces gráficas flutuantes utilizando o padrão Observer e Tweener
O modismo é criar interfaces gráficas para sites em tela cheia - fullscreen. Estou trabalhando em um site que exige essa funcionalidade de modo que os elementos na interface gráfica distribuam-se corretamente pela tela do usuário de modo a adequar-se a resolução do monitor ou o tamanho da janela do navegador que o flash está sendo executado.
É bem tranquilo tratar essas informações para um, dois, três elementos… Mas para n elementos, como fazer? Neste site podem ser criadas inúmeras janelas, além do habitual menu e imagem de fundo. Ainda é necessário uma série de protótipos até a entrega final do produto e modificar o código em diversos pontos, diversas vezes sem dúvida não é uma boa prática.
A solução que encontrei foi utilizar um padrão de projeto chamado de Observer. Com ele um objeto Subject poderá enviar mensagens para muitos objetos Observers criando uma relação fracamente acoplada para os Observers que poderão ser reutilizados de maneira simples neste ou em outros projetos.
A classe principal enviará informações sobre o tamanho da tela utilizada pelo usuário e notificará os Observers toda vez que o tamanho da tela for alterado pelo usuário.
A classe abstrata ASubject deve conter alguns métodos específicos para notificar os possíveis subjects, bem como anexar subjects e remove-los. Ela será a classe pai do nosso Subject Main.
package {
import com.adobe.utils.ArrayUtil;
import flash.display.MovieClip;
import flash.errors.IllegalOperationError;
public class ASubject extends MovieClip {
// Lista com IObservers.
protected var _observers:Array = new Array();
// Guarda estado do Subject.
protected var _state:Object;
public function get state():Object {
return _state;
}
public function ASubject(c:ASubject):void {
if (c != this) {
throw new IllegalOperationError("This class must be overriden");
}
}
/**
* Adiciona um objeto IObserver
* à lista _observers.
* @param o:IObserver
*/
public function attach(o:IObserver):void {
_observers.push(o);
}
/**
* Remove um objeto IObserver
* à lista _observers.
* @param o:IObserver
*/
public function detach(o:IObserver):void {
ArrayUtil.removeValueFromArray(_observers, o);
}
/**
* Notifica objeto IObserver presentes
* na lista _observers.
* @param s:ASubject
*/
public function notify(s:ASubject):void {
for each(var o in _observers) {
o.update(this);
}
}
}
}
É necessário criar uma interface para Observer para que o Subject se comunique com ele.
package {
public interface IObserver {
/**
* Realiza alterações no objeto
* que implementa esta interface
* e está presente na lista _observers.
*
* @see ASubject
*
* @param a:ASubject
*/
function update(a:ASubject):void;
}
}
Criamos um IObserver Menu.
package {
import caurina.transitions.*;
import flash.display.MovieClip;
import flash.display.Shape;
public class Menu extends MovieClip implements IObserver {
private var _background:Shape;
public function Menu():void {
draw();
}
public function update(a:ASubject):void {
Tweener.addTween(_background, { x: a.state.width - _background.width - 20,
y: 20,
time: 1 } );
}
private function draw():void {
_background = new Shape();
_background.graphics.beginFill(0x000000);
_background.graphics.drawRect(0, 0, 180, 250);
_background.graphics.endFill();
addChild(_background);
}
}
}
Agora anexamos o IObserver Menu ao ASubject Main da nossa aplicação. Note que ASubject é filho de MovieClip, necessário - poderia ser Sprite também - para que nossa classe se torne o Document Class do filme flash.
package {
import flash.events.Event;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
public class Main extends ASubject {
private var menu:Menu;
private var footer:Footer;
public function Main():void {
super(this);
menu = new Menu();
addChild(menu);
// Adiciona objeto.
attach(menu);
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.addEventListener(Event.ACTIVATE, activateHandler);
stage.addEventListener(Event.RESIZE, resizeHandler);
}
private function activateHandler(event:Event):void {
_state = { width: stage.stageWidth, height: stage.stageHeight };
// Notifica objetos.
notify(this);
}
private function resizeHandler(event:Event):void {
_state = { width: stage.stageWidth, height: stage.stageHeight };
// Notifica objetos.
notify(this);
}
}
}
Agora supondo que eu queira adicionar um novo IObject ao meu projeto basta criar uma nova classe que herde a interface de IObject, como por exemplo a classe Footer abaixo.
package {
import caurina.transitions.*;
import flash.display.MovieClip;
import flash.display.Shape;
public class Footer extends MovieClip implements IObserver {
private var _background:Shape;
public function Footer():void {
draw();
}
public function update(a:ASubject):void {
Tweener.addTween(_background, { width: a.state.width,
y: a.state.height -20,
time: 1} );
}
private function draw():void {
_background = new Shape();
_background.graphics.beginFill(0xFF0000);
_background.graphics.drawRect(0, 0, 530, 20);
_background.graphics.endFill();
addChild(_background);
}
}
}
Agora para integrar o novo objeto ao meu Subject Main basta alterar seu método construtor para:
public function Main():void {
super(this);
menu = new Menu();
addChild(menu);
// Adiciona objeto.
attach(menu);
footer = new Footer();
addChild(footer);
// Adiciona objeto.
attach(footer);
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
stage.addEventListener(Event.ACTIVATE, activateHandler);
stage.addEventListener(Event.RESIZE, resizeHandler);
}
E sobre o Tweener? Bem, é muito simples de utiliza-lo, existem inúmeros tutorias e uma boa documentação disponível na Internet. Usei ele apenas para deixa-lo mais #cool. Fica aqui como uma dica.
Baixe os arquivos utilizados como exemplo e o resultado final.