태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.
밤하늘의 실제별, 나도 가질 수 있다?!

[Flex]동적으로 스킨을 적용시킨 시계 컴포넌트 제작해보기

2008/05/15 13:22

 

[공지]이미지나 링크가 깨졌다면 댓글 부탁드립니다.

이 글은 한국 어도비 Flex 공식사이트 http://adobeflex.co.kr/ 에 올린 기술문서이다.

목차

1. SDK에서 제공하는 컴포넌트가 아니라면?
2. 시계 컴포넌트 제작 프로세스 1
  2.1 시계 컴포넌트 제작 의뢰
  2.2 시계 컴포넌트 제작 제작 방향 선정
  2.3 시계 컴포넌트 제작
    2.3.1 스킨 제작
    2.3.2 CSS 제작해보기
    2.3.3 스킨 공통 라이브러리 제작
    2.3.4 프로그램 기반 skin을 제작해보자
    2.3.5 시계 컴포넌트의 Base를 만들자
    2.3.6 아날로그 시계를 만들어 보자
    2.3.7 디지털 시계 만들어보기
  2.4  1차 제작 완료
3. 시계 컴포넌트 제작 프로세스 2
  3.1 시계 컴포넌트 추가 제작 의뢰
  3.2 시계 컴포넌트 추가 의뢰 사항에 따른 제작 방향 선정
  3.3 추가 제작
    3.3.1 벽시계 제작
    3.3.2 아날로그+디지털 시계 컴포넌트 제작
    3.3.3 시계 위젯 제작


내용



개인적으로 Flex의 매력에 흠뻑 빠지게 하는 요인 중 하나가 바로 CSS를 이용하여 동적으로 스킨을 바꿀 수 있는 것이 아닌가 싶다.


CSS를 이용해 동적으로 스킨을 바꾼다?
이를 풀어 설명하면, 프로그래머가 만든 커스텀 비주얼 컴포넌트(custom visual component)에 CSS를 포함(Embed)시키는 것이 아니라 따로 CSS 파일을 여러 개 만들어 프로그램 실행 후 원하는 스킨만 골라서 동적으로 적용시킬 수 있게 한다는 말이다. 한가지 더 유용한 점은 Flash를 이용하여 SWF 형태의 스킨을 만들고 그 안에 Symbol들을 CSS에 포함시킬 수 있는 점이다. 이는 디자이너와 개발자간에 쉽게 협업할 수 있게 하는데 아주 좋은 방법을 제공해준다. 즉, 디자이너는 Flash를 이용해 Symbol들을 만들어 주고 개발자는 기본 CSS를 제공하고 그에 맞게 프로그램 하면 된다.


Flex SDK 소스 분석을 통한 해법 찾기
Flex SDK에서 제공하는 비주얼 컴포넌트를 사용한다면 컴포넌트에 적용할 스킨은 디자이너 몫이다. Flex SDK에서 제공하는 기본 스킨으로 구성된 Flash 파일인 AeonGraphical.fla 안에 있는 Symbol들만 수정하면 된다. 이렇게 기본으로 제공되는 비주얼 컴포넌트를 사용해 다른 다양한 커스텀 비주얼 컴포넌트를 만들어도 쉽게 스킨을 적용할 수 있게 된다.
그러나 만약, Flex SDK에서 제공하는 컴포넌트가 아닌 전혀 새로운 컴포넌트를 만들면서 이러한 스킨 기능을 제공하는 경우라면 어떻게 할 것인가? 다행인 점은 이러한 어려움에 빠졌을 때, 가이드가 될 수 있는 것이 바로 Flex SDK라는 것이다. Namespace가 mx로 시작하는 컴포넌트들은 모두 소스가 공개되어 있다. 이 소스를 분석하고 공부해두면 새로운 커스텀 컴포넌트를 만들면서 부딪히는 어려움을 극복하는데 큰 도움을 얻을 수 있다. 완전히 새로운 컴포넌트를 만들어 스킨을 적용시키는 것도 예외가 아니다. Button, ComboBox등만 어느 정도 분석해도 나만의 새로운 컴포넌트를 만드는데 큰 도움을 받을 수 있다.
Flex에서 사용하는 모든 비주얼 컴포넌트는 UI Component를 확장(Extends)해 만들어진다. 기존 SDK에서 제공하는 컴포넌트를 전혀 사용하지 않는다면 바로 UI Component를 확장해서 만들면 된다. UI Component를 사용할 때는 기본적으로 라이프 사이클(Life cycle)에 대해서 이해하고 그 때 발생되는 이벤트들과 호출되는 함수의 특성을 잘 알아둘 필요가 있다. 특히 무효화 함수(invalidateSize(), invalidateProperties(), invalidateDisplayList())들에 대해서 잘 이해하고 활용할 줄 알아야 한다. 특별히 지금부터 언급할 스킨 적용을 위해 몇가지 함수의 특징도 알아야 한다. UI Component는 다양한 인터페이스들을 implement로 사용하게 되는데, 그 중에 IStyleClient, ISimpleClient 등이 있다. 여기에 선언된 함수들 중 getStyle(), setStyle(), styleChanged() 등이 있는데 이들 함수를 잘 활용해야 한다. 더 자세한 내용은 이 글의 주제에서 벗어나므로 생략하도록 하겠다.


1. SDK에서 제공하는 컴포넌트가 아니라면?
여러분은 Flex SDK에서 기본으로 제공하는 비주얼 컴포넌트(Button, ComboBox) 외에 완전히 새로운 비주얼 컴포넌트를 만들어야할 상황에 직면한 적이 있는가?
지금 소개할 컴포넌트가 바로 그런 컴포넌트 중에 하나인, 시계 컴포넌트이다. 시계 컴포넌트는 Flex SDK에서는 제공하지 않는 컴포넌트이다. 그러므로 앞서 말했던 UI Component를 확장해서 만들어야 한다. 중요한 점은 이 시계 컴포넌트가 요즘 많이 사용하는 블로그나 미니홈피 등에 사용되는 위젯(widget) 기반으로 만들어져 다양한 스킨을 적용할 수 있어야 하며 디지털시계, 아날로그 시계, 벽시계, 디지털+아날로그 시계 등 다양한 시계 형태로 확장이 가능해야 한다는 점이다.
시계 컴포넌트를 만듦에 있어서 한가지 프로세스를 가지고 제작 예를 보여주고자 한다. 제작의뢰자가 있고 개발자와 디자이너가 있다. 제작의뢰자는 기획자 또는 외부업체 등이 될 수 있겠다. 제작의뢰자는 다양한 스킨을 입힐 수 있는 시계 컴포넌트 제작을 의뢰했다. 제작자(개발자/디자이너)는 의뢰한 사항을 분석해 제작방향을 선정하고 제작에 들어간다. 후에 의뢰자는 추가 개발을 의뢰하게 되며 제작자는 다시 제작방향을 선정하고 개발에 착수하게 된다. 이러한 프로세스로 진행하도록 하겠다.
한가지 언급할 내용은 여기서는 프로그램 소스는 전부 공개하지만 프로그램적인 기술은 많이 언급하지 않는다는 점이다. 사실 언급하기에도 분량이 많기 때문에 이러한 프로세스로 만들어 갔다는 것만 숙지하고 소스는 따로 분석하길 바란다.


2. 시계 컴포넌트 제작 프로세스 1
2.1 시계 컴포넌트 제작 의뢰

시계 컴포넌트에 대한 제작 의뢰사항은 다음과 같다.


 a. 디지털 시계와 아날로그 시계를 Flash 기반으로 만들어 줄 것
 b. 시간대(timezone)을 설정할 수 있게 할 것
 c. 시계의 스킨을 자유자제로 바꿀 수 있게 할 것


2.2 시계 컴포넌트 제작 제작 방향 선정
어떤 컴포넌트를 만들던지 확장성을 고려하지 않을 수 없다. 또 스킨을 사용해야 하므로 그에 대한 제작 방향도 고려해야겠다. Flex에서는 일반 이미지 형태를 스킨으로 사용할 수 있지만 프로그램 형태로 스킨을 만들 수 있다. ProgrammaticSkin 클래스를 이용하면 되는데 이 클래스를 이용해서 시계 컴포넌트의 기본적인 모습을 만들어 낼 수 있겠다. 이러한 사항들을 고려할 때, 다음과 같은 제작방향을 선정하도록 했다.


 a. 추가사항에 대비하여 다양한 확장 경로 확보
시계 컴포넌트는 UI Component를 확장하여 만들어지며 기본적인 시계의 기능을 제공할 수 있도록 ClockBase 클래스를 만들어 준다. 이 클래스는 Date 클래스와 Timer를 이용해 시, 분, 초를 1초마다 변경할 수 있도록 하며, 시간대(timezone) 설정도 할 수 있도록 한다. ClockBase를 확장해 디지털 시계인 DigitalClock 클래스와 아날로그 시계인 AnalogClock 클래스를 제작하도록 한다.


 b. 아날로그 시계(AnalogClock 클래스) 컴포넌트의 CSS 및 스킨 정책
아날로그 시계는 배경, 시침, 분침, 초침, AM/PM마크, 바늘회전 중심위에 올라가는 cap 등의 스킨을 만드는 것으로 한다. CSS를 이용해 이들의 스킨 뿐만 아니라 Layout, 그림자 효과, 모션관련 Effect에 대한 정의도 수정될 수 있도록 제작한다.


 c. 디지털 시계(DigitalClock 클래스) 컴포넌트의 CSS 및 스킨 정책
디지털 시계는 아날로그 처럼 배경, AM/PM 마크등이 스킨이 되며 CSS를 이용해 시간을 표시하는 Label의 폰트 설정과 Layout 설정이 될 수 있도록 한다.


 d. 기본 스킨은 ProgrammaticSkin 계열로 제작한다.
 e. 각종 스킨 및 Layout를 포함하는 CSS를 정적(Embed) 혹은 동적인 방법으로 적용될 수 있도록 제작한다.


지금까지 의뢰 사항에 대한 것뿐만 아니라 다양한 확장경로도 고려해서 제작방향을 설정해 보았다. 이제 이에 따라 제작하면 된다.


2.3 시계 컴포넌트 제작
2.3.1 스킨 제작

Flex의 강점 중 하나가 다양한 형태로 스킨을 적용시킬 수 있는 것은 이미 언급했다. 특별히 CSS를 이용하여 스킨을 입히도록 할 것이다. 디자이너는 스킨 제작 방향에 따라서 아래와 같이 아날로그 시계와 디지털 시계를 디자인 했다.



디자인된 시계 컴포넌트


디자인 결과물을 가지고 배경, 바늘과 같은 구성요소를 분리시켜서 한개씩 이미지로 만들어서 CSS 안에서 backgroundSkin: Embed(source=”backgroundSkin.jpg”); 형태로 만들어도 무방하다. 하지만 그 갯수가 많아질 경우 관리가 힘들어지므로 Flash를 이용해 한개의 파일로 일괄적으로 Symbol로 만들어주는 것이 훨씬 좋다. 디자인된 시계를 Flash 위에 MovieClip으로 만들어 Symbol 형태로 배치시켜 놓은 것이다. 어떤 배치로 가져가도 상관없으며 편의성을 위해 각각의 스킨에 아래처럼 Symbol 이름을 표시해두는 것이 좋겠다.



SWF 파일로 제작된 시계 컴포넌트의 스킨


Symbol의 이름을 지정하는 방법은 라이브러리에서 해당 MovieClip을 선택한 후 오른쪽 마우스 버튼을 누르면 아래 그림처럼 Context 박스가 나온다. 여기서 Linkage…를 선택하면 창이 하나 뜨는데 아래 Linkage에 Export for ActionScript를 체크하면 Identifier 텍스트Input 박스가 활성화 된다. 여기에 Symbol 이름을 지정하면 된다. 이렇게 하면 내부적으로 ActionScript로 접근이 가능한 형태로 만들어진다.
한가지 중요한 점은 시침, 분침처럼 회전하는 것에 대해서는 (0,0) 기준 점을 잘 맞춰야 한다. 위 그림에서 배경 스킨이 선택되어 있는데 파란색 선 상단좌측에 ‘+’ 모양이 있다. 이 배경스킨의 기준점은 바로 +로 표시된 점이다. 만약 스킨을 회전(rotation 속성 사용)하게 되면 이 점을 기준으로 회전하기 때문에 반드시 회전하는 스킨인 경우 (0,0) 기준점을 잘 잡아주는 것이 중요하겠다.
 


MovieClip을 Symbol로 지정하는 방법


이렇게 만들어진 스킨을 담은 SWF 파일에 이미지들을 CSS에 담기 위해 backgroundSkin: Embed(source=”ClockSkin.swf”, symbol=”b_am”); 식으로 사용하면 되겠다.


2.3.2 CSS 제작해보기
CSS를 만드는 것은 의외로 간단하며 쉽게 제작할 수 있다. 동적 또는 Embed 방식에 모두 적용할 수 있게 하기 위해 CSS를 외부파일로 만드는게 좋다. 위의 스킨을 Embed하고 레이아웃의 정보를 담는 CSS 파일을 만들어보자.


AnalogClock
{
 /* 스킨 */
 backgroundSkin: Embed(source="assets/ClockSkins.swf", symbol="skin_rainbow");
 hourNeedleSkin: Embed(source="assets/ClockSkins.swf", symbol="r_hour");
 minuteNeedleSkin: Embed(source="assets/ClockSkins.swf", symbol="r_minute");
 secondNeedleSkin: Embed(source="assets/ClockSkins.swf", symbol="r_second");
 amSkin: Embed(source="assets/ClockSkins.swf", symbol="r_am");
 pmSkin: Embed(source="assets/ClockSkins.swf", symbol="r_pm");
 
 
 /* 스킨 visible */
 visibleHourNeedle:true;
 visibleMinuteNeedle:true;
 visibleSecondNeedle:true;
 visibleAMPM:true;
 visibleNeedleCap:false;
 
 /* 그림자 처리 */
 hourNeedleDropShadowEnabled : true;
 hourNeedleDropShadowLength: 4;
 hourNeedleDropShadowAngle: 45;
 hourNeedleDropShadowColor: #000000;
 hourNeedleDropShadowAlpha: 0.2;
 (생략)
 
 /* 스킨 위치, 바늘의 위치가 생략되면 Background 크기의 중간에 위치됩니다. */
 ampmX: 10;
 ampmY: 55;
 
 /* 스킨의 Layer 위치 순서 */
 skinLayers: amSkin,pmSkin, hourNeedleSkin, minuteNeedleSkin,secondNeedleSkin,needleCapSkin;
}


위 CSS는 아날로그 시계에 적용된 CSS 파일이다. 스킨을 Embed했으며 각각의 스킨에 대해 Visible 대한 정보와 바늘의 그림자 처리 정보, 그리고 Layout정보, 스킨의 상하 Index 배열 정보 등을 담는다. 이 CSS 파일을 시계 컴포넌트에 적용할 때 styleChanged() 함수가 호출되며 그 안에서 위의 정보를 처리하면 된다.


중요 포인트 : CSS를 동적으로 로드해야 한다
중요한 점은 처음에도 말했을 듯이 이러한 CSS를 동적으로 로드해야한다는 점이다. 그렇게 할려면 CSS를 컴파일을 해야한다. Flex SDK만 설치한 사람이라면 mxmlc를 이용해 컴파일 할 수 있으며 Flex Builder를 사용하는 사람이라면 해당 CSS 파일을 선택한 다음 오른쪽 마우스 버튼을 누르면 Context 메뉴에 Comfile CSS to SWF를 Check만 하면 CSS가 수정되고 저장할 때마다 자동으로 SWF로 컴파일 되어 bin 디렉토리에 만들어 지게 된다.
이렇게 만들어진 컴파일된 CSS (SWF 파일)은 StyleManager 클래스의 loadStyleDeclaration() 함수를 통해 Load를 할 수 있다. 방법은 라이브 독(Livedocs)을 참고하기 바란다.


2.3.3 스킨 공통 라이브러리 제작
스킨 기반으로 컴포넌트를 제작할 것이므로 공통적으로 사용되는 SkinLib 클래스와 SkinObject 클래스를 만들었다.


패키지 : com.jidolstar.skins
클래스 명 : SkinObject
설명 : 객체화 skin값과 그 skin을 객체화 시키기 위해 Class 정보를 담는 역할을 한다.
구성 함수 : 생성자 : 아래 구성 변수들을 인자로 받는다.
구성 변수 :
- public var skin:IFlexDisplayObject;
- public var skinClass:Class;


패키지 : com.jidolstar.skins
클래스 명: SkinLib
설명 : 이 클래스나 스킨을 추가하거나 변경, 그리고 Index를 변화시키거나 스킨의 종류를 분별하기 위해 만들어졌다.
구성 함수 : 모두 static 함수로 구성된다.

- createSkin() : 스킨을 처음 만들때 사용한다. UI Component의 createChildren() 함수에서 보통 컴포넌트를 자식으로 추가하듯이 스킨을 자식으로 추가할 때 사용하게 된다. 자동적으로 자식으로 추가 시켜주며 해당 스킨값과 Class 정보를 담은 SkinObject 객체를 반환한다. 스킨을 만들 수 없다면 null값이 반환된다. 한가지 중요한 점은 스킨이 프로그램 기반 스킨인 경우 styeName 속성이 부모 컴포넌트로 설정되며 부모의 style 변화가 있을 때 자동적으로 프로그램 기반의 스킨에서도 styleChanged() 함수가 호출된다는 점이다. 또 스킨의 이름인 name 속성에 인자값으로 넘겨주는 styleName 값이 설정되어 이름에 해당한느 스킨이 그려진다는 것이다. 이미지 기반 스킨인 경우에는 생각할 필요가 없겠다.


- changeSkin() : 스킨을 교체하기 위해 호출한다. 보통 UI Component의 styleChanged()가 호출될 때 사용하면 되겠다. createSkin()과 마찬가지로 교체를 성공적으로 하게되면 SkinObject 객체를 반환하며 교체할 수 없다면 null 값이 반환된다.


- setChildMaxIndex() : 스킨이 교체되면서 다시 자식으로 들어가면 그 스킨은 가장 마지막 Index에 위치하게 된다. 가령 background 스킨이 바뀌는 경우 시침, 분침, 초침 위에 올라와 이들을 가리게 되는데 이 함수를 이용하면 기준이 되는 자식보다 바로 위의 Index에 위치시킬 수 있다.


- isProgrammaticSkin() : 이 함수는 스킨이 Program으로 만든 스킨인가 검사해준다. Swf로 만들어진 이미지 기반 스킨인 경우 false로 반환한다.
 

2.3.4 프로그램 기반 skin을 제작해보자
이미지 기반의 스킨 말고 프로그램으로도 스킨을 만들 수 있다. mx.skins 패키지에 정의된 ProgrammaticSkin 계열의 클래스를 이용하면 되는데, 기본적으로 ISimpleClientStyle 인터페이스를 implement로 사용했기 때문에 이 클래스로 만든 스킨을 UI Component에 자식으로 추가하고 styleName을 이 UI Component의 객체(this)로 설정하면 자동적으로 UI Component의 Style 변화로 인해 styleChanged() 함수가 호출되면서 ProgrammaticSkin의 styleChanged() 함수도 호출되게 된다. 즉, UI Component에 설정된 모든 style 정보를 스킨에서도 참고할 수 있게 된다. 이미지 기반의 스킨은 이러한 것은 없으며 단지 프로그램 기반 스킨만 가능하겠다. 프로그램 기반의 스킨은 마음대로 그림을 동적으로 그릴 수 있기 때문에 편하지만 복잡한 표현에는 제한이 따른다.
아래 코드는 ProgrammaticSkin을 확장한 Border 클래스를 가지고 아날로그 시계의 프로그램 기반 스킨의 함수 구성을 보여주고 있다.


package com.jidolstar.clock.skins
{
 import mx.skins.Border;
 public class AnalogClockSkin extends Border
 {

  public function AnalogClockSkin()
  {
   super();
  }
 

  override public function styleChanged(styleProp:String):void
  {
   super.styleChanged(styleProp);

  }
 
  override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
  {
   super.updateDisplayList(unscaledWidth,unscaledHeight);
  }
 }
}
 

아래는 이러한 구성으로 backgroundSkin에 대한 정보를 styleChanged() 함수를 통해 읽어오고 skin의 name 값에 따라 해당 skin을 그려준다. 즉, 하나의 프로그램 스킨 안에 name값만 다르다면 시침, 분침, 초침 등의 스킨을 한 클래스 안에 정의할 수 있다.


protected var backgroundColor:Number;
protected var backgroundAlpha:Number;
protected var borderColor:Number;
protected var borderAlpha:Number;
protected var borderSize:Number ;

override public function styleChanged(styleProp:String):void
{
 super.styleChanged(styleProp);

 backgroundColor = getStyle("backgroundSkinColor");
 if( isNaN(backgroundColor) || !StyleManager.isValidStyleValue(backgroundColor)) backgroundColor = 0xFFFFFF;
 backgroundAlpha = getStyle("backgroundSkinAlpha");
 if( isNaN(backgroundAlpha) || !StyleManager.isValidStyleValue(backgroundAlpha) ) backgroundAlpha = .85;
 borderColor = getStyle("backgroundBorderColor");
 if( isNaN(borderColor) || !StyleManager.isValidStyleValue(borderColor) ) borderColor = 0x606060;
 borderAlpha = getStyle("backgroundBorderAlpha");
 if( isNaN(borderAlpha) || !StyleManager.isValidStyleValue(borderAlpha) ) borderAlpha = 1;
 borderSize = getStyle("backgroundBorderThickness");
 if( isNaN(borderSize) || !StyleManager.isValidStyleValue(borderSize) ) borderSize = 1;

}

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
 super.updateDisplayList(unscaledWidth,unscaledHeight);
 graphics.clear();    
 switch( name )
 {
  //배경 스킨
  case "backgroundSkin":
   drawBackgroundSkin(unscaledWidth,unscaledHeight);
   break;
 }  
}

protected function drawBackgroundSkin( w:Number, h:Number ):void
{  
 graphics.lineStyle( borderSize, borderColor, borderAlpha );
 graphics.beginFill( backgroundColor, backgroundAlpha );
 graphics.drawEllipse(x,y,w,h);
 graphics.endFill();  
}


2.3.5 시계 컴포넌트의 Base를 만들자
지금까지 스킨에 대한 내용만 언급했다. 이제 시계 컴포넌트를 만들어 보겠다. 앞서 언급했듯이 시계 컴포넌트는 크기 아날로그 시계와 디지털 시계로 나뉜다. 하지만 형태만 다르지 내부적 동작 방식은 같다고 볼 수 있겠다. 그러므로 중복된 코드를 줄이기 위해 공통적으로 사용할 수 있는 컴포넌트를 제작한 다음 이를 확장해서 사용하는 것이 좋을 것이다.
참고로 시계 컴포넌트는 Flex Library Project로 만들어 SWC 형태로 배포하는 편이 좋을 것이다. 왜냐하면 Flex Project 마다 시계 컴포넌트 소스 코드를 프로젝트 안에 일일히 복사해서 쓰는 것은 소스관리 뿐아니라 사용성 면에서 비효율적이기 때문이겠다.
시계 컴포넌트의 기반이 되는 클래스의 이름은 ClockBase로 하며 UI Component를 상속받도록 한다. 내부적으로는 Date와 Timer를 이용해 1초마다 ClockEvent를 송출하도록 제작한다. ClockEvent는 Event를 확장한 커스텀 컴포넌트가 되며 시, 분, 초, 시간대정보,  AM/PM 정보 등이 포함되도록 제작한다. 참고로 시간대 정보는 분단위(minutes)이며 우리나라의 경우 영국의 그리니치로부터 -9시간 차이가 나므로 -540분다.


package com.jidolstar.clock.events
{
 import flash.events.Event;
 public class ClockEvent extends Event
 {
  public static const TIME_CHANGED:String = "timeChanged";
  (생략)
  public function ClockEvent(type:String, hours:Number, minutes:Number, seconds:Number, timezoneOffset:Number, timezoneOffsetChanged:Boolean=false, ampmChanged:Boolean=false)
  {
   ( 생략 )
  }
 
 }
}


ClockBase는 정보등이 포함되도록 제작한다. 대략적인 구조는 다음과 같다.


package com.jidolstar.clock
{
 import com.jidolstar.clock.events.ClockEvent;
 [Event(name="timeChanged", type="com.jidolstar.clock.events.ClockEvent")]
 public class ClockBase extends UIComponent
 {
  public static const AM:String = "AM";
  public static const PM:String = "PM";
  private var timer:Timer;
  public function ClockBase():void
  {
   super();
   timer = new Timer(1000, 0);
   timezoneOffset = new Date().timezoneOffset;
   start();
  }
  private function timerHandler( event:TimerEvent ):void
  {
   setTime();  
  }
  private function setTime():void{}
  private function setHours( n:int ):void{}
  private function setMinutes( n:int ):void{}  
  private function setSeconds( n:int ):void{}
  public function start():void{}
  public function stop():void{}
  public function get running():Boolean{}
  public function get hours():int {}
  public function get minutes():int{}
  public function get seconds():int{}
  public function get ampm():String{}
  public function get timezoneOffset():Number{}
  public function set timezoneOffset( minutes:Number ):void{}
 }
}


2.3.6 아날로그 시계를 만들어 보자
ClockBase를 만들었으니 이것을 확장한 아날로그 시계 Class인 AnalogClock을 만들어 보겠다. 위에서 프로그램 기반 스킨을 모두 만들었다. 그 프로그램 기반 스킨만 가지고 아래와 같은시계를 보여줄 수 있다.



프로그램 기반 아날로그 시계 컴포넌트의 모습


createChildren()함수에서 처음에 제작한 SkinLib에 정의한 static 함수인 createSkin을 이용해 자식으로 프로그램 스킨을 자식으로 아래와 같이 추가하면 된다.


override protected function createChildren():void
{
 super.createChildren();
 var skinObject:SkinObject;
 if( !backgroundSkin )
 {
  skinObject = SkinLib.createSkin( this, "backgroundSkin", AnalogClockSkin );
  if(skinObject)
  {
   backgroundSkin = skinObject.skin;
   backgroundSkinClass = skinObject.skinClass;
  }
 } (생략)…
}


CSS를 동적으로 로드하거나 Embed하게 되면 styleChanged() 함수가 자동으로 호출된다. 이때 SkinLib에서 정의한 changeSkin() static 함수를 이용해 스킨 교체를 시도한다. 예제는 다음과 같다.


override public function styleChanged(styleProp:String):void
{
 var anyStyle:Boolean = styleProp == null || styleProp == "styleName";
 super.styleChanged( styleProp );
 var style:*;
 var skinObject:SkinObject;
 if( styleProp == "backgroundSkin" || anyStyle)
 {
  skinObject = SkinLib.changeSkin(this, backgroundSkin, backgroundSkinClass, "backgroundSkin");
  if(skinObject)
  {

   backgroundSkin = skinObject.skin;
   backgroundSkinClass = skinObject.skinClass;
   //무조건 맨 아래 Layer에 위치
   setChildIndex( DisplayObject(backgroundSkin), 0 );
   //backgroundSkin의 크기 만큼 Clock의 크기를 결정한다.
   this.width = backgroundSkin.width;
   this.height = backgroundSkin.height;
   dispatchEvent( new ResizeEvent( ResizeEvent.RESIZE, false, false));
  }
 }  (생략…)
}


backgroundSkin이 성공으로 만들어지면 backgroundSkin으로 시계 컴포넌트의 크기를 조절하도록 만들어진 것까지 확인한다. 그리고 코드를 살펴보면 갖가지 다른 속성들도 이 styleChanged() 함수를 통해 변경되는 것을 확인할 수 있겠다. 또 스킨들의 화면 표시 순서 Index도 여기서 바로 조정되는 것도 확인하기 바란다. 보통 styleChanged() 함수를 호출한 다음에는 invalidateDisplay() 함수를 호출해서 다시 그려주는 작업을 하도록 하는게 좋겠다.
invalidateDisplay()에 의해 updateDisplayList() 함수가 호출되면 이 함수에서는 각각의 스킨을 배치하고 visible 속성을 적용한다. 또한 스킨에 대한 그림자 표현도 DropShadowFilter 클래스를 이용해 표현한다.


2.3.7 디지털 시계 만들어보기
디지털 시계는 아날로그 시계와 다르게 시간을 표현하기 위해 숫자와 클론(:)이 있다. 스킨의 경우엔 backgroundSkin과 AM/PM 스킨밖에 없다.
숫자 표현을 위해 Flex SDK에서 제공하는 Label을 자식으로 createChildren()에서 아래처럼 추가해서 사용한다.


override protected function createChildren():void
{
 super.createChildren();
 (생략)  
 if( !timeLabelParent )
 {
  timeLabelParent = new UIComponent;
  addChild(timeLabelParent);
 }
 if( !timeLabel )
 {
  timeLabel = new Label;
  timeLabelParent.addChild(timeLabel);
 }
}


이 Label에 Style을 적용시키는 방법은 조금 다르다. Label도 UI Compoent를 확장해 만들었으므로 기본적으로 스타일 적용이 가능하다. Flex SDK에서는 소스를 분석해보면 이렇게 복합적으로 UI Compoent의 자식들에 스타일을 적용시키기 위해 자식들의 styleName을 CSS상에 따로 만들어 class selector 방식으로 접근하도록 하고 있다. 먼저 디지털 시계에 적용할 CSS를 보도록 하자.


DigitalClock
{
 /* 스킨 */
 backgroundSkin: Embed(source="assets/ClockSkins.swf", symbol="skin_digital");
 amSkin: Embed(source="assets/ClockSkins.swf", symbol="d_am");
 pmSkin: Embed(source="assets/ClockSkins.swf", symbol="d_pm");
 (생략…)
 /*time label StyleName*/
 timeLabelStyleName : digitalTimeLabel; /*아래 .timeLabel과 같은 이름인 것을 잘 봐두시길...*/
}

 /* time label 스타일 설정  */
 .digitalTimeLabel
 {
  color : #ffffff;
  fontFamily : digitalTimeLabelFontFamily;
  fontWeight : normal;
  fontSize: 35;
 }
 
 /* 폰트 Embed */
 @font-face
 {
     src: local("arial");
     fontFamily: digitalTimeLabelFontFamily; /*폰트이름*/
     advancedAntiAliasing: true;
     unicodeRange:
      U+0030-U+003A; /*숫자 및 콜론(:)에 대한 유니코드 */
 }


위에서 보면 알 수 있듯이 Type Selector인 DigitalClock 안에 timeLableStyleName 속성이 존재한다. 이 속성은 String 형태의 digitalTimeLabel 로 지정되어 있다. 같은 이름으로 앞에 콤마(.)만 하나 붙은 Clsss Selector인 .digitalTimeLabel 이 정의되어 있다. 이는 디지털 시계 컴포넌트에 시간을 표시하기 위한 Label의 Style 속성이다. 특별히 Anti-alias와 같은 표현을 하기 위해 숫자를 Embed하였다.
Label에 적용시키기 위해 디지털 시계의 Class안에 styleChanged() 함수에서는 다음과 같이 코딩한다.


// time label의 스타일을 적용할 수 있는가 확인한 다음 적용한다.  
var styleName:String;
var styleDecl:CSSStyleDeclaration;
if( styleProp == "timeLabelStyleName" || anyStyle )
{
 styleName = getStyle("timeLabelStyleName");
 if( styleName )
 {
  //실제로 Style이 적용되어 있는지 확인한다.
  styleDecl = StyleManager.getStyleDeclaration("."+styleName);
  if( styleDecl )
  {
   timeLabel.styleName = styleName;
   timeLabelStyleChanged = true;
  }
 
 }
}


이렇게 하면 디지털 시계 컴포넌트에 들어가는 Label에도 Style을 적용시킬 수 있다. Label이라면 CSS에서 Type Selector인 Label로 직접 적용하면 되지 않겠냐고 생각할 수 있지만 만약 이 디지털 시계 컴포넌트뿐만 아니라 Label을 자식으로 사용하는 다른 컴포넌트와 함께 사용하게 되는 경우 그 Label에도 적용되므로 좋은 방법이 될 수 없다. 디지털 시계 컴포넌트의 Label에만 적용되어야 하는 Style이므로 이렇게 했다.


2.4  1차 제작 완료
의뢰자의 의견을 반영함과 동시에 확장성을 고려한 시계 컴포넌트를 완성시켰다. 동작화면은 아래와 같다. Flex 애플리케이션에 붙힌후 CSS를 컴파일한 SWF를 동적으로 로드해서 스킨을 적용할 수 있게 제작해 보았다.



동적으로 CSS를 적용할 수 있는 아날로그 시계 컴포넌트와 디지털 시계 컴포넌트


3. 시계 컴포넌트 제작 프로세스 2
추가 의뢰 사항이 들어왔다. 다른 형태로의 시계 컴포넌트와 위젯 형태의 시계가 만들어졌으면 좋겠다는 의뢰였다.


3.1 시계 컴포넌트 추가 제작 의뢰

의뢰 사항은 다음과 같다.


1. 벽시계와 디지털+아날로그 시계 컴포넌트 제작
2. 위젯 형태로의 확장


3.2 시계 컴포넌트 추가 의뢰 사항에 따른 제작 방향 선정
지금까지 만든 시계 컴포넌트는 확장성을 고려했으므로 벽시계, 디지털+아날로그 시계 컴포넌트를 만드는데 큰 제약이 안따른다. 위젯의 경우는 상황이 다른데, 기본적으로 애플리케이션을 확장해서 위젯의 크기에 맞게 조절될 수 있게 하고 자바스크립트를 이용해 시계의 시작, 종료, 시간대 조정, 스킨 종류 선택등이 가능하도록 만들어야 한다.


1. 벽시계는 기존 아날로그 시계 컴포넌트에서 추 스킨과 추에 대한 Mask 스킨을 추가하고 그에 대한 회전 Effect를 적용하도록 하고 Effect의 각 크기와 속도를 CSS에서 조절할 수 있도록 한다.
2. 아날로그+디지털 시계는 UI Component를 확장해서 createChildren()시에 자식으로 추가하여 배치시킨다. Background 스킨도 추가하도록 한다.
3. 시계 위젯은 애플리케이션에 시계 컴포넌트를 동적으로 삭제, 추가될 수 있도록 만들고 시계 종류, 시간대, 스킨, 시작과 종료 등을 자바스크립트를 통해 조절할 수 있도록 만든다. 시계 컴포넌트의 크기가 변화됨에 따라 이 위젯을 둘러싸는 html의 div 태그의 크기도 변경되어야 하므로 시계 컴포넌트의 Resize 이벤트를 받아 자바스크립트로 위젯의 크기를 시계 컴포넌트의 크기에 맞게 조절할 수 있도록 제작한다.


3.3 추가 제작
3.3.1 벽시계 제작

벽시계는 단순하게 이미 만든 아날로그 시계를 확장해서 스킨 2개만 추가하고 추의 동작만 정의하면 끝이다. 클래스 이름은 WallClock으로 하겠다. 기존과 조금 다른 점은 마스크가 들어간다는 점이다. 마스크는 추가 움직일 때 시계 내부에서 움직이므로 추가 보여지는 부분을 표현해주기 위해 사용한다. 아래 그림에서 마스크 영역은 네모난 창 영역이다. 스킨을 이용하는 것이므로 디자이너의 의도대로 마스크를 만들 수 있다는 점이 장점이겠다.



벽시계 컴포넌트


벽시계 스킨은 아래와 같이 구성된다. 아날로그 시계 컴포넌트와 다르게 마스크와 추 스킨이 있는것을 볼 수 있다. 물론 추 스킨인 경우 이름이 b_second로 지정되어 있는데 상관없다. CSS에서 최종적인 이름은 pendulumSkin이기 때문이다.



벽시계 컴포넌트의 스킨


3.3.2 아날로그+디지털 시계 컴포넌트 제작
아날로그+디지털 시계 컴포넌트는 어느 것 하나 더이상 확장해서 사용할 수 없다. 그렇다고 완전히 새로 만든다는 것은 효율적인 면에서 좋지 못하다. 가장 좋은 방법은 이미 만들어진 두개의 컴포넌트를 자식으로 추가하는 방법이다. 그리고 자식으로 추가된 두 컴포넌트의 Style을 적용시키기 위해 앞서 설명했던 디지털 시계 컴포넌트의 Label에 Class Selector를 이용하면 되겠다. 그리고 Layout는 Background스킨의 크기에 따라 크기가 조정되며 CSS에 아날로그, 디지털 시계 컴포넌트의 위치를 지정할 수 있겠다.



아날로그+디지털 시계 컴포넌트의 Background 스킨



아날로그+디지털 시계 컴포넌트에 스킨을 적용해본 모습


3.3.3 시계 위젯 제작
시계 위젯을 제작하기 위해 알아야할 제반 지식은 ExternalInterface를 사용하는 방법이다. ExternalInterface는 Flash와 Javascrupt간 통신할 수 있도록 만들어진 클래스이다. Flex에서 Javascript함수를 호출하기 위해 call() 함수를 사용하고 Javascript에서 Flex의 함수를 호출 할 수 있도록 callback() 함수를 사용하면 되겠다.

더불어 여기서는 Application.application.parameters를 이용해 javascript의 함수의 이름을 Flex 내부에서 결정하지 않고 Javascript 자체에서 결정할 수 있도록 고려한다. 풀이하자면 Application.application.parameters은 실행된 Flex SWF 파일을 Embed할 때 파라미터로 flashvars 속성 값을 지정할 수 있다. 이 값에 className=AnalogClock&skinURL=css/AnalogClockStyle.swf 라고 지정하면 Application.application.parameters.className은 AnalogClock 문자열이고 Application.application.parameters.skinURL은 css/AnalogClockStyle.swf 가 된다. 이러한 식으로 자바스크립트에서 제공되는 함수의 이름도 flashvars 속성에 넘겨주면 Flex에서 Javascript 함수를 참고할 때, 해당 함수 이름으로 참고가 가능하겠다. 예를 들면 다음과 같다.


//start 함수를 Javascript에서 호출 할 수 있도록 
funcName = Application.application.parameters.startFunc;
if( funcName )
{
 ExternalInterface.addCallback( funcName, start_callback );
}  

//자바스크립트 함수를 Flex에서 호출
if( ExternalInterface.available )
{
 var resizeFunc:String = Application.application.parameters.resizeFunc;
 ExternalInterface.call( resizeFunc, this.width, this.height );
}


이처럼 Application.application.parameters와 ExternalInterface를 조합하면 좀더 Javascript 코드가 훨씬 수월해 질 수 있겠다.
Flex 응용 프로그램을 html 코드 안에 Embed하기 위해 Flex 자체 index.templete.html이 정의되어 있긴 하지만 생각보다 쓰기 불편하고 어떻게 보면 쓸데없는 코드가 많이 들어간다. 게다가 방금 언급했던 flashVars 속성을 표현하기 위해 name=flex&age=30&school=flexSchool 식으로 밖에 표현할 수 없다. 이러한 단점을 보완해서 만들어진 javascript용 flash embed 오픈 소스가 있는데 SWFObject라는 것이다. 이 프레임워크를 사용하면 아주 쉽게 flash를 embed 할 수 있다. 예제를 보자.


// Load clock
function loadClock( divId, swf, id, className, skinURL, timezoneOffset )
{
 clockDivId = divId;
 clockId = id;
 var so = new SWFObject(swf, id, "100%", "100%", "9", "#FF6600");
 clockObj = so;

 //Variables
 so.addVariable("className", className);
 so.addVariable("skinURL", skinURL);
 so.addVariable("timezoneOffset", timezoneOffset);

 //Functions : JS -> Flex
 so.addVariable("createClockFunc", "createClock");
 so.addVariable("setTimezoneOffsetFunc", "setTimezoneOffset");
 so.addVariable("loadSkinFunc", "loadSkin");
 so.addVariable("startFunc", "startClock");
 so.addVariable("stopFunc", "stopClock");
 
 //Functions : Flex -> JS
 so.addVariable("resizeFunc","resize");
 
 //Paramenters
 so.addParam("quality", "high");
 so.addParam("allowScriptAccess", "always");
 so.addParam("wmode", "transparent");
 so.write(divId);

 clockSWF =  getSWFName(id);
}


만약 이것을 사용하지 않는다면 다음과 같이 표현해야만 한다.


<object id="ClockWidget" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="100%" style="undefined">
<param name="movie" value="ClockWidget.swf" />
<param name="bgcolor" value="#FF6600" />
<param name="quality" value="high" />
<param name="allowScriptAccess" value="always" />
<param name="wmode" value="transparent"/>
<param name="flashvars" value="className=AnalogClock&skinURL=css/AnalogClockStyles01.swf&timezoneOffset=-540&createClockFunc=createClock&setTimezoneOffsetFunc=setTimezoneOffset&loadSkinFunc=loadSkin&startFunc=startClock&stopFunc=stopClock&resizeFunc=resize" />
</object>


다른 속성에 비해 flashvars 같은 경우 너무 복잡하고 사용하기 불편해 보인다.
아래 그림은 이를 이용해서 만들어본 시계 위젯이다. 붉은색 테두리는 html의 div 태그 border이며 크기가 제대로 조절되고 있는지 확인하기 위한 부분이다. 즉, 시계 컴포넌트의 크기가 변함에 따라서 html의 div 크기도 함께 변하도록 해서 시계 위젯이 전체가 잘 보일 수 있도록 하는게 중요하겠다.



시계 위젯의 종류 및 스킨을 Javascript로 변환해 보는 화면


가장 위의 버튼은 각각의 시계 컴포넌트로 동적으로 바꿔주는 역할을 한다. 그 아래는 해당 스킨을 동적으로 로드하여 시계 컴포넌트에 적용시켜준다. 주의할 점은 해당 컴포넌트에 속한 스킨만 제대로 적용된다는 것이다 AnalogClock을 선택했는데 DigitalClock의 스킨은 제대로 적용되지 않는다는 것이다. Start, stop 버튼도 만들어 시계를 시작, 중지 시킬 수 있도록 했다. 내부적으로 시간대도 조정할 수 있도록 만들었는데 여기서는 생략되어 있다.


4. 마무리 하며
지금까지 시계 컴포넌트를 통해 Flex SDK에서 제공하지 않는 컴포넌트를 만드는 방법과 CSS를 동적으로 적용하는 예제, 그리고 위젯까지 짧지만 다양하게 언급하였다. 내용상 방대하기 때문에 많은 부분 함축해서 글을 썼다. 제일 좋은 방법은 이 글을 통해 전체적인 구성만 이해하고 아래 제공되는 소스를 분석해보는 것이 더욱 좋을 것이다.
구성은 1개의 라이브러리 프로젝트와 2개의 Flex 프로젝트로 구성되어 있다. Flex Workspace 추가해서 테스트 해보길 바란다. 테스트시 주의할 사항은 css 파일은 반드시 Compile CSS to SWF 가 Check 되어 있어야 한다는 점이다. 그리고 외부 서버에 CSS대신 그것을 컴파일한 SWF를 올리길 바라며 보안정책을 따르므로 crossdomain.xml을 설정하길 바란다.



ClockLib 구성


참고로 개발환경은 Flex Builder 3 Beta 2이다.

프로그램 소스


문서





저작자 : 지돌스타 (http://blog.jidolstar.com/333)  

크리에이티브 커먼즈 라이선스
Creative Commons License

Adobe Flash Platform , , , , , ,

Trackback 주소: http://blog.jidolstar.com/trackback/333
  1. 2009/01/24 00:50
    Alprazolam. Tracked from History alprazolam.
  1. Blog Icon

    비밀댓글 입니다

  2. 안녕하세요
    블로그를 옮기면서 링크가 깨졌습니다.
    지금 다시 연결했으니 참고하시고
    지적 감사해요.
    블로그 보시다가 이런 부분 있으면 댓글 부탁드릴께요.

  3. Blog Icon
    tact83

    다시 연결했다고 되어 있는데 안받아지네요 ㅠ_ㅠ