[AS3] Custom Sliderbar

플래시 가로 슬라이더바

개발이유

플래시에서 제공하는 슬라이더 컴포넌트는 너무 플래시스럽지 않아 자체적으로 제작하여 사용하였습니다.

개발내용

  1. 슬라이더는 이미지를 사용하지 않고 Rectangle 객체를 이용하여 처리한다.
  2. 슬라이더의 값이 변경되면 이벤트를 발생한다.
  3. 슬라이더의 단계를 입력하여 단계별로 이벤트를 발생시킨다.

결과물

CustomSlider

소스

[HorizontalSlider.as]

package  {
    import flash.geom.Rectangle;
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.events.Event;
    import flash.display.GradientType;
    import flash.geom.Matrix;
    import flash.display.SpreadMethod;

    public class HorizontalSlider extends Sprite {

        public static const SLIDER_CHANGE:String = "sliderChange";

        var sliderBackground:Sprite;
        var sliderKnob:Sprite;

        var sliderKnobWidth:Number = 25;
        var sliderKnobHeight:Number = 40;

        var sliderBackgroundWidth:Number;
        var sliderBackgroundHeight:Number;

        var prevX:Number;
        var boundaries:Rectangle;

        var stepWidth:Number = 40;

        /**
        * 슬라이더 초기 좌표를 저장한다. StepIndex에 따라 슬라이더를 가운데에서 좌, 우로 이동한다.
        */
        var sliderKnobCenterPositionX:Number = 0;

        protected var _isPressed:Boolean;

        /**
        * 슬라이더 바를 생성하도록 한다. 슬라이더는 Rectangle Object 두개를 이용하도록 처리한다.
        * Background에 생성되는 Rectangle도 MOUSE_CLICK 이벤트를 수신하여 슬라이더 버튼을 이동시킨다.
        *
        * @param sliderWidth    슬라이더의 너비을 설정한다.
        * @param sliderHeight   슬라이더의 높이를 설정한다. 
        * @param stepCount      슬라이더 버튼을 Index의 위치에 맞게 이동시킨다.
        *                       슬라이더 이동 범위 (sliderWidth / stepCount) 룰을 따른다.
        *
        */
        public function HorizontalSlider(sliderWidth:Number, sliderHeight:Number, stepCount:Number) {
            _isPressed = false;

            this.sliderBackgroundWidth = sliderWidth;
            this.sliderBackgroundHeight = sliderHeight;

            this.sliderKnobWidth = Math.round(sliderBackgroundWidth / 15);
            this.sliderKnobHeight = Math.round(sliderHeight + 20);

            boundaries = new Rectangle(0, -10, sliderWidth - this.sliderKnobWidth, 0);          

            this.stepWidth = int(sliderWidth / stepCount);

            drawSliderBackground();
            drawSliderKnob();           
            bindSliderEvents();

        }

        private function bindSliderEvents() : void {
            this.sliderKnob.addEventListener(MouseEvent.MOUSE_DOWN, onSliderKnobMouseDownHandler);
            this.sliderKnob.addEventListener(MouseEvent.MOUSE_UP, onSliderKnobMouseUpHandler);

            this.sliderBackground.addEventListener(MouseEvent.MOUSE_UP, onSliderBackgroundMouseUpHandler);
        }




        private function drawSliderBackground() : void {            
            sliderBackground = new Sprite();


            sliderBackground.graphics.lineStyle(2, 0xF298A2);
            sliderBackground.graphics.beginFill(0xFFFFFF);
            sliderBackground.graphics.drawRoundRect(0, 0, this.sliderBackgroundWidth, this.sliderBackgroundHeight, 5);          
            sliderBackground.graphics.endFill();
            sliderBackground.width = this.sliderBackgroundWidth;
            sliderBackground.height = this.sliderBackgroundHeight;
            trace(sliderBackground.width);
            trace(sliderBackground.height);

            this.addChild(sliderBackground);
            sliderBackground.x = 0;
            sliderBackground.y = 0;         
        }


        private function drawSliderKnob() : void {
            this.sliderKnob = new Sprite();
            this.addChild(sliderKnob);
            this.sliderKnob.graphics.beginFill(0xE53F00);
            this.sliderKnob.graphics.drawRoundRect(0, 0, sliderKnobWidth, sliderKnobHeight, 5);
            this.sliderKnob.graphics.endFill();     



            sliderKnob.x = (sliderBackground.width - sliderKnob.width) / 2;
            sliderKnob.y = sliderBackground.y - 10;

            //
            // 중간값을 저장하고 슬라이더 버튼을 이동시킨다.
            //
            sliderKnobCenterPositionX = sliderKnob.x;           
        }


        private function onSliderKnobMouseDownHandler(e:MouseEvent) : void {            
            this.sliderKnob.startDrag(false, boundaries);

            stage.addEventListener(MouseEvent.MOUSE_UP, onStageMouseUpHandler);
            stage.addEventListener(MouseEvent.MOUSE_MOVE, onStageMouseMoveHandler);
            this.prevX = this.sliderKnob.x;
            _isPressed = true;
            this.sliderBackground.removeEventListener(MouseEvent.MOUSE_UP, onSliderBackgroundMouseUpHandler);

        }


        private function onStageMouseMoveHandler(e:MouseEvent) : void {
            var currentPositionX = this.sliderKnob.x;

            if (_isPressed) {               
                if (Math.abs(currentPositionX - prevX) > 0) {                                        
                    prevX = currentPositionX;
                    // dispatchEvent(new Event(HorizontalSlider.SLIDER_CHANGE));
                    e.updateAfterEvent();
                } 

            }
        }


        private function onStageMouseUpHandler(e:MouseEvent) : void {
            this.sliderKnob.stopDrag();
            stage.removeEventListener(MouseEvent.MOUSE_UP, onStageMouseUpHandler);
            stage.removeEventListener(MouseEvent.MOUSE_DOWN, onStageMouseMoveHandler);
            _isPressed = false;
            this.sliderBackground.addEventListener(MouseEvent.MOUSE_UP, onSliderBackgroundMouseUpHandler);

            this.calculatePositionX(sliderKnob.x);
        }


        private function onSliderKnobMouseUpHandler(e:MouseEvent) : void {
            this.sliderKnob.stopDrag();

            stage.removeEventListener(MouseEvent.MOUSE_UP, onStageMouseUpHandler);
            stage.removeEventListener(MouseEvent.MOUSE_DOWN, onStageMouseMoveHandler);
            _isPressed = false;
            this.sliderBackground.addEventListener(MouseEvent.MOUSE_UP, onSliderBackgroundMouseUpHandler);

            this.calculatePositionX(sliderKnob.x);

        }


        private function onSliderBackgroundMouseUpHandler(e:MouseEvent) : void {
            var target:Sprite = e.target as Sprite;                         
            sliderKnob.x = e.localX - (this.sliderKnob.width / 2);

            this.calculatePositionX(sliderKnob.x);
        }


        private function calculatePositionX(x:Number) : void {
            var stepIndex:int = int(sliderKnob.x / this.stepWidth) + 1;                                     
            var offsetCount:Number = stepIndex - (int(int(this.sliderBackgroundWidth / this.stepWidth) / 2)+1);         
            this.sliderKnob.x = this.sliderKnobCenterPositionX + (offsetCount * this.stepWidth);        



            var param:Object = {
                "index" : stepIndex
            };
            this.dispatchEvent(new HorizontalSliderEvent(HorizontalSliderEvent.HorizontalSliderEvent, param));

        }
    }

}

[HorizontalSliderEvent.as]

package  {
    import flash.events.Event;

    /**
    * 슬라이더값 변경시 발생되는 이벤트
    * 
    * <사용법>
    * 
    *   //
    *   // Dispatch Event
    *   //
    *   var param:Object = {
    *       "index" : currentIndex
    *   };
    *   this.dispatchEvent(new HorizontalSliderEvent(HorizontalSliderEvent.HorizontalSliderEvent, param));
    *
    */
    public class HorizontalSliderEvent extends Event {

        public static const HorizontalSliderEvent:String = "HorizontalSliderEvent";

        public var param:Object;    


        public function HorizontalSliderEvent(type:String, param:Object, bubbles:Boolean = false, cancelable:Boolean = false) {
            super(type, bubbles, cancelable);
            this.param = param;
        }


        override public function toString() : String {
            return "HorizontalSliderEvent: " + param.toString();
        }

    }

}

여러 SWF 파일을 하나로 합치기

0. 실행결과

4개의 파일 N001_a.swf, N001_b.swf, N001_c.swf, N001_d.swf 를 포함하는 N001.swf 파일로 변경
N001.swf 파일에는 MovieClip 객체로 a,b,c,d 가 객체로 모두 포함됨.


1. 준비

1.1 32Bit JRE or JDK Download

http://www.oracle.com/technetwork/java/javase/downloads/index.html

1.2 Flex SDK Download

http://www.adobe.com/devnet/flex/flex-sdk-download.html

1.3 환경변수 설정

위치: 제어판-모든 제어판 항목-시스템 – 고급 시스템 속성 – 환경변수

  • JAVA_HOME: C:\Program Files (x86)\Java\jdk1.8.0_51
  • FLEX_SDK_HOME: C:\Program Files\Adobe\flex_sdk_4.6

Path에 추가
path=%JAVA_HOME%\bin;%FLEX_SDK_HOME%\bin;

[주의사항]
– 반드시 32Bit JAVA 를 Path로 설정해야 한다.

1.4 실행 확인

1.4.1 JAVA 버전 확인

C:>java -version
java version "1.8.0_51"

1.4.2 mxmlc 실행 여부 확인

C:>mxmlc
Adobe Flex 컴파일러 (mxmlc)
Version 4.6.0 build 23201
Copyright (c) 2004-2011 Adobe Systems, Inc. All rights reserved.

mxmlc [options] [defaultVar]
자세한 정보를 보려면 'mxmlc -help'를 사용하십시오.

2. Single SWF 파일 생성

2.1 ActionScript 파일 작성

아래의 예제는 3개의 SWF를 포함하는 파일

// N001.as
package {
    import flash.display.Sprite;
    import flash.display.*;


    dynamic public class N001 extends Sprite {  

        [Embed(source='./noun/N001_a.swf')] 
         private var _N001_a:Class;

        [Embed(source='./noun/N001_b.swf')] 
         private var _N001_b:Class;

        [Embed(source='./noun/N001_c.swf')] 
         private var _N001_c:Class;

         [Embed(source='./noun/N001_d.swf')] 
         private var _N001_d:Class;

         // 생성자
         public function Noun() {}              

        //
        //  외부에서 MovieClip을 호출할 때 사용하는 함수
        // 
        public function N001_a() : MovieClip { var mc:MovieClip = new _N001_a(); return mc; }
        public function N001_b() : MovieClip { var mc:MovieClip = new _N001_b(); return mc; }
        public function N001_c() : MovieClip { var mc:MovieClip = new _N001_c(); return mc; }
        public function N001_d() : MovieClip { var mc:MovieClip = new _N001_d(); return mc; }       

    }
}

2.2 파일 구조

- N001.as
- N001
    - N001_a.swf
    - N001_b.swf
    - N001_c.swf
    - N001_d.swf

2.3 명령창으로 빌드

2.2 구조로 생성된 폴더로 이동

[명령구문]

mxmlc -static-link-runtime-shared-libraries=true -use-network=false -o [출력파일 SWF 경로] -source-path="./" "[스크립트 파일 경로]" 

[실행예]

C:temp>mxmlc -static-link-runtime-shared-libraries=true -use-network=false -o ./N001.swf -source-path="./" "./N001.as" 

구성 파일 C:Program FilesAdobeflex_sdk_4.6frameworksflex-config.xml을(를)
불러오는 중
C:tempN001.swf(6488852바이트)

3. N001.swf 에서 Embed 파일 불러오기

var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onSwfLoadCompleteHandler);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onErrorHandler);
//
// 상대 경로 참조
//
loader.load(new URLRequest('./N001.swf'));

function onSwfLoadCompleteHandler(event:Event) : void { 
    var loaderInfo:LoaderInfo = LoaderInfo(event.target);   

    //
    // target을 Object 타입으로 반환해야 동적으로 함수를 호출할 수 있다.
    //
    this.n001Content = event.target.content as Object;      
    if (this.n001Content == null) {
        trace('noun contents is empty');
        return ;
    }

    //
    // 동적으로 함수 호출, methodName이라는 값은 N001.as 파일에 작성한 public function 함수의 이름과 일치해야 한다.
    //
    var methodName:String = 'N001_a';
    var m:Function = this.n001Content[methodName];  
    var mc:MovieClip = m(); 
    addChild(mc);

}

Trouble Shooting

1. mlmxc 를 실행 안되는 경우

[오류메시지]
C:>mlmxc
Error loading: C:Program FilesJavajdk1.8.0_51jrebinserverjvm.dll
[해결]

mxmlc 가 32 Bit Java를 참조하기 때문에 반드시 환경변수 JAVA_HOME 설정과 Path 정보를 32Bit를 참조하도록 해야함.


2. 트랜스코딩 실패

[오류메시지]
C:UsersoskwonDownloadstempN001.as(7): 열: 3: 오류: 트랜스코딩할 './noun/N001_a.swf'을(를) 확인할 수 없습니다.
[Embed(source='./noun/N001_a.swf')] private var _N001_a:Class;      

C:UsersoskwonDownloadstempN001.as(7): 열: 3: 오류: ./noun/N001_a.swf을(를)   트랜스코딩할 수 없습니다.  
[해결]

Embed 되는 SWF의 경로가 잘못되어 발생되는 문제


3. 정의되지 않은 메소드 실행

[오류 메시지]
    C:UsersoskwonDownloadstemp>mxmlc -static-link-runtime-shared-libraries=true -use-network=false -o ./N001.swf -source-path="./" "./N001.as"
    구성 파일 C:Program FilesAdobeflex_sdk_4.6frameworksflex-config.xml을(를) 불러오는 중  C:UsersoskwonDownloadstempN001.as(17): 열: 65 오류: 정의되지 않은 메서드 _N002_a을(를) 호출했습니다.
                    public function N001_a() : MovieClip { var mc:MovieClip = new _N    002_a(); return mc; }
[해결]

Embed에서 선언한 전역변수와 메소드에서 사용되는 이름이 일치하지 않아 발생되는 문제.


In App Purchase in AIR SDK 16

이슈사항

Error Image

Flash를 이용하여 크로스 플랫폼을 지원하는 어플리케이션을 개발하다 보니 iOS 버전용 앱내구매(In App Purchase)와 관련된 이슈사항를 공유해드리고자 합니다.

Google 에서 Adobe air Storekit 으로 검색 하면 처음에 나타나는 페이지는 밀크게임즈 링크 입니다. 회사에서도 이전 버전은 밀크게임즈 에 있는 StoreKit 라이브러리를 이용해서 앱내구매 프로세스를 진행하였으나 Apple의 64Bit 지원 정책이 변경됨에 따라 AIR SDK 버전을 부득이하게 16이상으로 변경하게 되었습니다. AIR버전 16버전에서는 밀크게임즈 Storekit 라이브러리를 사용하는 경우 컴파일 오류가 발생되며 빌드가 되지 않는 심각한 문제가 발생하였습니다.

원인

밀크게임즈에서 제공하는 ANE(AIR Native Extension)가 64Bit에서 컴파일되지 않아 발생되는 문제입니다.

대안

원본 소스를 64Bit 버전으로 다시 컴파일

하지만 현실적으로 유료 라이브러리를 소스 공개해주는 회사는 거의 없습니다.

ANE가 업데이트 되는 시점을 기다린다.

개발하는 회사에서는 시급한 문제지만 라이브러리 판매자 입장에서는 라이브러리 업데이트에 대해 긴급하게 대응하지 않기 때문에 현실적으로 대안이 되지 못합니다.

다른 라이브러리 사용.

64Bit가 지원되는 StoreKit 라이브러리가 있다면 해당 라이브러리를 이용하는 것도 괜찮은 방법입니다.

해결

Adobe Game SDK ProductStore ANE 를 이용하여 신규로 개발하는 방법을 택했습니다.
그렇지만 Adobe Game SDK를 설치하면 인스톨 되는 ANE 파일은 32Bit 전용으로 64Bit 지원 버전은 별도로 다운 받아야 합니다. 다운로드 링크