Tween은 속도가 관건입니다. 많은 오브젝트의 위치나 값을 어떻게 하면 빠르게 변경할 수 있을까에 대한 해결책을 제시하기 위해 그간 많은 Tween관련 라이브러리들이 쏟아져 나왔습니다. 지금까지 가장 빠르고 안정적으로 평가받는 TweenMax를 자주 사용했죠.

몇주전 속도적인 면에서 TweenMax를 뛰어넘은 BetweenAS3를 hika님으로부터 소개받았었죠.
BetweenAS3는 일본의 Spark 프로젝트 팀의  Yoshihiro Shindo라는 사람이 만들었습니다.

아래 링크는 BetweenAS3의 공식 홈페이지입니다.

위 링크로부터 소스를 다운로드 하거나 SVN으로 공유할 수 있고 각종 예시도 볼 수 있어서 실무에 언제든지 적용할 수 있을 정도입니다. Alpha버전이지만 거의 완벽해보이는군요.

도데체 얼마나 빠를까요? 다음 링크에서 테스트 해보세요. 속도만 비교해볼때 BetweenAS3가 압승입니다.

무슨 마술(?)을 부렸나 봅니다. ㅎㅎ
역시 일본 Spark 프로젝트 팀는 최고네요.

글쓴이 : 지돌스타(http://blog.jidolstar.com/587)

 

Adobe Flash, Flex, AIR 에 관련된 강좌들을 모아봤습니다.
공부하는데 큰 도움이 될 것이라 생각합니다.

 

http://opencast.naver.com/FL188/9

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/583)

한글 에러 : ArgumentError: Error #2180: AVM1 내용(AS1 또는 AS2)이 AVM2(AS3) 내용으로 로드된 경우 AVM1 내용을 displayLis의 다른 부분으로 이동할 수 없습니다."

 

영문 에러 : ArgumentError: Error #2180: It is illegal to move AVM1 content (AS1 or AS2) to a different part of the displayList when it has been loaded into AVM2 (AS3) content.


위와 같은 에러를 본 적이 있는가?

 

이것은 Flash Player 9 버전으로 만들어진 애플리케이션에서는 발생하지 않는다. Flash Player 10 버전으로 만들어진 애플리케이션에서 위와 같은 문제가 발생한다.

 

위 에러가 발생하는 조건은 다음과 같다.

 

  1. AVM2(ActionScript Virtual Machine 2)기반으로 만들어진 Flash 애플리케이션
    AVM2 기반이라는 것은 ActionScript 3.0 기반으로 만들어진 애플리케이션을 의미한다.
  2. Flash Player 10 기반으로 컴파일한다.
    컴파일 옵션에서 Flash Player 버전을 9.0.124 등이 아닌 10.0.0을 기반으로 한다.
  3. 위 두 조건하에서 AVM1기반으로 만들어진 SWF파일을 flash.display.Loader를 이용해 로드한다.
    AVM1 기반이라는 것은 ActionScript 1.0 또는 ActionScript 2.0 기반으로 만든 SWF파일을 의미한다.
  4. Loader.contentLoaderInfo.content를 addChild() 한다.
    Loader는 외부의 이미지, 플래시 파일등을 로드해서 디스플레이 객체로 등록하는 일종의 wrapper역할을 담당한다. 이들을 로드완료하면 Loader.contentLoaderInfo.content에 해당 객체의 참조를 얻을 수 있다. 일반적으로 화면에 출력하기 위해 addChild( Loader ) 식으로 하면 되는데 이렇게 안하고 addChild( Loader.contentLoaderInfo.content ) 를 하는 경우다. 이것이 가능한 이유는 Loader.contentLoaderInfo.content 도 DisplayObject이기 때문이다.

 

위 조건에 맞춰서 ActionScript 3.0 기반으로 코딩을 아래와 같이 해보겠다.

 

package
{
	import flash.display.DisplayObject;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.net.URLRequest;
	
	public class AVM1LoadTest extends Sprite
	{
		private var loader:Loader;
		
		public function AVM1LoadTest()
		{
			loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE,onLoadComplete);
			loader.load( new URLRequest("avm1asset.swf") );	
		}
		
		private function onLoadComplete( event:Event ):void
		{
			var loaderInfo:LoaderInfo = event.target as LoaderInfo;
			trace( loader.numChildren );
			trace( loaderInfo.content.parent );
			var avm1asset:DisplayObject = loaderInfo.content;
			addChild( avm1asset );
			trace( loader.numChildren );
			trace( loaderInfo.content.parent );
		}
	}
}

 

 

위 프로그램을 Flash Player 9 버전으로 컴파일하고 실행해보자. (실행되는 Flash Player 버전은 10이어야 한다. 당연히 실험 애플리케이션이 Flash Player 9버전 뿐 아니라 10버전도 만들 것이기 때문이다.)

 

아래 화면과 같이 AVM1 기반 SWF를 예쁘게 출력해준다.

 

 

onLoadComplete() 안을 살펴보면 addChild() 앞뒤로 똑같은 trace()문이 있다. 이것은 addChild() 전후로 loader.numChildren과 loaderInfo.content.parent(이것은 loader.content.parent와 같다)의 변화를 살펴보기 위함이다. loader.numChildren은 Loader 객체에 로드된 AVM1객체가 자식으로 등록되어 있는 경우에는 1이 출력되고 그 반대는 0이 된다. loaderInfo.content.parent는 로드된 AVM1객체의 부모가 무엇인지 가리킨다.

 

결과는 다음과 같다.

 

1
[object Loader]
0
[object AVM1LoadTest]

 

무엇을 의미하는가? addChild()를 하기 전에는 AVM1객체는 Loader의 자식이지만 addChild() 후로는 AVM1LoadTest객체의 자식이 된다. AVM1LoadTest는 위 프로그램의 메인 클래스이다. 이것은 당연한 결과로 자식은 두개 이상의 부모를 가질 수 없는 것을 의미한다. Flash Player 9버전으로 만든 경우에는 이러한 코딩이 가능했다.

 

조건을 바꿔보자. 이제 위 프로그램을 Flash Player 10 버전으로 컴파일하고 실행해보자. 언급했던 에러가 발생한다.

 

"ArgumentError: Error #2180: AVM1 내용(AS1 또는 AS2)이 AVM2(AS3) 내용으로 로드된 경우 AVM1 내용을 displayLis의 다른 부분으로 이동할 수 없습니다."

 

무엇을 의미하는가? Flash Player 10 기반으로 만들어진 애플리케이션은 AVM1 객체를 AVM2 기반의 디스플레이 객체로의 이동을 불허한다. 즉 Loader에서 떠나지 못함을 의미한다.

 

Flash Player 10기반에서 AVM2 SWF를 로드한 경우는 addChild( loader.contentLoaderInfo.content ) 가 가능할까? 이것은 된다!

 

Flash Player 10에서 AVM1기반의 SWF는 부모로 Loader이어야만 하는 이유는 무엇일까? 사실 여기에 대한 답변을 나는 아직 찾지 못했다. 혹시 아시는 분은 댓글 및 트랙백을 부탁한다.

 

일본에서는 이미 이 문제로 고민한 사람이 꽤 있었다. 재미있게도 AVM1을 AVM2로 강제로 바이트 단위로 버전을 수정해 버려 로드해버리는 커스텀 Loader까지 만들어 배포하는 사람이 있었다. 좋은 방법인지는 모르겠지만 아무튼 이런 연구가 활발히 진행되는 일본이 부럽당.. ㅡㅡ

 

Illustrator의 벡터형식 데이터를 읽어들이는 방법(Loader 클래스편)

Illustrator의 벡터형식 데이터를 읽어들이는 방법(Embed 태그 편)

BeInteractive - AVM2에서 AVM1의 SWF를 억지로 로드하는 ForcibleLoader 

BeInteractive - AVM2에서 AVM2의 SWF를 억지로 로드하는 방식 해설

ForcibleLoader 다운로드

ForcibleLoader 브라우징하기

 

흑... 이것도 spark 프로젝트....


아래 내용도 보면 좋을거다.

로드한 SWF 내부에 작성된 ActionScript 3.0 클래스 이름 찾기

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/582)

 

 

Flash/Flex 개발에 있어서 데이타와 UI의 의존성을 줄여주어 장기적으로 유지보수 및 효율적 관리를 하기 위해 MVC 디자인 패턴이 기본적으로 적용된 프레임워크를 직접 제작하거나 무료로 공개된 것들을 사용할 수 있다. 여기서 소개하는 프레임워크들은 Flex, Flash 개발하는데 MVC를 적절하게 활용해볼 수 있는 것들이다. 유용한 정보가 되길 바라며 관련된 많은 글들이 올라왔으면 한다.

 

http://opencast.naver.com/FL188/7

Flash가 Cross OS, Cross Browser를 지향하지만 한글문제를 비롯해 각종 몇가지 기능을 제대로 수행하지 못하는 경우가 있다. 마우스 휠(Mouse Wheel)도 그와 같은 맥락이다.

 

Mac 운영체제에서는 마우스 휠 기능이 전혀 먹지 않는다. MS Window에서 wmode가 transparent일때 Internet Explorer를 제외하고 다른 브라우저에서는 마우스 휠 기능을 사용할 수 없다.

 

이 문제를 해결할 수 있는 유일한 방법은 Javascript를 이용하는 방법이다. ActionScript3의 ExternalInterface를 이용해 Javascript와 통신해서 마우스 휠 이벤트를 사용하는 것이다. 자바스크립트를 이용하는 방법은 내 블로그에도 몇번 글을 올렸다.

 

ActionScript 3.0 만으로 쿠키를 제어 - Actionscript Cookie Util 소개

[Flex/Flash] ActionScript 3.0 으로 브라우저 종류 알아내기

 

Pixelbreaker 블로그에 Mac에서 마우스 휠을 해결하는 방법을 다룬 유명한 글이 올라와 있다.

 

AS3.0 MouseWheel on Mac OS

 

하지만 이 방법은 Mac에만 유용하고 wmode=transparent 일때 대처를 하지 못한다. 또한 js파일을 따로 html에 포함 해야하니 사용하기 귀찮다.

 

위 소스를 개선하여 어떤 경우에라도 마우스 휠을 사용할 수 있으면서 따로 js파일을 포함안하고 actionscript 코드만으로 해결한 소스가 있다. 일본 Spark 프로젝트 팀에서 만든 SWFWheel이라는 하나의 클래스 코드인데 사용하기도 쉽고 깔끔하다.

 

공식홈페이지 : http://www.libspark.org/wiki/SWFWheel 

 

아래에서 공개한 JS파일을 AS 3.0 코드에 포함한다. 아래 링크를 보면 쉽게 이해할 수 있겠다.

swfwheel.js : http://www.libspark.org/browser/as3/SWFWheel/trunk/zoo/swfwheel.js

SWFWheel.as : http://www.libspark.org/browser/as3/SWFWheel/trunk/src/org/libspark/ui/SWFWheel.as

 

다운로드는 아래 링크에서 AS파일만 다운받아 사용하면 되겠다.

http://www.libspark.org/svn/as3/SWFWheel/trunk/src/org/libspark/ui/

 

 

사용법은 너무 간단하다.

import org.libspark.ui.SWFWheel;
SWFWheel.initialize(stage);

 

위처럼 하고 ActionScript 를 통해 마우스 휠 이벤트를 stage로 부터 등록하여 사용한다.

stage.addEventListener( MouseEvent.MOUSE_WHEEL, mouseWheelHandler );

SWFObject를 이용해 아래와 같은 방법으로 Flash Content를 삽입한다.

var flashvars = {};
var params = {};
var attributes = {
    id: "myDynamicContent",
    name: "myDynamicContent"
};

swfobject.embedSWF("myContent.swf", "myContent", "300", "120", "9.0.0", "expressInstall.swf", flashvars, params, attributes);

 

단, 위 방법이 잘될 수 있도록 하기 위해 allowScripAccess는 같은 도메인의 swf인 경우 sameDomain, 다른 도메인의 swf인 경우 always로 지정해야한다. allowNetworking은 항상 all로 설정해야한다. 이에 대한 자세한 내용은 다음글을 참고한다.

 

위젯도 마음대로 못다는 네이버 블로그

 

 

SWFWheel 클래스는 browserScroll 속성이 있다. 이것을 이용해면 Flash 위에서 MouseWheel을 이용할 때 Flash를 담은 브라우저의 스크롤링을 허용할 것인가 설정할 수 있다. 기본값은 false이다.

 

네이버 오픈캐스트의 메인 프로그램이 Flash로 만들어져 있는데 마우스 휠 기능이 어떤 운영체제든 브라우져든 잘 동작한다. 아마도 이 SWFWheel을 사용하지 않았나 생각된다.

 

이렇게 꽁수 안부리고 마우스 휠이든 한글문제든 Flash Player가 문제 없이 잘 동작하면 얼마나 좋을까?

 

[생각더하기]일본의 Spark 프로젝트의 이름에 눈길이 간다. Flex 4의 새로운 컴포넌트는 Spark로 명명했다. 그럼 Flex 4가 만들어질때 일본의 Spark 프로젝트의 역할이 컷던 것 아닐까? 실제로 Spark 프로젝트는 일본내에서 매우 활발하고 유명한 Flash 관련 프로젝트이다. 그 유명한 증강현실(FLARToolKit)도 이 프로젝트에서 만들어진 것이다. 우리 나라에는 왜 이런 멋진 프로젝트 팀이 없는 것일까? 안타깝다.

 

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/577)

 

 

 

 

 

단지 25줄의 ActionScript 코드로 얼마나 멋진 애플리케이션을 만드는가 가리는 대회이다. 웹서핑하다가 우연치 않게 본 페이지에서 25줄이라고는 믿기지 않을 정도의 결과물들을 보고 이 세상에는 정말 천재들이 많구나라는 생각이 들었다.

 

대회 규정 및 이전 대회 결과물들은 아래 홈페이지를 통해 볼 수 있다.

 

http://www.25lines.com

 

아래에서 소개하고 있는 ActionScript 결과물들은 2009년 1월 결승전에 올라온 작품들이다. 작품 하나하나마다 25줄의 코드라고는 믿기지 않는 작품들임을 볼 수 있을 것이다. 만든것도 대단하지만 창의적인 면에 있어서도 높은 점수를 주고 싶다.

 

올라온 소스 코드를 분석해보려고 읽기 쉽게 정리하면서 봐도 이해못하는... ㅜㅜ

언젠간.. 나도 저 대열에....

 

Entry 001
Code
SWF

Entry 002
Code
SWF

Entry 007
Code
SWF

Entry 008
Code
SWF

Entry 009
Code
SWF

Entry 014
Code
SWF

Entry 021
Code
SWF

Entry 023
Code
SWF

Entry 027
Code
SWF

Entry 029
Code
SWF

Entry 030
Code
SWF

Entry 032
Code
SWF

 

글쓴이 : 지돌스타(http://blog.jidolstar.com)

 

Flex Explorer만 선별하여 네이버 오픈케스트에 발행했습니다. Flex, AIR 개발자라면 꼭 들어가서 볼 필요있는 것만 나름대로 엄선했으니 참고 바랍니다. Flex를 처음 공부하는 사람에게는 더욱 필요한 정보가 될 것입니다.  현재 Flex 4 Beta가 나와 있는 상태이지만 Flex 2, Flex 3에 대한 Explorer로 포함합니다. 이는 Flex SDK 버전에 상관없이 유용할 것이 판단했기 때문에 넣었습니다.

 

그럼 즐 Flex/AIR 하세요.

 

http://opencast.naver.com/FL188/4

 

제 오픈케스트는 1주일에 한번씩 발행됩니다.

마음에 드신다면 꼭 구독도 하세요 ^^

 

이외에 아래 링크들도 유용합니다.

 

 

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/571)

 

Flex 4에 대해서 자료를 모으고 공부하는 중에 매우 괜찮은 블로그를 찾았다.

http://www.hulstkamp.com/

이 사람 블로그에 가면 Flex 4기반 커스텀 컴포넌트를 만든 예제들이 정말 많다. 한동안 이 사람 블로그와 친해져야 겠다는 생각이 들었다. ㅎㅎ

올라온 자료중에 Gumbo(4.0.0.4932) 버전으로 만든 Knob Button 예제가 있었는데 Flex 4 SDK Beta버전이 정식으로 나오기 전이라 바로 실행할 수 없었다. 그래서 소스를 보면서 마이그레이션 작업을 해보았다. 아래 실행 예제는 바로 이 작업의 결과물이다.

위 프로그램은 Knob Button에 대한 데모이다. 마우스로 돌려볼 수 있다.


마이그레이션 하면서 Spark 기반 커스텀 컴포넌트를 제작하는 법에 더욱 익숙해질 수 있었다.

Flex 3의 Halo 기반 컴포넌트는 스킨이 그래픽 기반만 지원했는데 Flex 4의 Spark기반 컴포넌트들은 스킨이 그래픽적 요소 뿐 아니라, 상태변화(state), 다른 컴포넌트 배치, 데이타 표현등이 된다. 또한 FXG를 지원하고 MXML형태로 스킨을 만들 수 있기 때문에 그 확장력이 무궁무진하게 되었다. Halo 컴포넌트의 경우에는 상태변화 바꾸거나 내부 컴포넌트 배치만 달라져도 컴포넌트 자체를 확장해서 다시 만들어야 하는 불편함이 있었지만 Spark컴포넌트는 큰 변경없이 커스텀 스킨만 제작하는 것만으로도 충분히 해결할 수 있게 되었다. 이 내용은 여기에 올려놓은 소스 또는 "Spark DropDownList 사용하기"를 보면 알 수 있을 것이다.

Spark 컨테이너의 경우에는 Layout까지 동적으로 변경할 수 있도록 만들어져서 언제든지 다른 Layout으로 바꿀 수 있고 또는 커스텀 Layout를 만들어 쓸 수도 있다. 반면 Halo 기반 컨테이너는 컨테이너의 Layout을 바꾸기 위해 기존 컨테이너를 다시 확장하거나 새로 만들어야만 했다. 이 내용에 대해서는 "Spark 컨테이너의 Layout, Scrolling, Viewport 소개"를 읽어보길 바란다.

CSS는 정말 획기적으로 많이 추가 되었다. 기존 Class, Type Selector밖에 없었는데 Flex 4로 넘어오면서 ID, Descendant, Pseudo, Mutiple Class등 다양한 Selector가 추가되었다. 이에 대한 글은 "Flex 4의 CSS"를 참고하길 바란다.

아래는 위 프로그램의 소스이다. 참고바란다.


개발 환경은 Flash Builder 4 Beta 1 이다. Flash Builder는 아래 링크에서 다운 받을 수 있다.

Flex 4는 Flex 3의 컨테이너의 Scrolling 기능보다 더욱 강화되고 직관적으로 바뀌었다. 여기에 Layout, Viewport라는 개념까지 포함되어 더욱 다루기 편하도록 만들어졌다. 이 글은 Flex 4 Spark 컨테이너의 Layout, Scrolling, Viewport에 대해서 소개한다.

아래는 Flash Builder 4 환경에서 작업했다. Flash Builder 4는 아래 링크를 통해 다운받을 수 있다.

http://www.adoberia.co.kr/pds/down.html?src=text&kw=000026  

Spark 컨테이너의 Layout

 

Flex 3에서 Halo 기반 컨테이너는 Canvas, Box, HBox, VBox, Tile등이 있었지만 Flex 4의 Spark 기반 컨테이너는 모두 Group 하나로 통일되었고 layout 속성을 통해 BasicLayout, HorizontalLayout, VerticalLayout, TileLayout을 설정하여 Flex 3의 Canvas, HBox, VBox, Tile역할을 할 수 있도록 했다. Flex 4 Spark 기반 컨테이너는 이와 같이 동적으로 Layout을 바꿀 수 있도록 만들어졌기 때문에 좀 더 유연한 컨테이너 환경을 만들 수 있다.

 

아래는 위 프로그램의 소스이다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo" 
	minWidth="300" minHeight="300">
	<s:layout>
		<s:VerticalLayout/>
	</s:layout>
	<fx:Script>
		<![CDATA[
			import spark.layouts.TileLayout;
			import spark.layouts.VerticalLayout;
			import spark.layouts.HorizontalLayout;
		]]>
	</fx:Script>
	
	<s:DropDownList id="ddl" requiresSelection="true" labelField="label">
		<s:dataProvider>
			<s:ArrayList>
				<fx:Object label="TileLayout" layout="{new TileLayout()}"/>
				<fx:Object label="HorizontalLayout" layout="{new HorizontalLayout()}"/>
				<fx:Object label="VerticalLayout" layout="{new VerticalLayout()}"/>
			</s:ArrayList>
		</s:dataProvider>
	</s:DropDownList>
		
	<s:Group>
		<s:layout>{ddl.selectedItem.layout}</s:layout>
		<s:Button label="1"/>
		<s:Button label="2"/>
		<s:Button label="3"/>
		<s:Button label="4"/>
		<s:Button label="5"/>
		<s:Button label="6"/>
		<s:Button label="7"/>
		<s:Button label="8"/>
		<s:Button label="9"/>
		<s:Button label="10"/>
	</s:Group>

</s:Application>

 

Spark 컨테이너의 Scrolling

 

Flex 3의 모든 컨테이너는 Scroller기능이 내장되어 있었는데 반해 Flex 4 Spark 기반 컨테이너는 기본적으로 Scroller 기능이 없다. 그래서 컨테이너는 더욱 가벼워졌고 필요할 때 ScrollBar를 Group 태그 밖으로 감싸주기만 하는 것으로 스크롤을 추가할 수 있도록 되었다.

 

 

아래는 앞선 소스에서 Scroller만 추가한 것이다.

<s:Scroller width="200" height="100" left="1" right="1" top="1" bottom="1">
	<s:Group>
		<s:layout>{ddl.selectedItem.layout}</s:layout>
		<s:Button label="1"/>
		<s:Button label="2"/>
		<s:Button label="3"/>
		<s:Button label="4"/>
		<s:Button label="5"/>
		<s:Button label="6"/>
		<s:Button label="7"/>
		<s:Button label="8"/>
		<s:Button label="9"/>
		<s:Button label="10"/>
	</s:Group>
</s:Scroller>

 

Flex 4 Spark 컨테이너(List, Group등)는 이렇게 쉽게 Layout을 임의대로 바꿀 수 있고 Scroller도 쉽게 배치할 수 있도록 되어 있다. Flex 4 Spark 기반 컨테이너는 Flex 3의 Halo 컨테이너보다 더욱 사용하기 간편하고 직관적이며 활용도가 높다는 것을 해본 사람은 알 수 있을 것이다.

 

 

Spark 컨테이너의 Viewports

 

Spark 컨테이너에서 들어간 재미있으면서 유용한 개념이 들어갔는데 Viewport 라는 것이다. Viewport는 말그대로 보여지는 영역이다. 아래 그림만 봐도 Viewport의 의미를 쉽게 이해할 수 있을 것이다.

 

출처 : http://hansmuller-flex.blogspot.com/2009/06/introduction-to-viewports-and-scrolling.html

 

방금 설명했던 Scroller는 시각적으로 보이는 horizontalScrollbar, verticalScrollbar와 ViewPort의 horizontalScrollPosition, verticalScrollPosition 속성의 값을 묶어(Bind)준다. 그러므로 어느 한쪽이 조절되면 다른 한쪽이 변경되어지게 된다. 백문의 불여일타.... 아래 예제만 봐도 이게 무슨 말인지 바로 알게 된다.

 

 

<?xml version="1.0" encoding="utf-8"?>
<!-- 출처 :http://hansmuller-flex.blogspot.com/2009/06/introduction-to-viewports-and-scrolling.html-->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009" 
    xmlns:s="library://ns.adobe.com/flex/spark" 
    xmlns:mx="library://ns.adobe.com/flex/halo">
    <s:Group>
        <s:Group left="20" top="1" width="110" height="160">
            <s:Scroller id="scrollbar" left="2" right="2" top="2" bottom="2">
                <s:Group id="vp" horizontalScrollPosition="57" verticalScrollPosition="198">
                    <mx:Image source="http://sites.google.com/site/hansmuller/Home/archive/gyro-original.jpg"/>
                </s:Group>
            </s:Scroller>
            <s:Rect left="0" right="0" top="0" bottom="0">
                <s:stroke>
                    <s:SolidColorStroke weight="1" color="0xD8D8D8"/>
                </s:stroke>
            </s:Rect>
        </s:Group>
        <s:SimpleText horizontalCenter="10" top="175" text="viewport.width = {vp.width}"/>
        <s:SimpleText verticalCenter="-10" rotation="-90" text="viewport.height = {vp.height}"/>                
    </s:Group>

    <s:VGroup left="140" top="10" gap="15">
        <s:SimpleText text="viewport.contentWidth = {vp.contentWidth}"/>
        <s:SimpleText text="viewport.contentHeight = {vp.contentHeight}"/>
        <s:SimpleText text="viewport.horizontalScrollPosition = {vp.horizontalScrollPosition}"/>
        <s:SimpleText text="viewport.verticalScrollPosition = {vp.verticalScrollPosition}"/>
    </s:VGroup>

</s:Application>

 

Spark 컨테이너인 Group은 GroupBase를 상속받고 GroupBase는 IViewport 인터페이스를 구현하고 있다. Scroller는 이 IViewport과 바인딩되어 있다는 것을 이해하는 것이 중요하다.

 

    public interface IViewport extends IVisualElement
    {
        function get width():Number;
        function get height():Number;
        function get contentWidth():Number;
        function get contentHeight():Number;
        function get horizontalScrollPosition():Number;
        function set horizontalScrollPosition(value:Number):void;
        function get verticalScrollPosition():Number;
        function set verticalScrollPosition(value:Number):void;
        function getHorizontalScrollPositionDelta(scrollUnit:uint):Number;
        function getVerticalScrollPositionDelta(scrollUnit:uint):Number;
        function get clipAndEnableScrolling():Boolean;
        function set clipAndEnableScrolling(value:Boolean):void;
    }

 

그러므로 IViewport 인터페이스를 구현한 GroupBase, Group, DataGroup, RichEditableText, SkinnableContainer, SkinnableDataContainer 등은 모두 Scroller를 사용할 수 있게 되는 것이다.

 

위 소스에서 사용자가 Scroller를 컨트롤 하지 않고 Viewport를 컨트롤하여 스크롤바를 움직이게 할 수 있다.

 

위 프로그램은 앞선 소스에서 아래 코드를 추가하면 된다

<s:VGroup top="196" gap="15" x="20">
	<s:HGroup>
		<s:SimpleText text="HScroll Controller"/>    		
		<s:HSlider id="hSlider" 
			liveDragging="true"
			minimum="0" 
			maximum="{vp.contentWidth-vp.width}" 
			value="{vp.horizontalScrollPosition}" 
			change="vp.horizontalScrollPosition=hSlider.value"/>
	</s:HGroup>

	<s:HGroup>
		<s:SimpleText text="VScroll Controller"/>    		
		<s:HSlider id="vSlider" 
			liveDragging="true"
			minimum="0" 
			maximum="{vp.contentHeight-vp.height}" 
			value="{vp.verticalScrollPosition}" 
			change="vp.verticalScrollPosition=vSlider.value"/>
	</s:HGroup>
</s:VGroup>

 

이 코드가 의미하는 바는 사용자가 ScrollBar를 직접 컨트롤하지 않아도 Viewport를 통해 ScrollBar를 프로그램적으로도 컨트롤 할 수 있다는 것을 보여주고 있다.

 

(위 프로그램에서 Slider에 Viewport의 위치 값이 초반에 제대로 업데이트 안되는 이유는 이미지 로딩시점과 관련되어 있다. 이미지 로딩 뒤에 Viewport의 위치를 변경하면 아마도 문제가 해결될 것이므로 참고바란다. )

 

 

참고내용

Spark Layouts with Flex 4 Beta

Introduction to Viewports and Scrolling in Gumbo

 

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/568)

Flex 4의 Spark 계열 DropDownList는 List를 확장한 컴포넌트이다. Flex 3에서 ComboBox와 유사하게 사용할 수 있다.

 

DropDownList는 DropDownBase대신 List를 서브클래스로 한 것이 커다란 변화이다. 이렇게 된데에는 Iwo Banas라는 사람이 기존 Gumbo의 FxComboBox을 더욱 확장이 가능하고 내부적으로 독립화된 구조의 필요성에 대한 제안을 반영한 결과이다. 이에 따라 DropDownList는 dropDown 스킨 부분을 더이상 가지지 않고 List의 dataGroup 스킨을 사용하게 되었다.

 

DropDownList 컴포넌트의 기능 및 디자인 스펙을 보고 싶다면 아래 글을 보기 바란다. 이 글만 잘봐도 DropDownList에 내부구현 및 사용방법에 대해 어느정도 이해할 수 있을 것이라 생각한다.

 

http://opensource.adobe.com/wiki/display/flexsdk/Spark+DropDownList

 

또한 API에 대한 자세한 내용은 아래 글을 참고한다.

http://livedocs.adobe.com/flex/gumbo/langref/spark/components/DropDownList.html

 

DropDownList를 사용하는 방법은 Peter deHaan의 블로그만 봐도 쉽게 학습할 수 있다.

나는 이 블로그에서 DropDownList를 언급한 글들만 골라서 사용해 보았다. Flex 3와 달라 약간 생소하지만 놀라운 스킨 적용의 확장성에 매력을 느끼게 될 것이다. 아래에서 소개하는 4개의 큰 제목을 누르면 해당 포스트로 이동한다.

 

Setting a content background color on a Spark DropDownList control in Flex Gumbo

 

이 예제는 DropDownList를 이용하는 매우 간단한 예제로 DropDownList의 DropDown되는 리스트의 배경색을 바꾸는 것이다.

 

 

Setting the symbol color on the Spark DropDownList control in Flex 4

 

 

이 예제도 그 전 예제와 비슷한 난이도의 예제로 DropDownList의 Symbol의 색을 변경하는 예제이다.

 

 

Creating a tile layout Spark DropDownList control in Flex Gumbo

 

 

이 예제는 DropDownList의 커스텀 스킨을 적용하는 방법을 보여주고 있다. 블로그에 있는 커스텀 스킨은 Gumbo버전이라 작동을 잘 안하는데 스킨은 아래 것을 받아 해보길 바란다. Peter deHaan의 예제에서 PopUp을 사용했는데 Flex 4부터는 PopUpAnchor로 바뀌었다.

 

List에 보여지는 아이템이 원래 DropDownList의 기본 Skin인 spark.skins.DropDownListSkin과 달라진 점은 PopupAnchor의 DataGroup를 VerticalLayout으로 지정하는데 여기서 사용한 Layout은 HorizontalLayout이라는 점이 다르다. 게다가 위치도 아래표시가 아니라 우측에 표시되도록 했다. 이처럼 스킨부분에서 모든 Layout을 수정할 수 있도록 한 것이 Flex 4 컴포넌트의 컨셉이다. 이것은 Flex 3의 단점을 잘 극복한 부분중에 하나이다.

 

조금 더 재미있게 바꿔보자. Application과 DropDownSkin을 아래 코드로 바꿔보자.

 

 

스킨만 바꿨을 뿐인데...(갑자기 어느 광고가 생각남.. ㅋ)

위 프로그램 처럼 DropDownList의 스킨을 바꿀 수 있다. 그것도 아주 쉽게~~

 

 

Displaying images in a Spark DropDownList control in Flex Gumbo

 

방금 예제에서 DropDownList의 스킨을 바꾸는 것을 보여주었다. 그럼 DropDownList에 이미지도 보여줄 수 있을까? 당근 아주아주 쉽게 할 수 있다.

 

 

여기에서 사용된 BitmapImage는  spark.primitives.* 패키지에 포함되는 것으로 Group을 확장한 Graphic내에 넣을 수 있다. Graphic에 넣을 수 있는 것중에는 <Rect>, <Path>, <Ellipse>등도 포함한다. 관련 내용은 아래 링크를 참고한다.

 

http://livedocs.adobe.com/flex/gumbo/langref/spark/primitives/package-detail.html

 

 

정리하며

지금까지 Flex 4의 Spark DropDownList에 대해서 살펴보았다. Flex 3와 달리 Flex 4에서는 스킨 적용시 ActionScript 3.0 기반이 아닌 MXML이라 직관적이고 편리하게 할 수 있다는 것이 큰 매력이였다. 또한 기능도 매우 확대되었다. Spark 컴포넌트라면 이와 같은 방법으로 스킨을 적용할 수 있다는 것을 이해하는 것이 중요하겠다.


테스트 환경 : Flash Builder 4 + Flex SDK 4

Flash Builder 는 아래 링크에서 다운 받자.

http://www.adoberia.co.kr/pds/down.html?src=text&kw=000026 

글쓴이 : 지돌스타(http://blog.jidolstar.com/567)

Flex Builder 3 및 Flash Builder 4 로 개발하면서 도움말 참조는 흔히 있는 일이다. Blueprint는 이들 IDE에 plug-in 형태로 제공되며 단축키(Win:Alt+B, Mac:Control+B)만으로 쉽게 도움말을 찾을 수 있게 지원해준다.

 

 

라이브 독을 찾아다닐 필요없이 단축키만으로 쉽게 설명을 찾을 수 있으니 이 얼마나 편한가? ^^

 

다음 글을 통해 Blueprint를 경험해 보길 바란다.

 

Blueprint 설치하기 : http://labs.adobe.com/wiki/index.php/Blueprint:Installation_Instructions

Blueprint 사용하기 : http://labs.adobe.com/wiki/index.php/Blueprint:Using_Blueprint

 

Blueprint 공식 페이지 : http://labs.adobe.com/technologies/blueprint/

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/565)

 

Flex의 SystemManager는 Flex가 구동될 때 Application이 동작하기전 각종 설정을 하면서 사용자들에게 충분히 그 시간을 기다릴 수 있도록 UI적으로 Preloading 화면을 보여준다. 하지만 Flex가 아닌 ActionScript 3.0 프로젝트로 만들면 이런 UI를 보여주지 않는다. Flex는 되는데 ActionScript라고 못할까? 사실 매우 쉬운 방법으로 이 기능을 추가할 수 있다. 방법은 다음 글을 참고하길 바란다.

 

http://www.diebuster.com/?p=681

http://www.bit-101.com/blog/?p=946

 

ActionScript 3.0도 Flex 컴파일러인 mxmlc로 컴파일하는 것이기 때문에 [Frame] 메타 데이타 태그를 이용해 Preloading 기능을 추가할 수 있는 것이 핵심이다. 이를 이용해 만들어진 Preloading 기능 추가한 클래스에 각종 설정 및 자원관리를 할 수 있는 로직을 만들어 사용하면 좋겠다.

 

한가지 팁을 소개하자면....

 

mxmlc가 Flex 컴파일러라서 내부적으로 [Frame] 메타 데이타 태그를 사용하면 기본 CSS를 포함하게 된다. 그래서 위 글대로 하면 다음과 같은 경고 문구가 나온다.

 

Default css file not found

 

이 경고 문구를 없애려면 내용이 아무 것도 없는 null.css를 하나 만들고 컴파일 옵션으로 -defaults-css-url null.css를 추가하면 경고문구가 사라진다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com)

 

네이버 오픈케스트에 "Adobe ColdFusion에 대해 알아봐요."라는 제목으로 발행했습니다.

 

 

국내에서는 이상하게도 ColdFusion에 대해 많이 무관심한 편입니다. 알고 보면 정말 멋진 RIA 기술인데도 말이죠. 개발은 쉽게하고 유지보수도 편하게 하는 것을 좋아하시는 분들에게 ColdFusion은 큰 매력으로 자리 잡을 수 있을 것이라 생각합니다. 물론 개인 뿐 아니라 기업 입장에서도 효율적인 개발 및 유지보수 차원에서 도입한다면 좋을 것이라 생각합니다.

 

아래 링크에 접속하시면 보실 수 있습니다.

 

http://opencast.naver.com/FL188/3

 

한번 ColdFusion의 매력에 푹~ 빠져보시기 바랍니다.

Adobe에서 TLF(Text Layout Framework)의 소스를 공개했다.

 

먼저 어떤 것인지 데모 버전을 보자.

아래 링크로 보면 되겠다.

 

http://labs.adobe.com/technologies/textlayout/fma/Shell.swf

 

TLF는 Flash Player 10, AIR 1.5 기반의 새로운 Text 엔진이다. Adobe Flash CS4 및 Flex 4 에서 사용할 수 있다. Flash API에서 제공하는 TextField의 부족한 부분을 개선하고 일반 워드 처럼 Text 편집 작업을 할 수 있도록 하는 것이 이 프레임워크의 목적이 아닌가 싶다.

 

Adobe Labs에 다운로드 페이지에서 직접 받을 수 있다. 소스는 최신 Flex 4 SDK에 포함되어 있다고 한다. Night Build 버전으로 다운로드 받을 수 있고 [여기]를 참조한다.

 

TLF의 Beta 1 버전은 다운로드 받으면 CS4, Flex 3, Flex 4 환경에서 개발할 수 있고 꼭 Flash Player 10 버전으로 컴파일 옵션으로 지정해야한다. 그리고 3개의 SWC파일인 textLayout_conversion.swc, textLayout_core.swc, textLayout_edit.swc 를 라이브러리로 포함하면 되겠다.

 

관련 페이지는 다음 링크를 참고한다.

 

- Text Layout Framework(Adobe Open Source)

- Text Layout Framework(Adobe Labs)

 

다음 TLF 기반 예제들도 참고할만 하겠다.

- AIR기반의 Times Reader 2.0 http://timesreader.nytimes.com/timesreader/
- Acrobat.com Presentations http://labs.adobe.com/technologies/presentations/
- makebook http://www.makebook.com/

 

하지만 아직 한국에서 사용하기에는 무리이다. 한글입력을 할 때, 자음, 모음이 먼저 보여지지 않는 것도 있고 기능적으로도 부족한 점이 많다. Open Source라지만 근본적인 한글 입력문제는 Flash Player 10 자체에서 해결해 주어야 할 듯 싶다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/561)

 

코드네임 Strobe였던 OSMF(Open Source Media Framework)가 2009년 7월 21일 기점으로 오픈 소스로 공개되었다. 현재 버전은 0.3 2.0(2013-12-17)이다.

 

OSMF는 3개 부분의 구조로 나뉜다. User Interface, Monetization workflows, media delivery가 그것이다.

 

OSMF는 최적의 Media Player를 개발할 수 있는 프레임워크로 플레이어 개발의 복잡성을 줄어준다.

재생컨트롤, 비디오 네비게이션, 버퍼링, 다이나믹 스트리밍, 리포트 분석과 같은 플러그 인 설치등을 지원한다. 무엇보다 이 모든 기능이 오픈소스화 되어 어느 개발자든지 만들어진 소스를 공유하고 수정하며 다시 배포할 수 있게 되었다. 정확한 라이센스는 MPL에 따른다.

 

공식사이트는 아래 링크를 참고한다.

http://osmf.org/


아래는 OSMF 블로그이다. 

http://blogs.adobe.com/osmf/ 


다운로드는 아래 링크를 참고한다.

http://sourceforge.net/projects/osmf.adobe/files/


빠르게 익숙해지려면 아래 링크 동영상을 보도록 하자.

http://www.gotoandlearn.com/play.php?id=129 

http://tv.adobe.com/show/rich-media-player-development-with-osmf/ 


아래 링크에서 OSMF Examples를 볼 수 있다.

http://mediapm.edgesuite.net/osmf/swf/ExamplePlayer.swf  


Flash Builder, Flash CS3, CS4 환경이면 OSMF API로 개발이 가능하겠다.

본인도 OSMF를 다운로드 받아서 예제 소스를 가지고 Flash Builder 4 에서 만들어 보았다. Flex 소스가 Flex 3라서 Flash Builder 4에서는 SDK를 3.4 버전으로 맞춰줘야 한다.


참고: 아래소스는 0.3버전 시절겁니다. 그냥 참고만 하세요. (2013-12-17)

 

 

CompositionPlayer.zip

 

위 결과물은 OSMF 배포된 소스 안에 있는 샘플로 만든 것이다. UI는 형편없지만 다양한 방법로 Media 컨텐츠를 읽어와 재생하고 볼륨조정, Pan 조정도 가능하다는 것을 보여주고 있다.

 

아래 프로그램은 순수하게 ActionScript 3.0 프로젝트로만 만들어 본것이다.

 

FlashMediaPlayerTest.zip

 

필요하다면 OSMF를 프로젝트에 복사해서 사용해도 된다.

 

아니면 아래 처럼 라이브러리 형태로 배포된 SWC를 자신의 프로젝트의 libs에 추가해서 사용해도 된다.

 

예제만 잘 보면 활용방법도 쉽게 얻을 수 있다. ^^

이제 Media 관련 플레이어를 제작하는데 많은 노력없이 OSMF만 이용해도 충분히 만들 수 있게 되었다. 너무 멋지다 이런 멋진 플레이어의 소스를 볼 수 있다는 것이 말이다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/560)

Flex 4로 넘어오면서 생소하게 느꼈던 부분 중에 하나가 바로 자식(Child) 관리 메소드들의 변화가 아닌가 생각한다. 바뀐 부분이 무엇이고 왜 바뀌었는지 알아보도록 하겠다.

 

Flex 3에서 Canvas, Box 등의 컨테이너는 모두 mx.core.Container 클래스를 확장해서 만들어졌다. 이 Container 클래스는 flash.display.DisplayObjectContainer에서 정의되어 있는 자식관리 메소드인 numChildren, addChild(), addChildAt(), removeChild(), removeChildAt() 등을 그대로 Override(재정의) 해서 사용했다. 그런데 이렇게 재정의 한것이 문제가 컸다. Flex 3의 Container는 DisplayObjectContainer의 메소드를 그대로 사용하므로 자식은 모두 flash.display.DisplayObject로 받게 된다. 하지만 실제 자식으로 사용할 수 있는 것은 IUIComponent를 구현한 클래스만 자식으로 받을 수 있다. 즉 UIComponent만 자식으로 받을 수 있는 것이다. 아래 코드는 Flex 3의 Container에서 자식을 추가할 때 내부적으로 addingChild()메소드가 호출되는데 거기에 있는 코드이다.

var uiChild:IUIComponent = IUIComponent(child);

 

Flex 3에서 addChild()를 통해 받는 자식은 DisplayObject인데 런타임시 사용될 수 있는 컴포넌트는 IUIComponent이니 실제 Flex 3를 이용해서 개발하는 사람들은 오용할 소지가 크게 된 것이다. 나도 Container에 Sprite 객체를 넣으려고 하다가 안되서 애먹은 적이 있었는데 바로 이것때문이다.

 

Flex 4부터는 이 문제를 말끔히 없애고자 DisplayObjectContainer의 자식 관리 메소드를 재정의 하지 않고 다른 메소드를 만들었다. 그 다른 메소드는 무엇일까?

 

Flex 4에서는 아래와 같이 IVisualElementContainer 인터페이스를 정의했다. 이 인터페이스는 Flex 4의 모든 Spark, Halo 컨테이너에서 모두 구현하고 있다. 보면 알겠지만 addChild대신 addElement, removeChild대신 removeElement로 된 것이다. 결국 Child 대신 Element를 사용한거다.

package mx.core
{
public interface IVisualElementContainer
{
    function get numElements():int;
    function getElementAt(index:int):IVisualElement
    function addElement(element:IVisualElement):IVisualElement;
    function addElementAt(element:IVisualElement, index:int):IVisualElement;
    function removeElement(element:IVisualElement):IVisualElement;
    function removeElementAt(index:int):IVisualElement;
    function removeAllElements():void;
    function getElementIndex(element:IVisualElement):int;
    function setElementIndex(element:IVisualElement, index:int):void;
    function swapElements(element1:IVisualElement, element2:IVisualElement):void;
    function swapElementsAt(index1:int, index2:int):void;

}
}

 

위 인터페이스는 정확히 무엇을 의미하는가? 자식 추가할 때 이제 더이상 DisplayObject가 아니라는 것에 주목하자. DisplayObject대신 IVisibleElement를 관리하고 있다. 이것이 핵심이다. 즉 이제 런타임시가 아닌 컴파일 단계서부터 원천적으로 컨테이너에 자식들은 모두 IVisibleElement을 구현한 것만 다루겠다는 것이다. Flex 4는 Flex 3와 다르게 모든 컨테이너는 IVisibleElementContainer를 구현한 것이다. 그리고 mx.core.UIComponent는 IVisibleElement를 구현했다. 이로서 컨터이너에 붙는 자식들을 더욱 명확하게 사용할 수 있게 되었다.

 

그럼 addChild, addChildAt은 어떻게 했을까? 아래 코드는 Spark 컨테이너의 SkinnableComponent와 GroupBase에 재정의된 것이다. 즉 이들 메소드를 사용하면 런타임시 예외처리를 해준다. 이 클래스를 확장해서 만든 Spark 계열의 컨테이너인 SkinnableContainer, SkinnableDataContainer, Group, DataGroup, Panel 전부 이렇게 동작한다.

    /**
     *  @private
     */
    override public function addChild(child:DisplayObject):DisplayObject
    {
        throw(new Error(resourceManager.getString("components", "addChildError")));
    }
    
    /**
     *  @private
     */
    override public function addChildAt(child:DisplayObject, index:int):DisplayObject
    {
        throw(new Error(resourceManager.getString("components", "addChildAtError")));
    }

 

하지만 기존 Halo 기반의 컨테이너(mx.core.Container)는 addChild(), removeChild() 사용시 예외처리를 해두지 않았다. 물론 IVisualElementContainer는 구현했지만 말이다. 이는 기존 Flex 3 Halo 기반의 컴포넌트와의 호환성 문제 때문에 그리 한 것이라 판단한다.

 

Flex 4 문서를 보면 되도록이면 Halo보다 Spark 기반의 컴포넌트와 컨테이너를 사용할 것을 권장하고 있다. 지금까지 소개한 내용도 그렇게 권장하는 이유중에 하나일 것이다.

 

Flex SDK 개발자들이 Flex 2 시절 전부터 이런 문제를 알고 있었을 텐데 왜 지금에서야 바꿨는지... ㅎㅎㅎ

 

 

참고글

Spark Group - Functional and Design Specification

 

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/559)

 

 

InsideRIA에 올라온 Getting started with Spark skins 의 글을 읽고 따라해보았다.

 

아래는 Skin 소스이다. com.jidolstar.skins.MyButtonSkin으로 이름을 지었다.

<?xml version="1.0" encoding="utf-8"?>
<s:Skin 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo" 
	>
	<fx:Metadata>
		[HostComponent("spark.components.Button")]
	</fx:Metadata>
	
	<s:states>
		<mx:State name="up"/>
		<mx:State name="down"/>
		<mx:State name="over"/>
		<mx:State name="disabled"/>
	</s:states>
	
	<s:filters>
		<s:DropShadowFilter quality="3"
			alpha="0.5" alpha.over="0.3"
			distance="5" distance.over="15"
			blurX.over="15" blurY.over="15"
			inner.down="true"
		/>
	</s:filters>
	
	<s:Rect left="0" right="0" top="0" bottom="0" radiusX="5" radiusY="5">
		<s:fill>
			<mx:LinearGradient rotation="90">
				<mx:entries>
					<mx:GradientEntry color="#0a5c00" ratio="0.00"/>
					<mx:GradientEntry color="#84a381" ratio="0.50" ratio.over="0.25"/>
					<mx:GradientEntry color="#0a5c00" ratio="0.80"/>
				</mx:entries>
			</mx:LinearGradient>
		</s:fill>
	</s:Rect>
	
	<s:SimpleText id="labelElement" color="#ffffff"
		horizontalCenter="0" verticalCenter="0"
     	left="10" right="10" top="5" bottom="5"
     />	
	
	<s:layout>
		<s:BasicLayout/>
	</s:layout>
</s:Skin>

 

아래는 위 Skin을 사용한 소스이다.

<?xml version="1.0" encoding="utf-8"?>
<s:Application 
	xmlns:fx="http://ns.adobe.com/mxml/2009" 
	xmlns:s="library://ns.adobe.com/flex/spark" 
	xmlns:mx="library://ns.adobe.com/flex/halo" 
	minWidth="500" minHeight="400"
	>
	<s:Button label="확인" skinClass="com.jidolstar.skins.MyButtonSkin"/>
</s:Application>

 

코딩하면서 느끼는 것이지만 Flex 4는 Flex 3하고 너무 다르다. ㅎㅎ  

스킨 부분에 있어서 Flex 3에서는 ProgrammaticSkin을 기반으로 Skin을 ActionScript 코드로 만들었는데 Flex 4부터는 MXML로 만들 수 있도록 했다. 이 부분이 가장 크게 달라진거다.

 

위의 Skin 코드를 살펴보면 몇가지 재미있는 점을 발견할 수 있다. 살펴보자.

 

먼저 HostComponent 메타 데이타를 볼 수 있다. Flex 4에서 나온 새로운 메타 데이타이다. 이것은 이 스킨이 어떤 컴포넌트가 Host인가 명시적으로 지칭해주는 역할을 한다. Button은 ButtonBase를 확장해서 만들었고 ButtonBase는 SkinnableComponent를 확장해서 만들어졌는데 HostComponent가 쓰이는 부분은 SkinnableComponent인 듯 보인다. 아래는 SkinnableComponent내에 정의된 attachSkin() 메소드이다. 이것은 createChildren() 혹은 commitProperties()가 호출될때 이 메소드가 호출되는데 skinClass 스타일이 바뀔 때 Skin을 붙여주는 역할을 담당한다.

 

    /**
     *  Create the skin for the component. 
     *  You do not call this method directly. 
     *  Flex calls it automatically when it calls createChildren() or  
     *  the UIComponent.commitProperties() method.
     *  Typically, a subclass of SkinnableComponent does not override this method.
     * 
     *  

This method instantiates the skin for the component, * adds the skin as a child of the component, and * resolves all part associations for the skin

* * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ protected function attachSkin():void { // Factory var skinClassFactory:IFactory = getStyle("skinFactory") as IFactory; if (skinClassFactory) setSkin( skinClassFactory.newInstance() as Skin ); // Class if (!skin) { var skinClass:Class = getStyle("skinClass") as Class; if (skinClass) setSkin( new skinClass() ); } if (skin) { skin.owner = this; // As a convenience if someone has declared hostComponent // we assign a reference to ourselves. If the hostComponent // property exists as a direct result of utilizing [HostComponent] // metadata it will be strongly typed. We need to do more work // here and only assign if the type exactly matches our component // type. if ("hostComponent" in skin) { try { Object(skin).hostComponent = this; } catch (err:Error) {} } // the skin's styles should be the same as the components skin.styleName = this; super.addChild(skin); skin.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, skin_propertyChangeHandler); } else { throw(new Error(resourceManager.getString("components", "skinNotFound", [this]))); } findSkinParts(); invalidateSkinState(); }

위 코드에 보면 hostComponent 부분이 보이는데 바로 HostComponent 메타데이터와 관련이 있어 보인다. 아직 정확한 동작방식은 모르겠다.

 

주제를 바꿔서 맨 위 코드에 <s:state>가 존재하는 것을 볼 수 있다. 이것은 상태를 의미하는 것으로 이미 Button 자체에 up, down, over, up 상태가 메타 데이터 형태로 정의 되어 있다. . ButtonBase에 보면 아래와 같은 방식으로 4개의 상태를 나타내는 메타 데이터가 정의되어 있다.

/**
 *  Up State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("up")]

/**
 *  Over State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("over")]

/**
 *  Down State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("down")]

/**
 *  Disabled State of the Button
 *  
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 1.5
 *  @productversion Flex 4
 */
[SkinState("disabled")]

public class ButtonBase extends SkinnableComponent implements IFocusManagerComponent
{
   ...
}

 

저 상태변화는 SkinnableComponent 클래스에서 알아서 처리하도록 되어 있다. Button의 스킨의 상태에 따른 변화의 동작방식을 이해하기 위해 SkinnableComponent를 분석해야한다. 뭐, 분석까지 아니더라도 SkinnableComponent를 활용하는 방법만 알면 될 것이다. SkinnableComponent는 UIComponent를 확장한 것이고 모든 Spark 계열의 컴포넌트는 SkinnableComponent를 확장한 형태이다. ColorPicker나 DateChooser와 같이 예전 Halo 계열의 컴포넌트는 SkinnableComponent를 확장하지 않았다.

 

<s:filters>는 말그대로 필터를 설정하는 것을 의미한다. 여기서는 DropShadowFilter를 사용했다. Flex 4에서 추가한 새로운 기능중에 각 속성에 state를 지정할 수 있다. DropShadowFilter를 사용할 때도 아래 처럼 속성에 이 기능을 적용했다.  

 

alpha="0.5" alpha.over="0.3"

 

DropShadowFilter를 사용한 부분 중에 alpha값을 위처럼 사용했다. 보통때는 alpha값이 0.5이지만 상태가 over이면 0.3을 사용하겠다는 것을 의미한다. alpha.over="0.3" 부분이 어떻게 ActionScript 로 바뀌는가 살펴보려면 컴파일 옵션에 -keep-generated-actionscript를 해보면 알 수 있다. 그럼 컴파일을 하면 src/generated 폴더가 생성된다. 그 안에 com.jidolstar.skins 패키지로 가보면 MyButtonSkin-generated.as 파일이 있는데 열어보면 alpha.over="0.3" 가 어떻게 바뀌는가 한눈에 알 수 있다. 아래 코드는 MyButtonSkin-generated.as의 일부분이다.

 

    /**
     * @private
     **/
    public function MyButtonSkin()
    {
        super();
        // our style settings

        // properties
        this.filters = [_MyButtonSkin_DropShadowFilter1_i()];
        this.layout = _MyButtonSkin_BasicLayout1_c();
        this.mxmlContent = [_MyButtonSkin_Rect1_c(), _MyButtonSkin_SimpleText1_i()];
        this.currentState = "up";


        // events
		states = [
		  new State ({
		    name: "up",
		    overrides: [
		    ]
		  })
		  ,
		  new State ({
		    name: "down",
		    overrides: [
		      new mx.states.SetProperty().initializeFromObject({
		        target: "_MyButtonSkin_DropShadowFilter1",
		        name: "inner",
		        value: true
		      })
		    ]
		  })
		  ,
		  new State ({
		    name: "over",
		    overrides: [
		      new mx.states.SetProperty().initializeFromObject({
		        target: "_MyButtonSkin_DropShadowFilter1",
		        name: "alpha",
		        value: 0.3
		      })
		      ,
		      new mx.states.SetProperty().initializeFromObject({
		        target: "_MyButtonSkin_DropShadowFilter1",
		        name: "distance",
		        value: 15
		      })
		      ,
		      new mx.states.SetProperty().initializeFromObject({
		        target: "_MyButtonSkin_DropShadowFilter1",
		        name: "blurX",
		        value: 15
		      })
		      ,
		      new mx.states.SetProperty().initializeFromObject({
		        target: "_MyButtonSkin_DropShadowFilter1",
		        name: "blurY",
		        value: 15
		      })
		      ,
		      new mx.states.SetProperty().initializeFromObject({
		        target: "_MyButtonSkin_GradientEntry2",
		        name: "ratio",
		        value: 0.25
		      })
		    ]
		  })
		  ,
		  new State ({
		    name: "disabled",
		    overrides: [
		    ]
		  })
		];
    }

private function _MyButtonSkin_DropShadowFilter1_i() : spark.filters.DropShadowFilter
{
	var temp : spark.filters.DropShadowFilter = new spark.filters.DropShadowFilter();
	temp.quality = 3;
	temp.alpha = 0.5;
	temp.distance = 5;
	_MyButtonSkin_DropShadowFilter1 = temp;
	return temp;
}

 

결국 alpha.over="0.3"는 State 객체를 만들어 각 상태에 따른 속성으로 지정해버리도록 만들어진다. currentState를 "up"으로 설정하고 상태가 바뀔 때마다 등록된 State를 실행하도록 되는 것이다. 이걸 보니깐 원리가 확실히 이해가 된다.

 

다시 돌아가서....

<s:Rect>과 <s:SimpleText>는 mxmlContent=[_MyButtonSkin_Rect1_c(), _MyButtonSkin_SimpleText1_i()] 의 형태로 추가 된다. mxmlContent도 Flex 4부터 추가된 속성인데 생소하기 짝이 없다. ㅎㅎ 좀 알아보기 위해 또 뒤져봤다.

 

//----------------------------------
//  mxmlContent
//----------------------------------

private var mxmlContentChanged:Boolean = false;
private var _mxmlContent:Array;

[ArrayElementType("mx.core.IVisualElement")]

/**
 *  The visual content children for this Group.
 *
 *  

The content items should only be IVisualItem objectss. * An mxmlContent Array should not be shared between multiple * Group containers because visual elements can only live in one container * at a time.

* *

If the content is an Array, do not modify the Array * directly. Use the methods defined by Group class instead.

* * @default null * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ public function get mxmlContent():Array { if (_mxmlContent) return _mxmlContent.concat(); else return null; } /** * @private */ public function set mxmlContent(value:Array):void { if (createChildrenCalled) { setMXMLContent(value); } else { mxmlContentChanged = true; _mxmlContent = value; // we will validate this in createChildren(); } } /** * @private * Adds the elements in mxmlContent to the Group. * Flex calls this method automatically; you do not call it directly. * * @langversion 3.0 * @playerversion Flash 10 * @playerversion AIR 1.5 * @productversion Flex 4 */ private function setMXMLContent(value:Array):void { var i:int; // if there's old content and it's different than what // we're trying to set it to, then let's remove all the old // elements first. if (_mxmlContent != null && _mxmlContent != value) { for (i = _mxmlContent.length - 1; i >= 0; i--) { elementRemoved(_mxmlContent[i], i); } } _mxmlContent = (value) ? value.concat() : null; // defensive copy if (_mxmlContent != null) { var n:int = _mxmlContent.length; for (i = 0; i < n; i++) { var elt:IVisualElement = _mxmlContent[i]; // A common mistake is to bind the viewport property of an Scroller // to a group that was defined in the MXML file with a different parent if (elt.parent && (elt.parent != this)) throw new Error(resourceManager.getString("components", "mxmlElementNoMultipleParents", [elt])); elementAdded(elt, i); } } }

 

위 코드는 Group 클래스이다. mxmlContent는 Skin 클래스의 부모 클래스인 Group에 구현되어 있는 속성인 것이다. mxmlContent에 값을 설정하면 setMXMLContent() 함수가 호출되어 기존의 MXML 추가된 Element들을 지우고 새로운 Element인 Rect과 SimpleText를 추가한다. 또한 IVisibelElement인 컴포넌트만 자식으로 추가하도록 되어 있고 다른 부모를 가지는 컴포넌트인 경우에 예외처리도 한다. 결국 mxmlContent은 Group에서 사용된 MXML형태의 비주얼 컴포넌트를 Flex 컴파일러가 AS3로 추가해주기 위해 만들어진 것이다.

 

<s:Application> 부분에 버튼 추가한 부분은 어떻게 변해 있을까? 살펴보면 아래 처럼 되어 있다.

private function _sparkskin_Button1_c() : spark.components.Button
{
	var temp : spark.components.Button = new spark.components.Button();
	temp.label = "확인";
	temp.setStyle("skinClass", com.jidolstar.skins.MyButtonSkin);
	if (!temp.document) temp.document = this;
	return temp;
}

skinClass가 원래 Style속성이다. SkinnableComponent를 보면 [Style(name="skinClass", type="Class")]로 정의 되어 있다. 이는 validateSkinChange() 함수에서 붙여주게 된다.

 

/**
 *  @private
 */
private function validateSkinChange():void
{
    // If our new skin Class happens to match our existing skin Class there is no
    // reason to fully unload then reload our skin.  
    var skipReload:Boolean = false;
    
    if (_skin)
    {
        var factory:Object = getStyle("skinFactory");
        var newSkinClass:Class;
        
        if (factory)
            newSkinClass = (factory is ClassFactory) ? ClassFactory(factory).generator : null;
        else
            newSkinClass = getStyle("skinClass");
            
        skipReload = newSkinClass && 
            getQualifiedClassName(newSkinClass) == getQualifiedClassName(_skin);
    }
    
    if (!skipReload)
    {
        if (skin)
            detachSkin();
        attachSkin();
    }
}

 

내가 한가지 흥미를 느꼈던 것은 바로 <s:SimpleText> 부분이였다. Flex 4의 Button은 Label의 컴포넌트를 바꿀 수 없었는데 Flex 4에서는 스킨 기능을 이용해 Text엔진을 바꿀 수 있다. 가령 <s:SimpleText> 대신 <s:RichText>로 바꿔보길 바란다. 잘 된다. 이는 TextGraphicElement를 확장한 SimpleText와 RichText만 가능하다. 만약 TextArea와 같은 것을 사용하면 아래와 같은 에러가 나오게 된다.

 

TypeError: Error #1034: 유형 강제 변환에 실패했습니다. spark.components::TextArea@70c36d1을(를) spark.primitives.supportClasses.TextGraphicElement(으)로 변환할 수 없습니다.

 

이런 식의 사용은 Spark 컴포넌트 전체에서 사용할 수 있다. 참 좋은 방법이다.

 

 

생각하기

 

Flex 4는 Flex 3 처럼 무리하게 ActionScript 3.0까지 분석할 필요까지 느끼게 해주지 않도록 하는 것이 컨셉인 것 같다. 하지만 내가 좀 독특한 건가 예전 버릇을 버리지 못해 어찌 돌아가는지 알고 싶어서 Flex 4 SDK를 훑어보게 되었다. 하지만 이렇게 저렇게 분석하면서 살펴봤지만 아직도 모르는게 산적해있다. 먼저 사용하는 법부터 좀 공부해야겠다. ㅎㅎ

 

관련글

Getting started with Spark skins

Creating Spark Skins

The Spark Group and Spark SkinnableContainer containers

 

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/558)

 

 

2번째로 네이버 오픈케스트에 Adobe RIA 추천사이트를 올렸습니다. 제가 자주 가고 또 유용하다고 생각되는 사이트만 10개 엄선했습니다. 네이버 메인에서 구독할 수 있습니다. 1주일에 한번씩 업데이트합니다. 많은 구독 부탁드려요 ^^

 

지돌스타의 Adobe RIA 오픈 캐스트 보기 : http://opencast.naver.com/FL188

 

서기님의 블로그를 보다가 너무 멋진 그림을 봐서 따왔다.

 

Life Cycle of the Flex UIComponent Base Class

 

그림의 출처 : http://danorlando.com/?p=122

 

Flex의 모든 비주얼 컴포넌트는 Sprite를 확장한 UIComponent를 기반으로 한다. 생성할 대 호출되는 함수와 발생되는 이벤트에 대해서 하나의 그림으로 표현되어 있다. Flex 커스텀 컴포넌트를 작성해야할 때 Life Cycle을 이해하는 것은 반드시 필요하다. 그러므로 눈에다 팍팍 익혀두면 도움이 될 것이다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com)

 

 

 

일전에 Flex 4 CSS에 대한 글을 올렸습니다. Flex 4의 CSS는 Flex 3까지의 CSS의 한계를 극복하도록 만들어졌죠. 그런데 이 CSS가 기능이 확장된 계기가 있었습니다. 갑자기 궁금해지지 않나요? Flex 개발에 관련된 Adobe 개발자들이 머리를 짜서 하자고 했을까요? 아니면 어떤 요청이 있었을까요? 답은 Adobe Bug reporting 시스템에 있었습니다.

 

Adobe에서는 Adobe Bug Reporting 시스템을 운영하고 있습니다. 들어가보면 알겠지만 Flex, BlazeDS, Flash Player, ActionScript Compiler 주제로 개설되어 있고 개발자들의 요구사항 및 버그를 이곳에서 전부 받고 같은 요구사항이 있는 사람들이 투표하는 방식으로 진행되어 투표수가 많으면 Adobe에서 우선순위를 가려서 개발착수에 들어가지요.

Adobe Bug 리포팅 시스템 첫화면

 

 

Flex 4의 고급 CSS도 이런 과정을 통해 탄생된 겁니다.

 

https://bugs.adobe.com/jira/browse/SDK-14385

 

Flex CSS 지원해달라는 요구사항 페이지

 

Jacob Wright라는 사람이 리포팅을 했고 투표수가 75입니다. 그만큼 CSS의 기능이 더욱 확장되었으면 좋겠다는 사람의 수가 많은 거지요. 투표수가 많으니 Adobe 측에서도 무시할 수 없게 된 것이고 이번 Flex 4에서 지원하게 된 것입니다.

 

 

 

버그 리포팅에 참여하자.

 

그럼 우리도 참여할 수 있을까요?

때론 그럴겁니다 영어 못하기 때문에 참여 못한다고....

하지만 꼭 그렇지 않습니다. 자주 들어가 내용을 살펴보고 투표로써 작은 관심을 가져주는 것만으로도 충분합니다. 개발자중에서도 영어 잘하는 사람이 이런 글을 올립니다. 그럼 그 사람의 요청이 있으면 가서 투표해주면 되는 겁니다. 절대 어려운 일이 아닙니다. 아래에 버그 리포팅에서 추천(투표)의 중요성에 대해 알 수 있을 겁니다.

 

[열이아빠]플렉스 버그 리포팅에서 추천의 중요성

 

Adobe Flex/AIR 관련되어 한글문제가 꽤 있습니다. 이 한글문제를 해결하기 위해서도 이 버그 리포트 시스템을 이용하면 됩니다. 이미 Flash Platform 한글문제 공동대응팀이 생겼고 지속적으로 이 문제를 해결하기 위해 다양한 각도로 일하고 있습니다. 대응팀 총괄을 맡고 있는 이희덕씨 블로그에 관련 글이 많습니다.

 

[희희덕덕]한글문제 이슈 관련글

 

아래글은 열이아빠님이 쓰신 플렉스 버그 검색하는 방법입니다..

 

[열이아빠]플렉스 버그 검색해보기

 

투표에 참여해서 Flex/AIR/Flash 한글문제를 해결하는 방법입니다.

 

[희희덕덕]여러분의 참여로 한글 문제를 함께 해결해 봅시다!

 

 

앞으로 Flash Platform 한글문제 공동대응팀은 관련 자료를 종합하고 관리할 것입니다.

 

 

정리하며

 

여러분도 Adobe RIA 기술에 대한 불만 또는 원하는 사항들이 있을겁니다. 앞에서 설명드린데로 잘 정리해서 Adobe Bug Reporting 시스템에 올리시거나 투표를 적극적으로 참여함으로써 성취할 수 있는 겁니다. 한국 개발자들은 유독 영어 울렁증이 있다고들 호소하는데(저를 비롯) 사실 핑계라고 생각합니다. 필요하면 공부하면 되고요. 이런 일들은 꼭 영어를 잘해서 하는 일도 아니거든요. 한국 Adobe RIA 기술에 관련된 시장은 일본에 비해 거의 8배 정도 부족합니다.  그리고 적극성도 일본에 비해 많이 뒤쳐지며 나오는 컨텐츠의 창의력도 훨씬 뒤지는 편입니다. 왜 유독 일본어만 Flex Livedocs가 있을까요? Adobe는 시장성이 없는 한국에 한글문서 작성 인력을 투자하기에는 그들도 아깝다는 생각을 하는겁니다. 물론 Adobe는 그런 마음을 가지면 안되는 것이지만 또한 적극적이지 못한 우리도 반성해야될 일이라고 생각합니다. 우리가 안으로만 숨지않고 적극적으로 활동하면 Adobe에서 한국시장을 무시 못하게 될겁니다. 그런 날이 오길 반드시 바랍니다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/549)

Flex 3에서 DataGrid의 Header부분의 Separator는 headerSeparatorSkin 스타일로 정의되어 있다. 화면은 Flex DataGrid에 Separator가 붙은 보통의 모습이다.

 

 

위 프로그램은 아래와 같이 프로그래밍 한다.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
	<mx:DataGrid id="dataGrid">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

만약 Separator를 지우고 싶다면 단순히 headerSeparatorSkin 에 mx.skins.ProgrammaticSkin으로 정의하면 된다. 아래는 실행화면과 소스코드이다. mx.skins.ProgrammaticSkin는 프로그램적으로 스킨을 만들 필요가 있을때 사용하는 클래스로 이 클래스 내부에서는 어떤 렌더링도 하지 않기 때문에 headerSeparatorSkin 을 이 클래스로 대체하는 것만으로도 Header의 separator를 삭제할 수 있는 것이다.

 

 

 

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
	<mx:DataGrid id="dataGrid" 
		headerSeparatorSkin="mx.skins.ProgrammaticSkin">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

위 코드 대신 아래 코드처럼 <Style/> 블록이나 외부 .CSS 파일을 이용해서 headerSeparatorSkin의 스타일을 바꿀 수 있다.

 

 

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
    <mx:Style>
        DataGrid {
            headerSeparatorSkin: ClassReference("mx.skins.ProgrammaticSkin");
        }
    </mx:Style>	
	<mx:DataGrid id="dataGrid">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

또는 ActionScript 를 사용해 버튼을 누를때 동적으로 headerSeparatorSkin 스타일을 변경할 수 있도록 다음 코드처럼 만들 수 있다.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
    <mx:Script>
        <![CDATA[
            import mx.skins.ProgrammaticSkin;

            private function btn_click(evt:MouseEvent):void {
                dataGrid.setStyle("headerSeparatorSkin", ProgrammaticSkin);
            }
        ]]>
    </mx:Script>

    <mx:Button id="btn"
            label="Set header separator skin"
            click="btn_click(event);" />
            
	<mx:DataGrid id="dataGrid">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

좀더 파헤쳐보자.

 

DataGrid에는 drawSeparators() 메소드가 protected로 지정되어 있다. 만약 DataGrid를 커스터마이징해서 Separator 그리는 방식을 바꾸고 싶다면 drawSeparators() 부터 찾아가면 된다. 아래 코드는 DataGrid의 drawSeparators() 메소드 정의이다

/**
 *  Creates and displays the column header separators that the user 
 *  normally uses to resize columns.  This implementation uses
 *  the same Sprite as the lines and column backgrounds and adds
 *  instances of the headerSeparatorSkin and attaches mouse
 *  listeners to them in order to know when the user wants
 *  to resize a column.
 */
protected function drawSeparators():void
{
    DataGridHeader(header)._drawSeparators();
    if (lockedColumnHeader)
        DataGridHeader(lockedColumnHeader)._drawSeparators();
}

 

위 코드에서 볼 수 있듯이 결국 DataGridHeader에 정의된 _drawSeparators()쪽을 살펴봐야한다. 아래는 DataGridHeader의 _drawSeparators()와 drawSeparators() 부분이다.

mx_internal function _drawSeparators():void
{
    drawSeparators();
}

/**
 *  Creates and displays the column header separators that the user 
 *  normally uses to resize columns.  This implementation uses
 *  the same Sprite as the lines and column backgrounds and adds
 *  instances of the headerSeparatorSkin and attaches mouse
 *  listeners to them in order to know when the user wants
 *  to resize a column.
 */
protected function drawSeparators():void
{
    if (!separators)
        separators = [];

    var lines:UIComponent = UIComponent(getChildByName("lines"));
    
    if (!lines)
    {
        lines = new UIComponent();
        lines.name = "lines";
        addChild(lines); 
    }
    else
        setChildIndex(lines, numChildren - 1);

    // required to deal with some 2.x clipping behavior
    lines.scrollRect = new Rectangle(0, 0, unscaledWidth, unscaledHeight + 1);

    if (headerSepSkinChanged)
    {
        headerSepSkinChanged = false;
        clearSeparators();
    }

    var n:int = visibleColumns ? visibleColumns.length : 0;
    
    if (!needRightSeparator && n > 0)
    	n--;
    
    for (var i:int = 0; i < n; i++)
    {
        var sep:UIComponent;
        var sepSkin:IFlexDisplayObject;
        
        if (i < lines.numChildren)
        {
            sep = UIComponent(lines.getChildAt(i));
            sepSkin = IFlexDisplayObject(sep.getChildAt(0));
        }
        else
        {
            var headerSeparatorClass:Class =
                getStyle("headerSeparatorSkin");
            sepSkin = new headerSeparatorClass();
            if (sepSkin is ISimpleStyleClient)
                ISimpleStyleClient(sepSkin).styleName = this;
            sep = new UIComponent();
            sep.addChild(DisplayObject(sepSkin));
            lines.addChild(sep);
            
            separators.push(sep);
        }
        // if not separator
        if ( !(i == visibleColumns.length-1 && !needRightSeparatorEvents) )
        {
            DisplayObject(sep).addEventListener(
                MouseEvent.MOUSE_OVER, columnResizeMouseOverHandler);
            DisplayObject(sep).addEventListener(
                MouseEvent.MOUSE_OUT, columnResizeMouseOutHandler);
            DisplayObject(sep).addEventListener(
                MouseEvent.MOUSE_DOWN, columnResizeMouseDownHandler);
        }
		else
		{
            // if not separator
            if ( (i == visibleColumns.length-1 && !needRightSeparatorEvents) )
            {
                DisplayObject(sep).removeEventListener(
                    MouseEvent.MOUSE_OVER, columnResizeMouseOverHandler);
                DisplayObject(sep).removeEventListener(
                    MouseEvent.MOUSE_OUT, columnResizeMouseOutHandler);
                DisplayObject(sep).removeEventListener(
                    MouseEvent.MOUSE_DOWN, columnResizeMouseDownHandler);
            }
		}

        var cols:Array = visibleColumns;
        if (!(cols && cols.length > 0 || dataGrid.headerVisible))
        {
            sep.visible = false;
            continue;
        }

        sep.visible = true;
        sep.x = headerItems[i].x +
                visibleColumns[i].width - Math.round(sepSkin.measuredWidth / 2);
        if (i > 0)
        {
            sep.x = Math.max(sep.x,
                             separators[i - 1].x + Math.round(sepSkin.measuredWidth / 2));
        }
        sep.y = 0;
        sepSkin.setActualSize(sepSkin.measuredWidth, Math.ceil(cachedHeaderHeight));
        
        // Draw invisible background for separator affordance
        sep.graphics.clear();
        sep.graphics.beginFill(0xFFFFFF, 0);
        sep.graphics.drawRect(-separatorAffordance, 0,
							  sepSkin.measuredWidth + separatorAffordance,
							  cachedHeaderHeight);
        sep.graphics.endFill();
		sep.mouseEnabled = true;
    }

    while (lines.numChildren > n)
    {
        lines.removeChildAt(lines.numChildren - 1);
        separators.pop();
    }
}

 

DataGridHeader의 drawSeparators()에서 headerSeparatorSkin 스타일을 사용하는 것을 찾아볼 수 있다. 개발자는 이 부분을 커스터마이징할 수 있는 것이다.

 

DataGrid나 DataGridHeader를 커스터마이징할 필요없이 스킨만 변경하고 싶다면 headerBackgroundSkin의 기본 스킨인 mx.skins.halo.DataGridHeaderSeparator를 커스터 마이징하거나 mx.skins.Programmatics를 확장해서 사용하면 되겠다. 아래코드는 DataGrid에서 headerBackgroundSkin이 Style로 정의된 것을 보여주고 있다.

/**
 *  The class to use as the skin that defines the appearance of the  
 *  background of the column headers in a DataGrid control.
 *  @default mx.skins.halo.DataGridHeaderSeparator
 */
[Style(name="headerBackgroundSkin", type="Class", inherit="no")]

 

 

아래 코드는 mx.skins.halo.DataGridHeaderSeparator를 보여준다.

////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2005-2007 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package mx.skins.halo
{

import flash.display.Graphics;
import mx.skins.ProgrammaticSkin;

/**
 *  The skin for the separator between column headers in a DataGrid.
 */
public class DataGridHeaderSeparator extends ProgrammaticSkin
{
	include "../../core/Version.as";

	//--------------------------------------------------------------------------
	//
	//  Constructor
	//
	//--------------------------------------------------------------------------

	/**
	 *  Constructor.
	 */
	public function DataGridHeaderSeparator()
	{
		super();
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden properties
	//
	//--------------------------------------------------------------------------

	//----------------------------------
	//  measuredWidth
	//----------------------------------
	
	/**
	 *  @private
	 */
	override public function get measuredWidth():Number
	{
		return 2;
	}
	
	//----------------------------------
	//  measuredHeight
	//----------------------------------

	/**
	 *  @private
	 */
	override public function get measuredHeight():Number
	{
		return 10;
	}
	
	//--------------------------------------------------------------------------
	//
	//  Overridden methods
	//
	//--------------------------------------------------------------------------

	/**
	 *  @private
	 */
	override protected function updateDisplayList(w:Number, h:Number):void
	{
		super.updateDisplayList(w, h);
		var g:Graphics = graphics;
		
		g.clear();
		
		// Highlight
		g.lineStyle(1, 0xFFFFFF, 0.5);
		g.moveTo(0, 0);
		g.lineTo(0, h);
		g.lineStyle(1, getStyle("borderColor")); 
		g.moveTo(1, 0);
		g.lineTo(1, h);
	}

}

}

 

이외로 단순해서 놀랬는가? 결국 이 스킨은 borderColor 스타일로 지정된 색으로 구분자 선만 그어준다. 나는 이것을 바꿔서 실선을 점선을 그리도록 해보겠다. 아래 코드는 이를 구현한 DataGridHeaderDotSeparator 클래스이다.

 

package
{
import flash.display.Graphics;

import mx.skins.halo.DataGridHeaderSeparator;

/**
 * DataGrid Header에 점선을 그린다.
 */ 
public class DataGridHeaderDotSeparator extends DataGridHeaderSeparator
{
	/**
	 *  Constructor.
	 */		
	public function DataGridHeaderDotSeparator()
	{
		super();
	}
	
	/**
	 *  @private
	 */		
	override protected function updateDisplayList(w:Number, h:Number):void
	{
		var g:Graphics = graphics;
		
		g.clear();
		
		
		g.lineStyle(1, getStyle("borderColor"), 1); 
		var i:int;
		for( i = 0; i < h; i+=4 )
		{
			g.drawCircle( 1.5, i, 0.5 );
		}
	}		
}
}

 

 

아래 코드는 DataGridHeaderDotSeparator 스킨을 사용하는 예제이다.

<?xml version="1.0" encoding="utf-8"?>
<!-- http://blog.flexexamples.com/2009/03/20/removing-the-header-separator-on-the-datagrid-control-in-flex/ -->
<mx:Application
	name="DataGrid_headerSeparatorSkin_test"
	xmlns:mx="http://www.adobe.com/2006/mxml"
	backgroundColor="white" 
	layout="vertical">
	<mx:DataGrid id="dataGrid"
		headerSeparatorSkin="DataGridHeaderDotSeparator">
		<mx:dataProvider>
			<mx:ArrayCollection>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
				<mx:Object c1="1. One" c2="1. Two" c3="1. Three"/>
			</mx:ArrayCollection>
		</mx:dataProvider>
	</mx:DataGrid>
</mx:Application>

 

아래는 위 코드를 실행한 화면이다. Header부분에 구분자(Separator)가 점선으로 되었다. 좀 뭉뚝하지만 그냥 예제일 뿐이니깐 넘어가자. ^^;

 

 

Flex는 이처럼 프로그램적으로 스킨을 변경할 수 있다. 물론 이미지를 이용하는 방법도 있다. 이와 같은 방법으로 스킨을 변경하는 것은 Flex의 모든 컴포넌트에서 지원하므로 이런 스킬에 익숙해질 필요가 있겠다.

 

원본 예제 : Removing the header separator on the DataGrid control in Flex

 

주절주절 : 그냥 이렇게 가볍게 포스팅하는 것이 좋을 것 같다는 생각이 든다.

 

글쓴이 : 지돌스타(http://blog.jidolstar.com/546)

 

ActionScript 3.0(이하 AS3)은 Java와 매우 유사한 구조를 가진다. 그러므로 Java를 공부한 사람이라면 AS3를 쉽게 접근할 수 있게 된다. C++을 했던 사람도 비슷한 구조때문에 쉽게 접근이 가능하다.

 

AS2를 공부했던 사람은 객체지향적으로 설계된 AS3를 공부하면 된다.

 

여기서는 ActionScript 3.0에 대해서 어떤 절차없이 생각나는대로 적어보았다. 매우 개인적인 생각이기 때문에 반박하고 싶으신 내용도 있을지 모르겠다. 그렇다면 얼마든지 댓글 환영이다. ^^

 

 

Java와 ActionScript 3.0 차이점

 

Java와 AS3간에 차이점을 보여주는 표이다. 쭉 훑어보자.

http://blog.naver.com/surfwon/30049390718

 

 

ActionScript 3.0의 아쉬운점

 

AS3는 객체지향기반의 언어이지만 몇가지 아쉬운 점이 존재한다.

 

1. 메소드(함수)의 오버로드(overload)를 할 수 없다.

 오버로드는 클래스안에 같은 이름의 함수를 중복해서 사용할 수 있는 기능이다. AS3에서는 Java나 C++에서 지원되는 이 오버로드 기능이 없기 때문에 개발시 아에 생각을 하지 않게 하는 장점도 있지만 정말 필요할때 다른 방법으로 대체해야하는 불편함이 생긴다. 아래글을 보자.

 http://blog.jidolstar.com/484

 

2. 생성자는 하나만 존재한다.

위 오버로드를 지원하지 않는 것과 일맥 상통하는 내용이다.

 

3. 추상클래스가 지원되지 않는다.

Java와 같은 abstract 키워드가 존재하지 않아 추상클래스를 만들 수 없다. 하지만 throw등을 이용해 컴파일시가 아닌 런타임에서 동작하는 추상클래스는 제작이 가능하다. 추상클래스는 AS3에도 클래스 설계시 꼭 필요하지만 아직 지원하지 않아 아쉬운 부분이다. 아래 글을 보자.

http://blog.jidolstar.com/452

 

 

4. private 생성자가 지원되지 않는다.

private 생성자는 외부에서 클래스 객체를 임의로 만드는 것을 금지시켜주는 역할을 한다. 하지만 AS3에서는 생성자에 public외에는 private, protected 를 쓸 수 없다. private 생성자가 필요한 단적인 예는 싱글턴 패턴을 구현할 때인데 AS3에서는 아래와 같이 다른 방법으로 싱글턴 패턴을 구현한다.

http://koko8829.tistory.com/304

 

아래 링크는 싱글턴 패턴을 응용한 형태이다.

http://blog.jidolstar.com/468

 

 

 

ActionScript 3.0을 왜 사용해야하는가?

 

AS2를 주로 했던 사람은 AS3의 강력함을 잘 느끼지 못해서 전향하지 않는 사람들이 많은 것 같다. 아래 글을 보고 AS3를 왜 해야하는가 알아보자.

 

AS3를 왜 사용해야하는가? : http://ddongkang.tistory.com/76 

 

 

ActionScript 3.0 의 재미

 

초보자가 Java, C++에서 느끼지 못하는 AS3의 재미는 처음 만드는 애플리케이션이 시각화된 결과물이라는 점일 것이다. 이 게시판에 앞어 간단한 AS3프로젝트를 통해 3D를 단 몇줄도 안되는 코드로 구현했던 것을 보면 그 의미를 알 것이다. 또한 AS3를 하다보면 매우쉽게 데이터통신, 데이터제어, 각종 미디어 기술지원, Flex, AIR로의 개발확장성등으로 인해 C++, Java와는 또 다른 재미를 느끼게 될 것이다.

 

 

ActionScript 3.0 공부하자.

 

AS3는 얼마든지 공부할 수 있다. 좋은 동영상 강좌도 있다.

AS3 강좌모음 : http://ddongkang.tistory.com/73

 

AS3 학습을 위해 아래 링크의 문서와 친해지자.(한글이라서 좋다!)

http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/

http://help.adobe.com/ko_KR/AS3LCR/Flash_10.0/

 

위 문서는 영문도 있다.

http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/

http://help.adobe.com/en_US/AS3LCR/Flash_10.0/

 

 

 

Flash 기술에 대한 오해

 

아래 글에 오해하시는 분들이 많으신 것 같아요. 아래 제 댓글을 보시고 제가 보는 관점에서 아래 글을 판단해주셨으면 합니다. ^^

 

C++,  Java하시던 분(기초수준 및 서버개발만 주로 한 분들)이 AS3나 Flex를 접할때 잘못된 태도가 있다. 내가 좀 언어좀 하는데 그냥 쉽게쉽게 할 수 있겠지라는 마인드이다. 또는 Flash인데 그것도 언어야?... 이런 마인드.. 금물이다.

 

AS3나 Flex도 그만의 독특한 구조와 기능이 있다. C++, Java만 했던 사람이 데이터 바인딩, 메타데이터태그, 이벤트 모델, 비디오제어, 음원제어, 마이크로폰 제어, 디스플레이객체 다루기, 응용애플리케이션구조,각종보안, XML 다루기등에 대해서 잘 알리가 없지 않은가? 그만큼 Flash 기술이 단순히 그래픽을 위한 기술이다라고 오해하는데서 비롯된다고 생각한다. Flash 기술은 이미 데스크탑에서 모바일, 각종운영체제, 각종 브라우져에 MS계열의 기술보다 더욱 빠르고 거대하게 확장되어 이미 우리 실생활에 적용되고 있다. 그러므로 AS3.0을 공부할때 너무 가벼운 마음으로만 공부한다는 생각은 버리자. 요즘 안드로이드 폰에 Flash 지원하고 앞으로 AIR로 각종 모바일 프로그램을 만들 수 있게 된다는 것을 생각하면 가벼운 기술은 아니구나라는 생각을 할 수 있을 것이다.(못믿겠으면 여기를 본다) Flash는 이미 클라이언트 기반을 넘어 서버기반의 기술도 지원해주어 진정한 RIA의 선봉장에 있다. 이러한 점을 잘 이해하고 Flex,AS3,AIR,Flash등을 공부하는 것이 좋겠다.

 

Flash에서 이미지를 표현하기 위한 방법은 VectorBitmap이 있다.

 

Vector은 점,선,면등의 단순한 수학적 정보만 이용하여 이미지를 표현한다. 그렇게 때문에 적은 용량으로 이미지를 표현할 수 있으며 특히 확대/축소에도 깨지지 않는 이미지를 표현하는데 탁월하다. 또한 곡선 및 각종 이펙트 표현력도 훌륭하다. 적은 용량으로 이미지를 표현할 수 있기 때문에 메모리 관리에 도움이 된다. 하지만 복잡한 이미지의 경우 도리어 퍼포먼스의 저하가 일어날 수 있으며 이 경우 이동과 같은 에니메이션을 줄 경우 CPU연산이 많아지는 단점이 있다.

 

Bitmap은 이미지를 표현하기 위해 Pixel정보를 이용한다. 하나의 Pixel정보에는 ARGB값을 가진다. Pixel 하나하나 정보를 가지고 다루므로 복잡한 이미지를 나타내는데 좋으며 Vector와 달리 이동 애니메이션을 주더라도 CPU연산이 적다. 하지만 이미지가 클수록 Pixel 정보량이 제곱단위로 커지기 때문에 확대/축소시에 메모리가 과다하게 사용될 여지가 있다. 잘못사용하면 너무 큰 이미지로 인해 Flash Player가 다운될 수도 있다.

 

아래는 필자가 다루고자 하는 Vector와 Bitmap 이미지의 장단점을 언급한다.(아래 장단점은 완벽한 것은 아니다. 가령 Vector라고 해도 객체수가 적으면 속도에 무리가 없기 때문이다. 적어도 필자가 하고 있는 프로젝트 내에서 이런 문제가 있다는 것을 미리 언급한다.)

 

  • Vector 이미지
    - 메모리 효율이 좋다.(확대/축소시에도 메모리 점유율 변화 없음)
    - 이동 애니메이션 처리가 느리다.(CPU 연산이 커짐)
  • Bitmap 이미지
    - 메모리 효율이 나쁘다.(확대시 메모리 점유율 커짐)
    - 이동 애니메이션 처리가 빠르다.(CPU 연산 적음)

 

Vector 이미지와 Bitmap 이미지를 연구하게된 배경은 필자가 스타플 서비스(http://starpl.com/)의 별지도를 만드는데 이 부분에 대한 정보가 필요했기 때문이다. 먼저 아래 글을 읽어보면 이해가 쉽겠다.

 

[Flash,ActionScript 3.0]DisplayObject cacheAsBitmap 속성의 적용시점 문제

[Flex/AIR] 메모리의 무서운 적! DisplayObject의 cacheAsBitmap 속성

 

스타플의 별지도

 

구글맵과 같은 일반 지도의 경우 타일(Tile)들의 구성요소는 Bitmap 이미지이다. 최적화된 Bitmap이미지이기 때문에 렌더링 시간도 짧고 이동도 빠르다. 필자가 작업한 별지도는 천체 데이터 및 사용자 별 데이터를 읽어와 Vector 이미지로 렌더링하는 방식이다. 따로 데이터 가공 및 렌더링 시간이 필요하고 Vector이미지는 일단 이동 애니메이션이 느리다. 별이 반짝거리므로 단순히 Bitmap으로 바꿔서 처리할 수 있는 것도 아니다. 결국 스타플 별지도는 기본적으로 Vector 이미지로 타일을 구성해야한다.

 

앞서 설명했듯이 Vector 이미지를 이용하면 메모리 문제는 발생하지 않는다. 문제는 이동시 속도가 느리다는 것이다. 마우스로 지도를 드래그해서 이동하는 경우 저사양 컴퓨터에서는 버벅거림이 발생한다. Vector 이미지 타일로 구성된 지도의 단점이다. 그래서 이것을 극복하기 위해 첫번째로 사용하려했던 기능이 flash.display.DisplayObject의 cacheAsBitmap 속성이였다. 이 속성을 true로 설정하면 타일이 Bitmap으로 캐싱되고 실제로 적용후 이동처리해보면 훨씬 부드러운 이동 애니메이션을 볼 수 있다.

 

하지만 cacheAsBitmap은 Bitmap 이미지를 만드는 것이므로 확대/축소에서 문제가 있다. 이 속성을 true로 설정하고 확대하면 메모리가 기하급수적으로 늘어난다. 그래서 간단하게 확대/축소전에 cacheAsBitmap을 false로 지정하면 되겠다고 생각했다. 하지만 이 속성이 false로 설정된 순간 캐싱된 Bitmap을 해제시켜주지 못했다. 결국 false로 지정하고 확대해도 여전히 메모리가 증가한다.

 

이를 극복하기 위해 BitmapData를 이용했다. 만들어진 타일(Tile) 클래스에 bitmapMode라는 속성을 정의하고 true이면 Bitmap이미지를 false이면 Vector이미지를 보일 수 있도록 만든다. 그렇게 해서 이동이 발생시에는 Bitmap이미지를 사용하고 평상시 또는 확대/축소시에는 Vector 이미지를 사용할 수 있도록 한다. 아래는 이것을 구현하는 간단한 코드이다.(완벽한 코드가 아니다. 단지 예시일 뿐이다.)

 

this.addEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false, 0, true );

public function bitmapMode( value:Boolean ):void
{
	if( _bitmapMode == value ) return;
	_bitmapMode = value;
	
	.....

	if( _bitmapMode )
	{
		var bitmapData:BitmapData = new BitmapData( getWidth()+100, getHeight()+100, true, 0x00000000 );
		var matrix:Matrix = new Matrix();
		matrix.translate( 50, 50 );						
		bitmapData.draw( this, matrix, null, null, null, false );
		bitmapImage = new Bitmap( bitmapData );
		bitmapImage.x = -50;
		bitmapImage.y = -50;
		addChild( bitmapImage );
		removeChild( vectorImage );
	}
	else
	{
		addChild( vectorImage );
		removeChild( bitmapImage );
	}

	.....
}


public function disposeBitmap():void
{
	bitmapMode = false;
	if( bitmapImage )
	{
		bitmapImage.bitmapdata.dispose();
		bitmapImage = null;
	}
}

private function onRemovedFromStage( event:Event ):void
{
	this.removeEventListener( Event.REMOVED_FROM_STAGE, onRemovedFromStage, false );
	disposeBitmap();
}

 

bitmapMode 속성을 구현할때 주의할 사항은 bitmapMode가 true일 때마다 BitmapData를 생성하는 일은 매우 비효율적이라는 것이다. 왜냐하면 이동할때마다 BitmapData를 만드는 것이기 때문이다. 그러므로 처음 이동이 발생할때만 BitmapData를 만들도록 하는게 중요하다. 또한 bitmapMode가 false라고 해서 기존에 만든 BitmapData를 삭제해서는 안된다. 삭제하면 언급한데로 다시 BitmapData를 만들어야하기 때문이다.

 

또 한가지 여기서 발생하는 가장 큰 문제는 적절한 메모리 관리이다. BitmapData를 잘 삭제해주어야 메모리 문제가 발생하지 않기 때문에다. 단순히 위에서 언급한 bitmapMode 변경으로는 BitmapData를 제대로 삭제할 수 없다. 그러므로 타일(Tile)클래스에 disposeBitmap() 함수를 만들어 bitmap.bitmapdata.dispose()를 호출하여 비트맵을 삭제하도록 하고 타일이 removeChild 될때 이 disposeBitmap()을 호출하여 BitmapData를 완벽하게 삭제하도록 해준다. removeChild가 되는 시점은 Event.REMOVED_FROM_STAGE 이벤트 처리를 하면 바로 알 수 있다.

 

삭제된다는 이야기는 메모리에서 바로 삭제된다는 것을 의미하지 않는다. Flash Player는 가비지 컬렉터가 작동한다. 즉 메모리에서 삭제될 대상을 모아두었다가 어느 시점에 한꺼번에 삭제한다. 이는 메모리 관리의 효율적인 방법중에 하나로서 개발자는 가비지 컬렉터 대상이 되도록 모든 참조를 없애주도록 해야한다. 가비지 컬렉터에 대해서는 아래 필자의 글을 참고한다.

 

[Flex / AIR / ActionScript3] Event 청취자와 메모리 관리

Flash Player 의 가비지 컬렉션(GC) 동작 방식에 대해

 

메모리가 제대로 반환이 되는지 확인하기 위해 Flex Builder(현 Flash Builder)의 프로파일링 기능을 이용하거나 System.totalMemory를 적극적으로 활용하여 메모리 상태를 점검하는것이 좋겠다.

 

소개한 대로 Bitmap과 Vector 이미지를 필요할 때마다 바꿔가면서 처리하는 것이 필자가 발견한 최상 방법이였다. 적용시 키포인트는 적절한 메모리 관리와 BitmapData를 자주 생성하지 않도록 하는 것이다.

 

 

 

DisplayObject 속성중에 cacheAsBitmap을 사용해 본 적이 있나요?

 

cacheAsBitmap은 시각화된 객체를 Bitmap으로 캐싱해주는 역할을 하는 속성입니다. 이것이 true가 되면 DisplayObject는 Bitmap처럼 되는 것이지요. Flash에서 Bitmap과 상반되는 것이 Vector입니다. 일반적으로 Flash에서 그래픽적인 요소는 거의 Vector로 처리되지요. Bitmap과 Vector에 대한 차이점과 장단점을 굳이 설명안하겠습니다.

 

단지 제가 궁금하고 해결하고 싶은 문제는 cacheAsBitmap속성이 적용된 것을 확대/축소할때 입니다.

이 속성을 true로 하고 이동처리(마우스 Drag등)을 하는 것과 안한것은 엄청난(?)속도 차이를 보입니다. 복잡한 Graphic이 들어가고 이동이 필요한 경우 cacheAsBitmap을 true로 설정하여 애플리케이션의 퍼포먼스를 높힐 수 있지요. 하지만 cachAsBitmap은 메모리와는 적입니다. 가령 확대/축소할때 가장 큰 문제가 되는데 Bitmap이 확대/축소할때 이미지는 그 크기만큼 메모리를 잡아먹습니다. 아래 제가 쓴 글을 보시면 이해될 겁니다.

 

[Flex/AIR] 메모리의 무서운 적! DisplayObject의 cacheAsBitmap 속성

 

저는 이동시에는 cacheAsBitmap을 true로 설정하고 확대/축소할때는 false로 설정해서 퍼포먼스 개선을 하려고 합니다. 하지만 cacheASBitmap을 설정하더라도 적용되는 시점이 문제입니다. 확대/축소하기 전에 false로 지정하고 바로 확대/축소를 하면 메모리를 잡아먹는다는 것이지요. 다음 Render시점에서 확대/축소를 해야하는 것인가 생각이 드는데 아직 뾰족한 묘안이 없습니다.

 

혹시 저와 같은 경험을 하셨다던가 아니면 해결책을 알고 계신분 있다면 댓글 부탁드리겠습니다.

 

 

오늘 한국 Adobe RIA 공식사이트에서 발생된 ACC 뉴스레터를 이메일로 받았습니다. 

 

ACC는 Adobe Community Champion의 약어로 글로벌 정책으로 Adobe 각 제품군(Flash/Flex/AIR등)별 전문가들을 말합니다.  ACC 뉴스레터는 1개월에 한번씩 정기적으로 회원들에게 이메일을 통해 전송되며 한국 Adobe RIA 공식사이트에 가입하시면 받으실 수 있습니다.

 

제가 쓴 글을 비롯해 ACC분들의 글들이 올라왔습니다. 

 

ACC는 단순히 전문가만을 의미하지 않습니다. 또한 ACC라서 최상의 기술을 가지고 있는 것 또한 아닙니다. ACC가 아니더라도 드러내지 않는 실력 가지신 분들도 많답니다. 그럼 ACC는 어떤 사람들일까요?

 

제가 생각하는 ACC는 바로 Adobe 기술의 전도사입니다.

 

처음 Adobe 기술을 접하시거나 Adobe의 새로운 기술을 알고 싶고 때로는 토론하고 싶다면 ACC 분들과 적극적으로 커뮤니케이션을 유지하시면 좋겠다는 생각을 합니다.

 

한가지 조언드릴 것은 Adobe 기술도 나름 분야가 방대합니다. Flash, Flash Lite, Flex, ColdFusion, AIR, LiveCycle, BlazeDS 등등.. 아무리 ACC더라도 자신이 잘알고 해본 분야는 제한되어 있고요. 다 아는 것이 아닙니다. 그러므로 각각의 ACC의 전문분야를 잘아시고 목적에 맞게 대화를 하시는 것이 좋을 것 같습니다.

 

저의 경우 스타플(http://starpl.com)에 Adobe Flex/Flash 기술을 도입하면서 얻은 지식을 공유할 수 있습니다. Java분야나 ColdFusion, Flash Lite는 저도 잘 모릅니다. 그러므로 스타플에서 사용된 별지도, 별꾸미기, 위젯, 이미지에디터 등 Adobe RIA 기술에 국한되어 답변할 수 있을 것 같습니다. 같은 ACC지만 저도 다른 ACC의 도움을 받습니다. 그러므로 이 글을 보시는 여러분도 ACC와 소통하실때 그 부분을 잘 참고하시길 바랍니다.

 

또 한가지!

 

소통의 도구로 가장 좋은 것은 블로그입니다. 글 쓰는게 어려우신 분은 어쩔 수 없다지만 제가 생각할 때 블로그만큼 좋은 소통의 도구가 없습니다. 제가 ACC가 된 것도 블로그를 통해 기술을 공유하고 또한 소통하면서 얻은 타이틀입니다. 제가 잘해서 또는 이쪽에 탁월한 전문가이기 때문은 아니라는겁니다. ACC는 기술의 전도사라는 말을 기억하시면 이를 쉽게 이해할 수 있을겁니다.

 

제가 블로그를 강조하는 이유는 가령 이렇습니다. 일전에 제가 [ActionScript 3.0] Stage.invalidate()를 호출해도 Event.RENDER 이벤트가 발생하지 않는 문제 해결 글을 올렸습니다. 이 글을 올린데에는 단순히 지식전달 수준이 아닙니다. 지식전달만 목적이라면 블로그 할 필요없죠. 댓글을 보시면 답이 나옵니다. 서로 지식이 공유되고 있지요. 이게 파격적인 겁니다. 잘 모르더라도 잘 정리해서 글을 적으면 함께 고민하는 사람과 이야기할 수 있고 서로 알아갈 수 있습니다. 블로그는 댓글 기능만 있는 것이 아니라 블로그의 글끼리 묶어주는 트랙백(엮인글)이 됩니다. 또한 RSS발행을 통해 관심분야 사람들 유입경로를 만들어줍니다. 아직도 개발자인데 블로그 안하시나요? 공부하고 싶고 인맥을 구축하고 싶은데 블로그를 안하시나요? 실력이 없어서 또는 잘 모르는 지식 때문에 괜히 악플달릴까 고민하는 것은 좋지 않는 생각입니다. 틀리는 것을 부끄럽다고 생각하지 말아야 발전이 있는 것이니깐요. 블로그 하시는 것이 여러분의 실력을 키우는데 큰 도움이 되는 것을 강조하고 싶습니다. 적어도 저는 블로그의 혜택을 톡톡히 보는 사람중에 한사람이라 자신있게 강조합니다. ^^

 

ACC는 앞으로도 Adobe RIA 기술의 전도사로서 여러 활동을 할겁니다. 기대해주시고 기억해주세요. ^^

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 패턴을 구현했었다. 아~ 무식은 용감하다. ㅋ



Flash에서는 Remoting 호출을 위한 클래스가 존재하지 않는다. NetConnection을 가지고 할 수 있으나 Remoting 호출을 위한 전용 클래스가 아니기 때문에 실용적이지 못하다.

Flex에서는 Remoting 호출을 위해서 RemoteObject 클래스가 있다. 하지만 Flex 전용이라 Flash CS3, 4 개발자는 쓸 수 없다.

여기서는 NetConnection을 한번 wrapping하여 Remoting 호출을 구현한 Service 클래스를 소개한다. 이 Service 클래스는 byteArray.org 블로그 운영자가 만든 것으로 필요할 때 사용하면 매우 유용할 것이라 생각한다.

mport org.bytearray.remoting.Service;
import org.bytearray.remoting.PendingCall;
import org.bytearray.remoting.events.FaultEvent;
import org.bytearray.remoting.events.ResultEvent;
 
var service:Service=new Service("HelloRemoting",
"http://localhost:8001/amfphp/gateway.php");
 
var pendingCall:PendingCall=service.sayHello("remoting");
pendingCall.addEventListener(ResultEvent.RESULT,onResult);
pendingCall.addEventListener(FaultEvent.FAULT,onFault);
 
function onResult(e:ResultEvent):void{
trace(e.result);
}
 
function onFault(e:FaultEvent):void{
trace(e.fault);
}

사용법은 위처럼 너무 간단하다. AMFPHP, OpenAMF등으로 Remoting 서비스를 위한 서버를 구축하고 클라이언트는 Flash를 통해 개발하려는 사람에게 매우 유용할 듯 싶다. 또는 Flex가 아닌 순수 ActionScript 3.0 으로만 개발하려는 사람에게도 괜찮을 것 같다.



더 자세한 내용은 아래에 이 코드를 만든 원저자의 글을 보길 바란다.


Making Flash Remoting easier in Flash CS3


ByteArray.org에 기존 Flash Player 9에서 사용되는 corelib의 JPGEncoder보다 더 빠른 Flash Player 10용으로 제작된 JPGEncoder를 만들어 공개했다. 더 빠를 수 있었던 것은 VectorBitmapData.setVector() 때문이다. 기존 Array 형태보다 글쓴이 컴퓨터에서는 2.5배 빠르다고 한다. 아래 프로그램에서 테스트 할 수 있다. 2880 x 2800 비트맵데이타를 encoding 하도록 만들어졌다.

 

 

 

필자는 아직 여러가지 문제로 Flash Player 10용으로 개발하지 못하고 있어 아쉽지만 위와 같은 기능을 앞으로 잘 사용하면 속도개선에 큰 도움이 될 것이라 판단이 된다.

 

Flex Builder에서 라이브러리 프로젝트를 구성하다가 컴파일시 "unable to export SWC oem" 에러가 발생하며 SWC를 생성하지 못하는 경우가 종종 발생한다. 대부분의 경우, 라이브러리 빌드 Assets 경로에 등록한 파일의 경로가 강제로 수정되거나 삭제되었을 때 발생한다. 이 에러를 해결하기 위해 다음과 같이 진행해보자.

 

이 Assets 경로는 SWC로 만들때 필요한 자원을 포함하기 위한 것으로 comps 컴파일러의 옵션중에 -include-file 을 주는 것과 동일하다.

 

만약 존재하는 asset이 Assets 리스트에 존재하는데도 불구하고 에러가 발생했다면

 

1. Project 메뉴 > Properties > Flex Library Build Path > Assets 탭

2. asset을 uncheck한다.

3. OK버튼을 누른다.

4. 다시 똑같이 돌아가서 해당 asset을 re-check한뒤 OK버튼을 누른다.

 

 

만약 존재하지 않는 asset이 Asset 리스트에 등록되어 있어 에러가 발생했다면 임시로 해당 asset을 추가하는 방법으로 해결한다.

 

1. New > File 에서  하나의 파일을 만든다.

2. Project 메뉴 > Properties > Flex Library Build Path > Assets 탭

3. 새로 만든 파일을 check한다.

4. OK 버튼을 누른다.

5. 다시 돌아가 새로운 파일을 uncheck한 뒤 그것을 삭제한다.

 

 

같은 에러가 발생한 분들에게 도움이 되었으면 한다. ^^

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