Adobe Flex가 아닌 Flash CS3, CS4, ActionScript 3.0로 개발해본 사람이면 Stage를 자주 접하게 된다. 하나의 ActionScript 3.0 애플리케이션은 Stage 하나를 가지고 있다. 이것은 모든 DisplayObject 계열의 객체가 addChild()를 통해 시각화 과정이 완료후에 그 객체의 stage속성을 통해 접근이 가능하다. 모든 DisplayObject 계열의 객체에서 stage속성에 접근할 수 있다는 것은 stage가 매우 중요하다는 것을 암시하고 있다. 그러므로 잘 알고 활용해야 삽질을 방지할 수 있지 않을까?



1. Stage.invalidate()와 Event.RENDER 이벤트의 이해 


Stage 클래스에는 invalidate() 메소드가 있다. 이것을 호출하면 다음 렌더링 시점에 Event.RENDER 이벤트를 발생시킨다. 모든 DisplayObject 객체는 stage에 접근이 가능하므로 Event.RENDER 이벤트를 청취할 수 있다. 이는 애플리케이션 퍼포먼스를 향상시키고자 하는 개발자들이 반드시 알고 있어야할 사항이라고 생각한다. Stage의 invalidate()와 Event.RENDER 이벤트를 이용해 쓸데없는 렌더링을 예방함으로써 퍼포먼스를 향상시킬 수 있기 때문이다. 그 이유를 명확하게 알기 위해 다음 예제를 보자.


필자는 일반 ActionScript 3.0 프로젝트를 만들고 Sprite기반에서 애플리케이션을 만들고자 한다. 이때 두개의 클래스를 만드는데 하나는 Stage.invalidate()를 사용한 클래스이고 하나는 사용하지 않는것이다. 이 두개를 비교하면 Stage.invalidate()의 유용성을 확연히 알 수 있다.


InvalidateTest.as

package {
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.display.StageAlign;

	/**
	 * Stage.invalidate() 테스트 메인 
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class InvalidateTest extends Sprite
	{
		public function InvalidateTest()
		{
			super();
			
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			
			var button1:NotUseInvalidateButton = new NotUseInvalidateButton();
			button1.setWidth( 100 );
			button1.setHeight( 100 );
			button1.backColor = 0xff0000;
			button1.lineColor = 0x00ff00;
			button1.x = 20;
			button1.y = 20;
			addChild( button1 );
			
			var button2:UseInvalidateButton = new UseInvalidateButton();
			button2.setWidth( 100 );
			button2.setHeight( 100 );
			button2.backColor = 0xff00ff;
			button2.lineColor = 0x0000ff;
			button2.x = 130;
			button2.y = 20;
			addChild( button2 );			
		}

	}
}

위처럼 두개의 버튼 클래스를 만들었다. 2개 모두 폭, 높이, 배경색, 선색을 지정하도록 되어 있다. 결과는 비슷하다. 하지만 내부적으로 NotUseInvalidateButton은 내부에 Stage.invalidate()를 사용하지 않았고 UseInvalidateButton은 사용했다. 소스를 보자.


NotUseInvalidateButton.as

package
{
	import flash.display.Sprite;

	/**
	 * Stage.invalidate()를 사용하지 않는 Button
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class NotUseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		public function NotUseInvalidateButton()
		{
			super();
		}
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			drawNow();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			drawNow();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			drawNow();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			drawNow();
		}
		
		public function drawNow():void
		{
			trace( "[NotUseInvalidateButton] drawNow" );			
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}
		
	}
}


UseInvalidateButton.as

package
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * Stage.invalidate()를 사용하는 Button
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class UseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		private var isDrawNow:Boolean = false;
		private var isInvalidated:Boolean = false;
				
		public function UseInvalidateButton()
		{
			super();
			
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}		
		
		private function onAddedToStage( event:Event ):void
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if( isInvalidated )
			{
				isInvalidated = false;
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.invalidate();
			}
		}
		
		private function onRender(event:Event):void
		{
			if( this.stage )
			{
				this.stage.removeEventListener( Event.RENDER, onRender, false );
			}
			if( isDrawNow )
			{
				isDrawNow = false;
				drawNow();
			}
		}
		
		private function invalidate():void
		{
			if( this.stage )
			{
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.invalidate();
			}
			else
			{
				isInvalidated = true;	
			}
		}
		
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function drawNow():void
		{
			trace( "[UseInvalidateButton] drawNow" );
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}		
		
	}
}

디버깅 모드로 프로그램을 실행해보면 콘솔창에 다음과 같이 출력된다.


[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[NotUseInvalidateButton] drawNow

[UseInvalidateButton] drawNow 


NotUseInvalidateButton은 drawNow()메소드가 4번 호출되고 UseInvalidateButton은 1번만 호출된다. 이것만 보더라도 쓸데없는 렌더링을 줄여준 UseInvalidateButton 클래스가 더 잘 설계되었다는 것을 확인할 수 있다. 


NotUseInvalidateButton를 잘 살펴보면 setWidth(), setHeight(), set backColor, set lineColor 를 호출할 때마다 drawNow() 메소드를 호출한다. 개발자라면 이 4가지 속성이 모두 적용된 다음에 drawNow()를 호출하여 한번만 그려주고 싶어질 것이다. 이때 유용하게 사용할 수 있는 것이 Stage.invalidate() 이다.


UseInvalidateButton을 보자 NotUseInvalidateButton보다 약간 복잡해보이지만 잘 따라가보면 어렵지 않게 분석이 가능할 것이다. 결국 4개의 속성이 설정되면 invalidate() 메소드가 호출되고 Event.RENDER 이벤트를 받는 onRender()에서 drawNow()가 호출되도록 한다. 이렇게 되면 4개든 여러개든 할 것 없이 렌더링에 영향을 주는 다중 속성을 설정할 때마다 drawNow()를 호출하여 그림을 그려주는 부담을 줄일 수 있다. 


그림을 그리는 행위는 일반 속성을 설정하는 것보다 더 비싼 비용을 가진다. 그러므로 그림을 그리는 것은 되도록 한번에 처리할 수 있도록 하는 것이 애플리케이션 퍼포먼스 향상의 중요한 요소가 될 수 있는 것이다. 



2. Stage.invalidate()의 버그와 해결방법 


하지만 이렇게 좋은 Stage.invalidate() 가 있음에도 불구하고 실제로 이 메소드를 호출해도Event.RENDER 이벤트가 발생하지 않아 당황스러운 경우가 필자에게 있었다. 그래서 어떤 것은 drawNow()가 호출되고 또 어떤 것은 호출안되는 상황이 발생한 것이다. 너무도 어이가 없지만 Flash Player 10으로 넘어오면서도 이 버그는 아직까지도 Fix가 되지 않은 모양이다. 



위 링크는 모두 Adobe Bugs 관리 시스템에 등록된 것이다.(여러분도 가입해서 투표하길 바란다. 버그 시스템을 잘 이용하면 해결하지 못하는 문제도 쉽게 해결할 수 있을지 모른다. ^^)


Event.RENDER가 발생한 중간에 stage.invalidate()가 호출되면 이게 무시가 되나보다. 그래서 동작상으로는 문제없지만 개발자에게는 버그처럼 느껴질 수 있을지 모르겠다. 어쨌든 필자도 이 문제로 고민을 하다가 한가지 해결방법을 찾았는데 그것은 Event.ENTER_FRAME 이벤트를 이용하는 방법이다. 


Event.ENTER_FRAME을 이용해서 stage.invalidate()의 버그를 말끔히 해결할 수 있다. 다음 코드는NotUseInvalidateButton을 Event.ENTER_FRAME을 이용하는 것으로 바꿔본 것이다.


NotUseInvalidateButton.as



package
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * Stage.invalidate()를 사용하는 Button.
	 * Event.ENTER_FRAME 로 Stage.invalidate() 버그 우회 
	 * @author Yongho, Ji (jidolstar@gmail.com)
	 * @since 2009.6.1
	 */ 
	public class UseInvalidateButton extends Sprite
	{
		private var _height:Number = 50;
		private var _width:Number = 50;
		private var _backColor:Number = 0xffffff;
		private var _lineColor:Number = 0x000000;
		
		private var isDrawNow:Boolean = false;
		private var isInvalidated:Boolean = false;
				
		public function UseInvalidateButton()
		{
			super();
			
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}		
		
		private function onAddedToStage( event:Event ):void
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			
			if( isInvalidated )
			{
				isInvalidated = false;
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.addEventListener(Event.ENTER_FRAME, onRender, false, 0, true );
				this.stage.invalidate();
			}
		}
		
		private function onRender(event:Event):void
		{
			if( this.stage )
			{
				this.stage.removeEventListener( Event.RENDER, onRender, false );
				this.stage.removeEventListener( Event.ENTER_FRAME, onRender, false );
			}
			if( isDrawNow )
			{
				isDrawNow = false;
				drawNow();
			}
		}
		
		private function invalidate():void
		{
			if( this.stage )
			{
				this.stage.addEventListener(Event.RENDER, onRender, false, 0, true );
				this.stage.addEventListener(Event.ENTER_FRAME, onRender, false, 0, true );
				this.stage.invalidate();
			}
			else
			{
				isInvalidated = true;	
			}
		}
		
		
		public function get backColor():Number
		{
			return _backColor;
		}
		
		public function set backColor( value:Number ):void
		{
			_backColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function get lineColor():Number
		{
			return _lineColor;
		}
		
		public function set lineColor( value:Number ):void
		{
			_lineColor = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getWidth():Number
		{
			return _width;
		}
		
		public function setWidth( value:Number ):void
		{
			_width = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function getHeight():Number
		{
			return _height;
		}
		
		public function setHeight( value:Number ):void
		{
			_height = value;
			isDrawNow = true;
			invalidate();
		}
		
		public function drawNow():void
		{
			trace( "[UseInvalidateButton] drawNow" );
			graphics.clear();
			graphics.beginFill( backColor, 1 );
			graphics.lineStyle( 1, lineColor, 1 );
			graphics.drawRect( 0, 0, getWidth(), getHeight() );
			graphics.endFill();
		}		
		
	}
}


위에서 stage.addEventListener( Event.ENTER_FRAME )과 stage.removeEventListener( Event.ENTER_FRAME ) 이 추가된 것을 확인하자. 무작정 이벤트를 청취하고 있으면 안되므로 적절하게 삭제도 하고 있다. 


이렇게 처리함으로써 stage.invalidate()가 제대로 작동안하더라도 drawNow()메소드가 호출이 안되는 경우는 없게 된다.



3. fl.core.UIComponent 수정하기 


Flash의 UIComponent 를 보면 이것 stage.invalidate() 때문에 렌더링이 안되는 경우가 종종 발생한다고 한다. 필자는 Flash는 해본적이 없기 때문에 확실하게 모르지만 이것도 Event.ENTER_FRAME 을 이용해 해결할  수 있다. 그 방법은 다음 링크를 참고한다.


How to Fix the Flash CS3 Components


UIComponent의 callLater()가 어떻게 동작하는 것인지 이것을 보고 감을 잡을 수 있을 것이다. 결국  Event.RENDER, Event.ENTER_FRAME 으로 한다!


4. mx.core.UIComponent의 무효화 메소드들


Flex의 UIComponent에는 invalidateDisplayList(), invalidateProperties(), invalidateSize()와 같은 invalidate() 메소드가 존재한다. 이는 앞선 메소드들의 호출에 대해 각각 updateDisplayList(), commitProperties(), measure() 메소드가 다음 렌더링 시점에 호출되도록 하고 있다. 이는 invalidate/validate 패턴이다. 


재미있게도 Flex에서는 Stage.invalidate()에서 처럼 동작하지 않는 버그는 존재하지 않는다. 어떻게 된 것일까?


사실 Flex 내부를 살펴보면 Stage.invalidate() 와 Event.ENTER_FRAME을 이용해 invalidate 계열의 메소드를 처리하도록 되어 있다. UIComponent 부터, LayoutManager, SystemManager 를 들춰보면 Event.RENDER와 Event.ENTER_FRAME을 가지고 invalidate/validate 패턴을 구현하고 있다. (Flex 개발자도 어쩔 수 없었나보다. ㅡㅡ;;) 


이에 대한 자료는 아래 링크를 읽어보면 되겠다.


Flex internals: Setting a button label



Flex만 접해보고 ActionScript 3.0 학습에 소홀히 했다면 절대 이런 문제를 발견할 수 없었을 것이다. 




정리하며


Stage.invalidate()가 호출할 때 때떄로 Event.RENDER가 호출되지 않는 문제는 버그가 아닐 수 있다. 원래 그렇게 만들어졌을지도 모른다. 하지만 개발자는 이것을 버그로 여긴다. 왜냐하면 보통 개발할때는 그런 것은 생각안하고 메뉴얼만 보고 당연히 그러겠지 생각하기 때문이다. 좀 황당하지만 버그 아닌 버그로 Flex 까지 다 까보게 되었다 ㅡㅡ;


Event.RENDER, Stage.invalidate() 모를때는 setTimeout()과 Timer를 이용해 invalidate/validate 패턴을 구현했었다. 아~ 무식은 용감하다. ㅋ



Essensial ActionScript 3.0(이하 EAS)책에 보면 Stage의 invalidate() 메소드와 Event.RENDER의 활용법이 나와있다.(한국어 판이 나올 예정이니 기대가 크다. ^^)

 

invalidate() 메소드는 Event.RENDER 이벤트를 발생해주는 역할을 한다. 이것을 적절하게 사용하면 화면갱신을 필요할 때만 적절하게 수행할 수 있기 때문에 애플리케이션의 전체적인 퍼포먼스를 향상할 수 있게 된다. Event.RENDER 이벤트는 invalidate() 메소드의 호출 외에도 MouseEvent, KeyboardEvent등의 updateAfterEvent() 메소드를 호출해도 발생한다.

 

EAS 23장에 Event.RENDER를 이용한 최적화에서 최종 예를 보면 다음과 같다.(올려도 되는건가??)

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);

   if (hasChanged( )) {
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   stage.addEventListener(Event.RENDER, renderListener);
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

왜 저렇게 만들었는가 이유를 알고 싶다면 직접 분석하거나 EAS를 구입해보는 것이 좋겠다. 이것만은 기억하자 저렇게 만든 이유는 witdh, height가 바뀔때마다 draw() 메소드가 호출되어 쓸데없이 렌더링되는 것을 방지하기 위함이다.

 

본인의 생각에 위 코드의 최적화 부분에서 약간 문제점이 있다는 생각이 든다. 그것은 Ellipse객체가 Stage에 추가될 때 호출되는 addedToStageListener()내에 정의된 stage.addEventListener(Event.RENDER, renderListener)이 있는 부분이다.

 

이는 만약 100개의 Ellipse객체가 Stage에 추가된 경우 100개의 renderListener()이 등록된 것과 동일하다. Event.RENDER 이벤트가 발생할 때 renderListener()에서 hasChanged()메소드를 이용해 변경해야하는 경우에만 draw()를 호출한다고 하지만 그래도 여전히 100개의 renderListener() 이벤트 청취자 함수가 호출되는 것은 마찬가지이다.

 

렌더링하는 시점에만 stage.addEventListener(Event.RENDER, renderListener)로 renderListener() 메소드를 등록하고 renderListener()이 호출되면 stage.removeEventListener(Event.RENDER, renderListener)를 실행하면 쓸데없이 renderListener() 메소드가 호출되는 것을 막을 수 있지 않을까? 가령, 1개의 Ellipse객체만 다시 그려야하는 경우 그 객체의 renderListener() 메소드만 호출하면 되기 때문에 전체 퍼포먼스를 향상하는데 더 좋을 것이다. 그래서 본인은 위 Ellipse 클래스를 아래처럼 수정해봤다.

 

 

package {
 import flash.display.Shape;
 import flash.events.*;

 public class Ellipse extends Shape {
  private var w:Number;
  private var h:Number;
  private var changed:Boolean;

  public function Ellipse (width:Number, height:Number) {
   var stageDetector:StageDetector = new StageDetector(this);
   stageDetector.addEventListener(StageDetector.ADDED_TO_STAGE,
                  addedToStageListener);
   stageDetector.addEventListener(StageDetector.REMOVED_FROM_STAGE,
                  removedFromStageListener);

   w = width;
   h = height;

   setChanged( );
  }

  public function setWidth (newWidth:Number):void {
   w = newWidth;
   setChanged( );
  }

  public function getWidth ( ):Number {
   return w;
  }

  public function setHeight (newHeight:Number):void {
   h = newHeight;
   setChanged( );
  }

  public function getHeight ( ):Number {
   return h;
  }

  private function setChanged ( ):void {
   changed = true;
   if (stage != null) {
    stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function clearChanged ( ):void {
   changed = false;
    stage.removeEventListener(Event.RENDER, renderListener);
  }

  protected function hasChanged ( ):Boolean {
   return changed;
  }

  private function addedToStageListener (e:Event):void {
   if (hasChanged( )) {
		stage.addEventListener(Event.RENDER, renderListener, false, 0, true);
    stage.invalidate( );
   }
  }

  private function removedFromStageListener (e:Event):void {
   clearChanged();
  }

  private function renderListener (e:Event):void {
   if (hasChanged( )) {
    clearChanged();
    draw( );
   }
  }

  private function draw ( ):void {
   graphics.clear( );
   graphics.lineStyle(1);
   graphics.beginFill(0xFFFFFF, 1);
   graphics.drawEllipse(0, 0, w, h);
  }
 }
}

 

자, 위처럼 하면 필요할때만 renderListener()를 호출하므로 더 최적화 되었다고 할 수 있겠다.

 

이 방식을 응용하여 Sprite를 확장해서 필요할 때만 렌더링해주는 Flex의 UIComponent와 같은 추상클래스(?)를 만들어주면 어떨까?

 

이름은 flash.display.UISprite로 하고 그에 대한 인터페이스는 flash.display.IUISprite로 정했다. UISprite에는 Flex의 UIComponent에서 제공하는 invalidateProperties(), invalidateDisplayObject()를 Event.RENDER를 이용해 흉내낸 클래스이다. Flex SDK를 활용하지 않고 개발할 때 이 클래스는 최적화된 Rendering을 하는데 도움을 줄 수 있을 것이다.

 

 

package flash.display
{
	import flash.display.Sprite;
	import flash.events.Event;

	/**
	 * ActionScript 3.0 기반으로 만드는 프로젝트의 시각화 객체를 사용해야할 경우 이것을 확장해서 사용하도록 한다.
* 사용법은 Flex의 UIComponent와 흡사하다. * * @author Yongho, Ji (jidolstar@gmail.com) * @since 2009.05.12 */ public class UISprite extends Sprite implements IUISprite { private var isInvalidateProperties:Boolean = true; private var isInvalidateDisplayObject:Boolean = true; private var isCreatedChildren:Boolean = false; private var isCreationComplete:Boolean = false; public function UISprite() { super(); this.addEventListener( Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true ); this.addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true ); } /** * 고정적인 시각화 객체를 추가할 때 사용하는 함수로 Override 해서 사용한다. */ protected function createChildren():void { } /** * 속성이 변경되었을 때 처리하는 함수로 Override 해서 사용한다. */ protected function commitProperties():void { } /** * 시각화 객체가 새로 렌더링할때 사용하는 함수로 Overide해서 사용한다. */ protected function updateDisplayObject():void { } /** * 라이프 사이클이 모두 완료되면 이 함수가 호출된다. * 단 한번만 호출되는데 필요할 때 Override해서 사용하면 되겠다. */ protected function creationComplete():void { } /** * @inheritDoc */ public function validateProperties():void { isInvalidateProperties = true; commitProperties(); } /** * @inheritDoc */ public function validateDisplyObject():void { isInvalidateDisplayObject = true; updateDisplayObject(); } /** * @inheritDoc */ public function invalidateProperties():void { if( isInvalidateProperties ) return; isInvalidateProperties = true; invalidate(); } /** * @inheritDoc */ public function invalidateDisplayObject():void { if( isInvalidateDisplayObject ) return; isInvalidateDisplayObject = true; invalidate(); } /** * 무효화 */ protected function invalidate():void { if( stage != null ) { stage.addEventListener( Event.RENDER, onRender, false, 0, true ); stage.invalidate(); } } /** * Stage에 추가 되었을때 처리 */ private function onAddedToStage( event:Event ):void { //자식 추가, 처음에만 한번 호출됨 if( !isCreatedChildren ) { isCreatedChildren = true; createChildren(); } //무효화 처리 if( isInvalidateProperties || isInvalidateDisplayObject ) { invalidate(); } } /** * Stage에서 제거 되었을 때 처리 */ private function onRemovedFromStage( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } } /** * Render 이벤트 처리 */ private function onRender( event:Event ):void { if( stage != null ) { stage.removeEventListener( Event.RENDER, onRender ); } //commit properties if( isInvalidateProperties ) { isInvalidateProperties = false; commitProperties(); } //update display object if( isInvalidateDisplayObject ) { isInvalidateDisplayObject = false; updateDisplayObject(); } //creation complete. call only once if( !isCreationComplete ) { isCreationComplete = true; creationComplete(); } } /** * @inheritDoc */ public function get childrenCreated():Boolean { return isCreatedChildren; } /** * @inheritDoc */ public function get creationCompleted():Boolean { return isCreationComplete; } /** * @private */ override public function set width(value:Number):void { super.width = value; invalidateDisplayObject(); } /** * @private */ override public function set height(value:Number):void { super.height = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleX(value:Number):void { super.scaleX = value; invalidateDisplayObject(); } /** * @private */ override public function set scaleY(value:Number):void { super.scaleY = value; invalidateDisplayObject(); } /** * override해서 trace문을 대신 정의해서 사용한다. * * @example * * override protected function TRACE( ...args ):void * { * trace( prototype.constructor.toString(), args ); * } * */ protected function TRACE( ...args ):void { //trace( prototype.constructor.toString(), args ); } } }

 

UISprite는 검증되지 않았고 프로젝트에 적용하기 위해 테스트 단계에 있다.

 

전체 소스는 아래서 다운로드 받는다.

 

 

이 클래스에 대한 문제점 및 다른 의견이 있다면 언제든지 댓글 및 피드백을 걸어주었으면 한다.

+ Recent posts