반응형

Flex의 Visual Component(Box, Button, Application 등)은 기본적으로 UIComponent를 extends(상속)하여 만들어진다.

화면에 보여지는 컴포넌트라면 UIComponent를 extends하여 만들 것을 추천한다.

왜냐하면 기본적으로 잘 정리된 라이프 사이클(Life Cycle)로 구성되어 있어서 이를 잘 이용하면 쉽게 컴포넌트 하나를 만드는데 어려움이 없기 때문이다. 하지만 만약 개발자가 UIComponent에서 기본적으로 제공하는 라이프 사이클을 커스터마이징(Customize - 개인 또는 고객이 입맛에 맞게 만드는 것)해야 한다면 UIComponent가 아닌 Sprite나 DisplayObject를 extends하여 만들면 된다. 하지만 이때는 개발자가 많은 일을 해야한다.
(Naver Cafe FlexComponent 차카게님의 댓글 인용)

참고로 UIComponent는 다음과 같은 Class를 상속받아 만들어졌다.

UIComponent Inheritance FlexSprite Inheritance Sprite Inheritance DisplayObjectContainer Inheritance InteractiveObject Inheritance DisplayObject Inheritance EventDispatcher Inheritance Object

출처 : http://flexdocs.kr/docs/flex2/langref/m ··· ent.html

여기에 SpriteDisplayObject가 있다는 것을 확인하길 바란다. 참고로 각종 Event를 Dispatch(송출)해야하므로 EventDispatcher를 extends했다는 것도 확인할 수 있다.

이 글은 비주얼 컴포넌트가 기동시에 Event 및 method가 실행되는 시점을 분석하여 컴포넌트의 라이브 사이클을 알아내고 확장컴포넌트를 만드는데 필요한 지식을 얻는 것을 목표로 하고 있다.


1. 컴포넌트 기동시 송출(dispatch)되는 Event 분석


UIComponent 및 UIComponent를 extends한 컴포넌트는 기동시 아래와 같은 이벤트를 송출한다.

사용자 삽입 이미지

그림 1


이를 확인하기 위해 아래와 같은 코드로 확인해볼 수 있다.

ExLifeCycle.mxml (Language : xml)
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    preinitialize="traceEvent(event)"
    initialize="traceEvent(event)"
    creationComplete="traceEvent(event)"
    updateComplete="traceEvent(event)">

    <mx:Script>
        <![CDATA[
            private function traceEvent(event:Event):void
            {
                trace(event.currentTarget + " - " + event.type );
            }
        ]]>

    </mx:Script>
</mx:Application>

결과는 Debug모드에 Console 창에서 아래와 같이 나온다.
ExLifeCycle0 - preinitialize
ExLifeCycle0 - initialize
ExLifeCycle0 - creationComplete
ExLifeCycle0 - updateComplete
그럼 각 이벤트에 대해서 간단하게 설명하겠다.
  1. preinitialize - UIComponent에 정의된 initialize() 메소드가 호출되면 preinitialize 이벤트를 송출한다. 개발자가 내부구조를 작성하기 전에 컴포넌트를 변경하고 싶을때 preinitialize 이벤트에 대한 Listener를 작성해서 사용한다. 말그대로 가장 최초에 해야할 작업이 있을때 사용하면 된다.
    참고로 initialize() 메소드는 컴포넌트의 부모의 addChild() 및 addChildAt() 메소드을 통해 자식으로 등록이 될 때 자동적으로 호출되는 함수이다. initialize() 메소드에 대한 자세한 내용은 http://flexdocs.kr/docs/flex2/langref/m ··· itialize 를 참고한다.
  2. initialize - UIComponent의 initialize()메소드에서 내부적으로 처리해야하는 사항들(자식만들기,속성값정의,사이즈정의,그림그리기등)을 모두 처리할 수 있도록 한 뒤 initialize 이벤트를 송출한다. 컴포넌트의 레이아웃전에 추가 처리가 필요한 경우 이 이벤트를 사용한다.
  3. creationComplete - 컴포넌트 구축, 속성처리, 측정, 레이아웃 및 그림그리기가 모두 완료되었을때 송출된다. 이 이벤트는 컴포넌트 생성시 단 한번만 호출된다.
  4. updateComplete - 내부적으로 commitProperties(), measure(), updateDisplayList() 메소드을 호출함으로 인해 컴포넌트의 레이아웃, 위치, 사이즈등의 시각적 특징이 변경되어 컴포넌트의 화면상 모습이 갱신될때마다 송출된다.
이 이벤트들의 특성을 잘 파악하고 쓸 필요가 있다. 만약 Button컴포넌트를 생성되기도 전에 width와 height를 참고할려고 하면 그 값은 모두 0인 경우가 있다. 이런 경우에는 creationComplete 이벤트를 받아 처리하면 쉽게 해결할 수 있을것이다. 또 버튼이 그려지기 전에 동적으로 값을 설정할 필요가 있는 경우 preinitialize 이벤트를 받아 처리한다. 물론 Button 조차 동적으로 생성하는 경우에는 부모에 addChild()함수등으로 인해 자식으로 포함되기전 관련 작업을 해도 되겠다. 왜냐하면 addChild한 뒤에 initialize()함수가 호출이 되기 때문이다.


2. 컨테이너와 자식 컴포넌트 기동시 Event 송출 흐름 분석

여기서 말하는 컨테이너는 Application, Canvas, Box등이 그것이다. 자식으로 Component는 Button, ComboBox등이 되겠다. 아래 그림을 보자. 그림은 컨테이너가 자식컴포넌트를 가질때 발생하는 Event에 대해 잘 묘사하고 있다.

사용자 삽입 이미지

그림 2. 컨테이너에 속한 라이프사이클중에 Dispatch(송출)되는 주요 이벤트


전체적인 흐름은 그림 1 과 다른 것이 없다. 추가 및 변경 되는 사항은 컨테이너에 childAdd Event가 추가되었다는 것과 부모와 자식간에 Event 송출시점이 다르다는 점이다. 이런 Event흐름을 가질 수 밖에 없는 것은 앞에서 Event의 특성을 생각해보면 쉽게 유추할 수 있다. 참고로 만약 컨테이너가 Application인 경우엔 마지막에 applicationComplete Event가 송출된다.

다음은 위의 이벤트 흐름을 확인하기 위한 예제 코드이다.
(FlexComponent카페 브라이언님의 예제를 참고하였다. http://cafe.naver.com/flexcomponent/1200)

ExLifeCycle.mxml (Language : xml)
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    layout="absolute"
    add="traceEvent(event)"
    added="traceEvent(event)"
    preinitialize="traceEvent(event)"
    initialize="traceEvent(event)"
    childAdd="traceEvent(event)"
    creationComplete="traceEvent(event)"
    updateComplete="traceEvent(event)"
    applicationComplete="traceEvent(event)">

    <mx:Script>
        <![CDATA[
            private function traceEvent(event:Event):void
            {
                trace(event.type+"("+ event.eventPhase + ") " + event.currentTarget.name + " " + event.target.name + "   " + event.target.parent.name  );
            }
        ]]>

    </mx:Script>
    <mx:Button id="button"
            add="traceEvent(event)"
            added="traceEvent(event)"
            preinitialize="traceEvent(event)"
            initialize="traceEvent(event)"
            creationComplete="traceEvent(event);"
            updateComplete="traceEvent(event)"/>

</mx:Application>


결과는 Debug모드에서 실행하면 Console 창에서 아래와 같이 확인할 수 있다.
이벤트 Type(Phase) currentTarget target target.parent
added(3) ExLifeCycle0 border ExLifeCycle0
add(2) ExLifeCycle0 ExLifeCycle0 root1
preinitialize(2) ExLifeCycle0 ExLifeCycle0 root1
added(2) button button ExLifeCycle0
added(3) ExLifeCycle0 button ExLifeCycle0
childAdd(2) ExLifeCycle0 ExLifeCycle0 root1
add(2) button button ExLifeCycle0
preinitialize(2) button button ExLifeCycle0
added(3) button UITextField5 button
added(3) ExLifeCycle0 UITextField5 button
initialize(2) button button ExLifeCycle0
initialize(2) ExLifeCycle0 ExLifeCycle0 root1
added(3) button upSkin button
added(3) ExLifeCycle0 upSkin button
added(3) ExLifeCycle0 backgroundMask ExLifeCycle0
added(3) ExLifeCycle0 ApplicationBackground7 ExLifeCycle0
creationComplete(2) button button ExLifeCycle0
updateComplete(2) button button ExLifeCycle0
creationComplete(2) ExLifeCycle0 ExLifeCycle0 root1
added(2) ExLifeCycle0 ExLifeCycle0 root1
applicationComplete(2) ExLifeCycle0 ExLifeCycle0 root1
updateComplete(2) ExLifeCycle0 ExLifeCycle0 root1
Phase
(위상-이벤트흐름단계)
1 : Capture단계
2 : target단계
3 : bubble 단계


아래 그림 3은 위 프로그램의 부모 자식관계를 표현하고 있다. 우리는 인위적으로 Application에 button만 컴포넌트로 추가했다. 하지만 target을 보면 Application은 child로 border, backgroundMask, ApplicationBackground를 가지는 것과 button은 child로 UITextField와 upSkin을 가진다는 것을 확인할 수 있다. 위의 결과에서 added Event와 add Event만 뺀다면 그림 2 에서 보여준 Event 흐름과 다를 바 없다는 것을 확인할 수 있다.

사용자 삽입 이미지

그림 3. parent와 child관계

여기서 Event Listener를 Application과 Button에 만들었다. 다른 Event들과 다르게 added Event가 발생할 때는 자신 및 부모 컴포넌트에게도 Event가 전파되는 것을 확인할 수 있다. 즉, 부모컴포넌트인 button에 UITextField와 upSkin에서 발생한 added Event가 전파된다는 말이다. 이는 added Event송출시에 Event의 bubbles 인자가 true이기 때문이다. 구체적으로 설명하면 dispatchEvent( new Event( "added", true, false )) 형태로 Event의 2번째 인자를 true 로 설정해서 Event를 송출(Dispatch)한다. 그러므로 이벤트가 target단계뿐 아니라 bubble단계로 전파가 된다.

더욱 구체적인 살펴보기 위해, 위 결과에서 분홍색표시된 부분을 보자. button의 자식 컴포넌트인 UITextField는 added Event를 송출한다. added Event는 bubble단계도 전파하므로 target는 항상 UITextField이고 bubble(3)단계에서 UITextField의 부모인 button이 이 added Event를 listen하고 있으므로 currentTarget이 button일때 traceEvent()를 메소드를 실행하는 것을 볼 수 있다. 또 button의 부모인 Application에서도 added Event를 Listen하고 있으므로 currentTarget이 ExLifeCycle일때 traceEvent() 메소드를 실행하게 된다.

이것을 명확하게 이해하기 위해서는 currentTarget과 target이 뜻과 이벤트 전파방법(capture, target, bubble 단계)에 대해서 정확히 이해할 필요가 있다.
이벤트 전파에 관해서는 이벤트 전파(http://flexdocs.kr/docs/flex2/docs/00000475.html)를 참고한다.

아래 그림은 위 결과를 보기 쉽게 그림으로 표현한 것이다.
사용자 삽입 이미지

그림 4. 이벤트 전파 다이어그램




그럼, 여기서 새로 나온 Event인 add, added, childAdd 이벤트에 대해서 간단히 소개한다.

  1. add : Container.addChild(), Container.addChildAt()를 사용해서 컨테이너에 자식이 추가 될 때, mx.events.FlexEvent 객체 생성
  2. added : DisplayObjectContainer.addChild(), DisplacyObjectContainer.addChildAt() 을 사용해서 컨테이너에 자식이 추가될 때 flash.events.Event 객체 생성
  3. childAdd : addChild(), addChildAt()을 사용해서 자기 하위로 자식이 추가 완료되었을 때, 부모가 자식이 잘 추가되었는지 확인하기 위해 이 이벤트로 확인이 가능하다. mx.events.ChildExistenceChangedEvent 객체 생성


3. 비주얼 컴포넌트의 확장에 대해


이미 언급했듯이 대부분의 비주얼 컴포넌트가 UIComponent를 확장해서 만들었으므로 UIComponent가 기본적으로 어떤 순서에 의해 메소드가 호출되고 이벤트가 발생하는지 알아야, 이미 만들어진 컴포넌트(Button등)를 분석할 수 있고 더 나아가 확장한 고급 컴포넌트를 만드는데 도움이 될 것이라 생각한다. UIComponent를 확장한 컨테이너 및 컴포넌트의 Event 흐름은 이미 언급했으므로 이제는 관련 메소드를 언급할 차례이다.

UIComponent를 extends한 가장 간단한 컴포넌트는 다음과 같다.

MyComp.as (Language : java)
package
{
    import mx.core.UIComponent;

    public class MyComp extends UIComponent
    {
        public function MyComp()
        {
            trace("MyComp 생성자 호출");
        }
    }
}

이러한 형태는 UIComponet일 수 있고 이미 UIComponent를 extends해서 만든 Button, Box등을 extends해도 된다.

UIComponent는 이미 생성순서에 맞게 호출하거나 중간중간 새로운속성, 사이즈조정, 화면표시에 대해 작업할 수 있도록 재정의(override)가 가능한 메소드가 만들어져 있다. 이러한 메소드들을 직접호출하지 않는다.

대표적인 메소드는 다음과 같다.

  • commitProperties() : 컴포넌트의 속성(properties)를 일괄적으로 조정할 필요가 있을 때 사용한다.  addChild에 의해 부모에 추가되었을 때와 invalidateProperties() 메소드를 호출할때 자동적으로 실행된다. 만약 getter나 setter에 의해 설정된 속성들을 이 commitProperties()에서 처리할 수 있도록 하면 width와 height 또는 horizontalScrollPolicy와 horizontalScrollPosition과 같이 서로 연관성있는 속성을 한꺼번에 처리하여 작업의 중복을 무시할 수 있도록 만들 수 있다. 구현방법에 대해서는 commitProperties()메소드의 구현을 참고한다.
  • createChildren() : 컴포넌트에 다른 자식 컴포넌트를 addChild할 경우에 사용한다. 가령 새로 작성되는 컴포넌트에 자식 컴포넌트로 ComboBox와 TextInput를 사용해야한다면 이 메소드를 재정의하여 그 안에서 addChild 한다. 이 메소드는 컴포넌트가 parent에서 addChild할때 단 한번 호출된다. 구현방법은 createChildren() 메소드의 구현을 참고한다.
  • layoutChrome() : 이 메소드는 UIComponet를 extends한 Container에 정의된 메소드이다. 컨테이너의 경계선의 위치 및 사이즈를 설정할때 사용된다. addChild에 의해 parent에 추가되었을때와 invalidateDisplayList() 메소드를 실행시 호출되는 메소드이다. 상세한 내용은 layoutChrome() 메소드의 구현을 참조한다.
  • measure() : 컴포넌트의 default 폭과 높이, default 최소 폭과 높이를 설정할 경우 사용한다. 이 메소드를 이용하면 가령, 컴포넌트에서 표시에 필요한 텍스트 양, fontSize등에 의한 텍스트 스타일, 컴포턴트에 표시되는 이미지 사이즈, 컴포넌트의 Child측정 사이즈 또는 명시적 사이즈, 모든 경계선 및 간격 등을 계산하여 적절한 사이즈를 선택해 적용이 가능하다. 기본적으로 parent에서 addChild할때와 invalidateSize()메소드를 실행하며 호출된다. 상세한 것에 대하여는,measure() 메소드의 구현을 참조한다.
  • updateDisplayList() : 앞서 지정된 속성 및 스타일 설정에 근거하여 화면상에 자식 컴포넌트의 배치 및 사이즈를 조절하고 각종 스킨 및 그래픽을 묘사하는데 사용한다. 가령, 컴포넌트에 자식 컴포넌트가 있는 경우 이 메소드에서 move() 및 setActualSize() 메소드등을 호출하여 배치나 사이즈 조정을 한다. 또, 자식 컴포넌트가 없는 경우 graphic.drawRect()와 같은 메소드를 이용해 그림을 그릴 수 있다. 사이즈의 경우 width, height대신 인자로 넘어온 unscaledWidth와 unscaledHeight를 이용한다. addChild에 의해 parent에 추가되었을때와 invalidateDisplayList() 메소드를 실행시 호출된다. 상세한 것에 대하여는,updateDisplayList() 메소드의 구현을 참고한다.
위에서 메소드들을 살펴보았듯이 확장 컴포넌트를 만들때 필요에 따라 적절히 위 메소드들을 재정의해서 사용하는 것이 중요하겠다. 관련된 예제는 ModalText 컴포넌트의 작성을 참고한다.


4. 메소드 및 Event로 본 컴포넌트의 라이브 사이클

그럼 본래 목적으로 넘어오자. 앞서 Event의 발생순서와 비주얼 컴포넌트에서 제공하는 재정의 가능한 메소드를 설명했다. 각각의 호출되는 시점과 그 때 발생하는 Event를 확인해 보도록 하겠다.

먼저 Application과 Button 확장하여 아래와 같이 위에서 제시한 메소드들을 전부 재정의한다. 재정의된 메소드 내에는 언제 메소드가 호출되는지 확인하기 위해 trace()를 사용한다.

MyApplication.as (Language : java)
package
{
    import mx.core.Application;

    public class MyApplication extends Application
    {
        public function MyApplication()
        {
            trace("    MyApplication Constructor()");
        }
       
        override public function initialize():void
        {
            trace("    MyApplication initialize()");
            super.initialize();
        }
       
        override protected function createChildren():void
        {
            trace("    MyApplication createChildren()");
            super.createChildren();
        }
       
        override protected function commitProperties():void
        {
            trace("    MyApplication createProperties()");
            super.commitProperties();
        }
       
        override protected function measure():void
        {
            trace("    MyApplication measure()");
            super.measure();
        }
       
        override protected function layoutChrome(unscaledWidth:Number, unscaledHeight:Number):void
        {
            trace("    MyApplication layoutChrome()");
            super.layoutChrome(unscaledWidth, unscaledHeight);
        }
       
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            trace("    MyApplication updateDisplayList()");
            super.updateDisplayList(unscaledWidth, unscaledHeight);
        }      
    }
}


MyButton.as (Language : java)
package
{
    import mx.controls.Button;

    public class MyButton extends Button
    {
        public function MyButton()
        {
            trace("    MyButton Constructor()");
        }

        override public function initialize():void
        {
            trace("    MyButton initialize()");
            super.initialize();
        }
       
        override protected function createChildren():void
        {
            trace("    MyButton createChildren()");
            super.createChildren();
        }
       
        override protected function commitProperties():void
        {
            trace("    MyButton createProperties()");
            super.commitProperties();
        }
       
        override protected function measure():void
        {
            trace("    MyButton measure()");
            super.measure();
        }
       
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
        {
            trace("    MyButton updateDisplayList()");
            super.updateDisplayList(unscaledWidth, unscaledHeight);
        }      
    }
}

위에서 만든 두개의 컴포넌트를 아래와 같이 사용한다. 예전에 Application과 Button대신 새로 정의한 컴포넌트인 MyApplication과 MyButton을 사용한 것 밖에 다른 점이 없다.

ExLifeCycle.mxml (Language : xml)
<?xml version="1.0" encoding="utf-8"?>
<local:MyApplication
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:local="*"
    layout="absolute"
    add="traceEvent(event)"
    added="traceEvent(event)"
    preinitialize="traceEvent(event)"
    initialize="traceEvent(event)"
    childAdd="traceEvent(event)"
    creationComplete="traceEvent(event)"
    updateComplete="traceEvent(event)"
    applicationComplete="traceEvent(event)">
   
    <mx:Script>
        <![CDATA[
            private function traceEvent(event:Event):void
            {
                trace(event.type+"("+ event.eventPhase + ") " + event.currentTarget.name + " " + event.target.name + "    " + event.target.parent.name  );
            }
        ]]>

    </mx:Script>
    <local:MyButton id="button" label="Submit" 
                add="traceEvent(event)"
                added="traceEvent(event)"
                preinitialize="traceEvent(event)"
                initialize="traceEvent(event)"
                creationComplete="traceEvent(event)"
                updateComplete="traceEvent(event)"/>

</local:MyApplication>


 

결과는 다음과 같다.
이벤트Type(Phase) currentTarget target target.parent method
        MyApplication Constructor()
added(3) ExLifeCycle0 border ExLifeCycle0  
add(2) ExLifeCycle0 ExLifeCycle0 root1  
        MyApplication initialize()
preinitialize(2) ExLifeCycle0 ExLifeCycle0 root1  
        MyApplication createChildren()
        MyButton Constructor()
added(2) button button ExLifeCycle0  
added(3) ExLifeCycle0 button ExLifeCycle0  
childAdd(2) ExLifeCycle0 ExLifeCycle0 root1  
add(2) button button ExLifeCycle0  
        MyButton initialize()
preinitialize(2) button button ExLifeCycle0  
        MyButton createChildren()
added(3) button UITextField5 button  
added(3) ExLifeCycle0 UITextField5 button  
initialize(2) button button ExLifeCycle0  
initialize(2) ExLifeCycle0 ExLifeCycle0 root1  
        MyApplication commitProperties()
        MyButton commitProperties()
added(3) button upSkin button  
added(3) ExLifeCycle0 upSkin button  
        MyButton measure()
        MyApplication measure()
        MyApplication updateDisplayList()
        MyApplication layoutChrome()
added(3) ExLifeCycle0 backgroundMask ExLifeCycle0  
added(3) ExLifeCycle0 ApplicationBackground7 ExLifeCycle0  
        MyButton updateDisplayList()
creationComplete(2) button button ExLifeCycle0  
updateComplete(2) button button ExLifeCycle0  
creationComplete(2) ExLifeCycle0 ExLifeCycle0 root1  
added(2) ExLifeCycle0 ExLifeCycle0 root1  
applicationComplete(2) ExLifeCycle0 ExLifeCycle0 root1  
updateComplete(2) ExLifeCycle0 ExLifeCycle0 root1  
Phase
(위상-이벤트흐름단계)
1 : Capture단계
2 : target단계
3 : bubble 단계


아래 그림은 위 결과를 보기 쉽게 다이어그램으로 표시했다.

사용자 삽입 이미지

그림 5. 이벤트 전파 및 메소드 실행 시점 다이어그램


앞서 설명했듯이 initialize() 메소드는 컴포넌트의 부모 컴포넌트가 addChild할 때에 호출되며 처음에는 preinitialize 이벤드를 송출하고 creationChildren() 메소드를 수행한 다음 initialize 이벤트를 송출한다.  부모 컴포넌트인 경우 자식을 성공적으로 add하면 childAdd 이벤트를 송출한다.

measure(), layoutChrome(), updateDisplayList() 메소드는 initialize 이벤트가 송출된 다음에 호출이 되며 이 함수들이 모두 실행한 후에 creationComplete 이벤트와 updateComplete 이벤트가 발생하는 것을 확인할 수 있다.

이에 대해 조금더 이해하기 위해 컴포넌트의 인스턴스화 라이프 사이클에 대해를 참고하면 되겠다.


정리하며

비주얼 컴포넌트의 라이브 사이클을 메소드와 이벤트를 통해 이해하고 있으면 좀더 고급적인 컴포넌트를 작성하는데 도움이 된다. 내용을 이해하는 것이 그리 쉽지는 않을 것이라 생각한다. 하지만 Flex 컴포넌트를 이해하는데 매우 중요한 개념이므로 꼭 숙지할 필요가 있다.

내용도 많고, 시간을 너무 많이 소비하는 것 같고, 귀차니즘도 몰려오고 그래서 약간 내용의 부실함 가져온 것 같다. 아래 참고글들을 잘 읽어보고 이해 안가는 내용을 조금씩 습득했으면 한다.

앞으로 이와 관련되어 좋은 글이 많이 나왔으면 한다.
Posted by 1010