flexサーバデータの戻り待ち中に表示される紡績機アニメーション


Spinner class

package util
{
	import flash.events.Event;
	import flash.geom.Point;
	
	import mx.controls.SWFLoader;
	import mx.core.FlexGlobals;
	import mx.core.UIComponent;
	import mx.events.ResizeEvent;
	import mx.managers.PopUpManager;
	
	import spark.components.Application;
	import spark.components.SkinnableContainer;
	

	public class Spinner extends SkinnableContainer
	{
		private var _spinnerSWF:SWFLoader;
		private var _target:UIComponent;
		/**
		 * if true indicate the target is in a popup window, otherwise not.
		 */ 
		private var isTargetPopUp:Boolean;
		/**
		 * if user send another request before the first rquest complete, and both of them are spinning the same target,
		 * for the second request we will not use a new spinner, but only increase the counter of the spinner, when request finished, we will check the counter, if counter is not
		 * 0, which means another request has not been finished yet, in this case the spinner is not hidden, we only decrease the counter, 
		 * only all requests having spinner on the same target are finished, the spinner will be hidden.  
		 */ 
		public var counter:int=0;
		
		public function Spinner()
		{
			this.setStyle("backgroundColor","0xfafafc");
			this.alpha=0.5;		
			_spinnerSWF=new SpinnerSWF();
			_spinnerSWF.width=0;
			_spinnerSWF.height=0;	
			width=0;
			height=0;
			x=-10;
			y=-10;
			addElement(_spinnerSWF);
		}
		
		private function measurePosition():void{
			if(!_target){
				throw new Error("Target cannot be null");
			}
			if(_target.parent){
				var globalPosition:Point=_target.parent.localToGlobal(new Point(_target.x,_target.y));
				move(globalPosition.x,globalPosition.y);
			}
		}
		
		/**
		 * Make sure this method is invoked after measureSize()
		 */ 
		private function measureSpinnerSWFSize():void{
			var min:int = _target.width < _target.height? _target.width: _target.height;
			if( min> 0 && min<70 ){
				_spinnerSWF.width = min;
				_spinnerSWF.height = min;
			}else{
				_spinnerSWF.width = 70;
				_spinnerSWF.height = 70;
			}
		}
		
		private function measureSpinnerSWFPosition():void{
			_spinnerSWF.x = (_target.width - _spinnerSWF.width)/2;
			_spinnerSWF.y = (_target.height - _spinnerSWF.height)/2;
		}
		
		private function measureSize():void{
			width=_target.width;
			height=_target.height;
		}
		
		public function showSpinner(trgt:UIComponent):void{
			if(!trgt){
				throw new Error("Target cannot be null");
			}		
			_target=trgt;
					
			measureSize();
			measurePosition();
			measureSpinnerSWFSize();
			measureSpinnerSWFPosition();
			
			//why we add movement listeners to target.parentDocument if target is in a popup window, because "xChanged" and "yChanged" event is not
			//triggered on the target, only triggered on target.parentDocument when we move the popup window.
			//why we don't care whether target itself is a popup window, because if the target itself is a popup window, user
			//actually is not able to move the target, so we don't care
			if(_target.parentDocument!=null && (_target.parentDocument as UIComponent).isPopUp){
				isTargetPopUp=true;
			}else{
				isTargetPopUp=false;
			}
			if(isTargetPopUp){
				_target.parentDocument.addEventListener(ResizeEvent.RESIZE,targetResizeHandler);
				_target.parentDocument.addEventListener("xChanged",targetXChangedHandler);
				_target.parentDocument.addEventListener("yChanged",targetYChangedHandler);
				PopUpManager.addPopUp(this,_target,false);
			}else{
				_target.addEventListener(ResizeEvent.RESIZE,targetResizeHandler);
				_target.addEventListener("xChanged",targetXChangedHandler);
				_target.addEventListener("yChanged",targetYChangedHandler);
				(FlexGlobals.topLevelApplication as SkinnableContainer).addElement(this);
			}
		}
		
		private function targetXChangedHandler(e:Event):void{
			measurePosition();
			measureSpinnerSWFPosition();
		}
		
		private function targetYChangedHandler(e:Event):void{
			measurePosition();
			measureSpinnerSWFPosition();
		}
		
		private function targetResizeHandler(event:ResizeEvent):void{
			measureSize();
			measurePosition();
			measureSpinnerSWFSize();
			measureSpinnerSWFPosition();
		}
		
		public function hideSpinner():void{
			if(_spinnerSWF){
				_spinnerSWF.width=0;
				_spinnerSWF.height=0;
			}
			width=0;
			height=0;
			x=-10;
			y=-10;
			removeListeners();
			removeSpinner();
			
		}
		
		private function removeSpinner():void{
			if(isTargetPopUp){
				PopUpManager.removePopUp(this);
			}else{
				(FlexGlobals.topLevelApplication as SkinnableContainer).removeElement(this);
			}
		}
		
		public function destroySpinner():void{
			removeListeners();
			removeElement(_spinnerSWF);
			_spinnerSWF=null;
			removeSpinner();
		}
		
		private function removeListeners():void{
			if(_target.hasEventListener(ResizeEvent.RESIZE))
				_target.removeEventListener(ResizeEvent.RESIZE,targetResizeHandler);
			if(_target.hasEventListener("xChanged"))
				_target.removeEventListener("xChanged",targetResizeHandler);
			if(_target.hasEventListener("yChanged"))
				_target.removeEventListener("yChanged",targetYChangedHandler);
		}
	}
}

SpinnerFactory class
package util
{
	import flash.utils.Dictionary;
	
	import mx.collections.ArrayCollection;
	import mx.core.UIComponent;

	public class SpinnerFactory
	{
		private static var _instance:SpinnerFactory;
		private var _targetsHasSpinner:Dictionary;
		private var _spinnerPool:ArrayCollection;
		
		public function SpinnerFactory()
		{
			if(_instance){
				throw new Error("SpinnerFactory singleton instance has already constructed");
			}
			_targetsHasSpinner=new Dictionary();
			initSpinnerPool();
		}
		
		private function initSpinnerPool():void{
			_spinnerPool=new ArrayCollection();
			for(var i:int=0;i<5;i++){
				_spinnerPool.addItem(new Spinner());
			}
		}
		
		private function ensureCapacity():void{
			for(var i:int=0;i<5;i++){
				_spinnerPool.addItem(new Spinner());
			}
		}
		
		public static function getInstance():SpinnerFactory{
			if(!_instance){
				_instance=new SpinnerFactory();
			}
			return _instance;
		}
		
		public function spinOneTarget(target:UIComponent):void{
			if(target==null){
				return;
			}
			var spinner:Spinner=_targetsHasSpinner[target];
			if(spinner!=null){
				spinner.counter++;
				return;
			}
			if(_spinnerPool.length>0){
				spinner=_spinnerPool.getItemAt(0) as Spinner;
			}else{
				ensureCapacity();
				spinner=_spinnerPool.getItemAt(0) as Spinner;
			}
			if(spinner){
				_spinnerPool.removeItemAt(0);
				_targetsHasSpinner[target]=spinner;
				spinner.showSpinner(target);
			}
		}
		
		/**
		 * spin multiple UIComponents
		 */ 
		public function spinTargets(targets:Array):void{
			if(targets==null||targets.length==0)
				return;
			for each(var item:Object in targets){
				if(item!=null)
					spinOneTarget(item as UIComponent);
			}
			
		}
		
		public function unspinOneTarget(target:UIComponent):void{
			if(target==null){
				return;
			}
			var spinner:Spinner = _targetsHasSpinner[target];
			if(spinner!=null){
				if(spinner.counter!=0){
					spinner.counter--;
				}else{
					spinner.hideSpinner();
					_targetsHasSpinner[target]=null;
					_spinnerPool.addItem(spinner);
				}
			}
		}
		
		/**
		 * unspin multiple UIComponents
		 */ 
		public function unspinTargets(targets:Array):void{
			if(targets==null||targets.length==0)
				return;
			for each(var item:Object in targets){
				if(item!=null)
					unspinOneTarget(item as UIComponent);
			}
		}
	}
}

swf
<s:SWFLoader xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 xmlns:s="library://ns.adobe.com/flex/spark" 
		 xmlns:mx="library://ns.adobe.com/flex/mx"
		 width="100" height="100"  source="@Embed(source='assets/loading.swf')">
	<fx:Declarations>
		<!-- Place non-visual elements (e.g., services, value objects) here -->
	</fx:Declarations>
</s:SWFLoader>