글쓴이 보관물: coozplz

coozplz에 관하여

모바일 및 서버 개발에 관심이 많은 개발자. Java 서버 개발을 시작으로 개발에 입문하여 iOS, Android, Nodejs등 여러가지 언어를 거쳐 현재는 프론트엔드개발 업무를 주로하는 대한민국의 많은 개발자중 한명입니다.

[C] Echo server/client

대학교 이후로 거의 접해지 못했던 C 를 다시 시작하고 있습니다.

다시 시작하는 이유는 회사에서 C 개발자가 필요하기 때문입니다. 처음이라 진입이 쉽지는 않겠지만 포기하지 않고 계속 하다 보면 언젠가는 되리라 생각합니다.

예제는 http://www.joinc.co.kr/w/Site/win_network_prog/doc/winsock_basic 의 코드를 참조하며 모르는 부분에 주석을 추가하였습니다.

지속적으로 이전에 java로 만들었던 채팅 서버, 메신저 서버 등을 만들어볼 생각입니다.

개발환경

  • IDE: Visual Studio 2012
  • OS: Windows 10

[echo_server.c]

/*
File: echo_server.c
Desc: 에코 서버.
        서버는 클라이언트의 접속 요청을 수락한 후 송신된 메시지를 출력 및 동일 메시지 송신 후 클라이언트의 연결을 종료하는 프로그램
Date: 2016. 04. 27
Author: coozplz@gmail.com
*/
#include <winsock.h>
#include <stdio.h>

// Winsock 라이브러리 로딩을 위한 코드
#pragma comment(lib, "Ws2_32.lib");


#define MAX_PACKET_LEN 512
#define PORT 5552
#define BACK_LOG_SIZE 5

int main()
{
    WSADATA wsaData;
    int status;
    int socketLength;
    int readSize, writeSize;
    SOCKET endPointSocket, clientSocket;

    struct sockaddr_in SocketInfo, ClientSocketInfo;

    char buffer[MAX_PACKET_LEN];

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup error \n");
        return 0;
    }

    endPointSocket = socket(AF_INET, SOCK_STREAM, 0);

    if (endPointSocket == INVALID_SOCKET) {
        printf("endPointSocket is invalid \n");
        return 0;
    }

    printf("Server is running on port %d\n", PORT);

    // 구조체를 초기화한다.
    ZeroMemory(&SocketInfo, sizeof(struct sockaddr_in));
    SocketInfo.sin_family = AF_INET;
    // htons(): Host 시스템에서 Network 로 short 형 데이터를 보낼 때 바이트 오더를 바꿔주는 함수
    SocketInfo.sin_port = htons(PORT); 

    // htonl(): long형 데이터의 바이트 오더를 바꿔주는 함수.
    // INADDR_ANY 는 OS에 설정되어 있는 아이피 어느것이나 바인딩 한다는 의미
    SocketInfo.sin_addr.S_un.S_addr = htonl(INADDR_ANY); 

    status = bind(endPointSocket, (struct sockaddr*)&SocketInfo, sizeof(struct sockaddr_in));

    if (status == SOCKET_ERROR) {
        printf("failed to bind socket \n");
        return 0;
    }

    //
    // backlog 인자는 아직 미결인 연결들에 대한 큐의 늘어날 수 있는 최대 길이를 정의한다.
    // 큐에 도착한 연결 요청들이 꽉 찬다면 클라이언트는 ECONNREFUSED를 가리키는 에러를 받거나 만일 하위 프로토콜이 재전송을 지원한다면, 요청은 재시도가 성공되도록 하기 위해 무시된다.
    //
    status = listen(endPointSocket, BACK_LOG_SIZE);

    if (status == SOCKET_ERROR) {
        printf("failed to listen socket \n");
        return 0;
    }

    while (TRUE) {
        // 클라이언트 구조체 초기화
        ZeroMemory(&ClientSocketInfo, sizeof(struct sockaddr_in));
        socketLength = sizeof(struct sockaddr_in);

        clientSocket = accept(endPointSocket, (struct sockaddr *) &ClientSocketInfo, &socketLength);
        if (clientSocket == INVALID_SOCKET) {
            printf("client socket is invalid\n");
            closesocket(clientSocket);
            continue;
        }

        // inet_ntoa() : 네트워크 바이트 순서의 32비트 값을 Dotted-Decimal notation 의 주소값으로 변환
        // 예 0x6601a8c0: 102.1.168.192
        printf("[Accepted] %s:%d\n", inet_ntoa(ClientSocketInfo.sin_addr), (int)ntohs(ClientSocketInfo.sin_port));
        ZeroMemory(&buffer, sizeof(buffer));
        readSize = recv(clientSocket, (void *)buffer, MAX_PACKET_LEN, 0);

        if (readSize > 0) {
            printf("Message from client > %s\n", buffer);
            writeSize = send(clientSocket, buffer, readSize, 0);
        }
        else {
            printf("Error on reading\n");
        }
        closesocket(clientSocket);
        printf("[Closed] %s:%d\n", inet_ntoa(ClientSocketInfo.sin_addr), (int)ntohs(ClientSocketInfo.sin_port));
    }
    closesocket(endPointSocket);
    WSACleanup();
    return 0;
}

[echo_client.c]

/*
File: echo_client.c
Desc: 에코 클라이언트.
      서버에 접속 요청을 한 후 사용자가 입력한 메시지를 서버에 송신 후 응답 메시지를 출력한 후 소켓 연결이 종료되었는지 판단하고 소켓 연결을 종료한다.
Date: 2016. 04. 27
Author: coozplz@gmail.com
*/

#include <stdio.h>
#include <WinSock2.h>

// Winsock 라이브러리 로딩을 위한 코드
#pragma comment(lib, "Ws2_32.lib");


#define PORT_NUM 5552
#define MAX_LEN 1024

int main(int argc, char **argv)
{
    SOCKET socketFd;
    WSADATA wsaData;
    struct sockaddr_in addr;

    char buffer[MAX_LEN];
    char receiveBuffer[MAX_LEN];

    int status = 0;
    int readSize = 0;
    status = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (status != NO_ERROR) {
        printf("WSAStart has error\n");
        return 1;
    }

    socketFd = socket(AF_INET, SOCK_STREAM, 0);

    if (socketFd == INVALID_SOCKET) {
        printf("socketFd is invalid\n");
        return 1;
    }
    ZeroMemory(&addr, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;
    //
    // inet_addr() : Dotted-decimal 타입을 네트워크 32바이트 순서로 변경
    //
    addr.sin_addr.S_un.S_addr = inet_addr("192.168.10.52");
    addr.sin_port = htons(PORT_NUM);

    status = connect(socketFd, (struct sockaddr_in *)&addr, sizeof(addr));

    if (status == SOCKET_ERROR) {
        printf("connection failed...\n");
        return 1;
    }

    while (TRUE) {
        printf("Connection is established...\n");
        printf("> ");
        fgets(buffer, MAX_LEN - 1, stdin);
        if (strncmp(buffer, "quit\n", 5) == 0) {
            break;
        }

        printf("SEND: %s", buffer);
        send(socketFd, (void *)buffer, strlen(buffer), 0);
        ZeroMemory(&receiveBuffer, sizeof(receiveBuffer));
        readSize = recv(socketFd, (void *)receiveBuffer, MAX_LEN, 0);
        printf("READ = %s\n", receiveBuffer);
        //
        // 소켓연결이 종료되었는지 판단하기 위해 다시 recv() 함수를 호출한다.
        //
        ZeroMemory(&receiveBuffer, sizeof(receiveBuffer));
        readSize = recv(socketFd, (void *)receiveBuffer, MAX_LEN, 0);
        if (readSize <= 0) {
            printf("socket is disconnected by server!!\n");
            break;
        }
    }
    closesocket(socketFd);
    WSACleanup();

    printf("Press any key to exit program.");
    getchar();
    return 0;
}
이 글은 C 카테고리에 분류되었고 , 태그가 있으며 님에 의해 에 작성되었습니다.

[Shell Script] JSON parsing using Nodejs

Shell Script에서 Nodejs를 이용하여 JSON 값 가져오기


의도

쉘 스크립트를 이용해 프로그램 설치시 방화벽에 자동으로 포트를 추가하려고 하니 설정 정보는 JSON 파일로 되어 있어 쉘 스크립트만으로 파싱하기는 어려움이 있습니다.

다행히 프로그램이 nodejs 기반으로 돌아가는 어플리케이션이라 쉘 스크립트에서 nodejs 를 이용하여 파싱하고 값을 가져오는 처리를 하였습니다.

※ 다른 방법도 많이 있습니다. awkpython 을 이용하여 값을 가져오는 방법도 있으니 구글링해보시길 바랍니다.

입력, 실행 파일


[JSON file]

// default.json
{
    "http": {
        "port": 9080
    },
    "websocket": {
        "port": 9070
    },
    "peerjs": {
        "path": "/peerjs",
        "port": 9000,
    }
}

[Shell script file]

// test.sh

# http 포트 정보 가져오기 
HTTP_PORT=$(node -pe 'JSON.parse(process.argv[1]).http.port' "$(cat default.json)")

# peerjs 포트 정보 가져오기
PEERJS_PORT=$(node -pe 'JSON.parse(process.argv[1]).peerjs.port' "$(cat default.json)")

# websocket 포트 정보 가져오기
WEBSOCKET_PORT=$(node -pe 'JSON.parse(process.argv[1]).websocket.port' "$(cat default.json)")

echo "HTTP port is $HTTP_PORT"
echo "PEERJS port is $PEERJS_PORT"
echo "WEBSOCKET port is $WEBSOCKET_PORT"

# Output
# HTTP port is 9080
# PEERJS port is 9000
# WEBSOCKET port is 9070

실행


// 스크립트에 실행 권한을 추가 
> chmod u+x test.sh

// 스크립트 실행
> ./test.sh

[Script] bash root 권한 확인

Bash 스크립트 작성시 ROOT 여부 확인

아래와 같이 코드를 도입부에 추가하면 루트 권한이 있는 경우에만 실행 되도록 할 수 있습니다.

#!/bin/bash
ROOT_UID=0
E_NOTROOT=67

if [ "$UID" -ne "$ROOT_UID" ]
then
echo "Script have to execute on ROOT mode"
exit $E_NOTROOT
fi

Cordova Pros & Cons

Cordova


최근에는 Feedly 를 이용하여 제가 주로 보는 글을 RSS 형태로 보고 있습니다. 매번 사이트에 들어가서 확인하기도 힘들고 해서 이용하는 굉장히 편리한 어플리케이션 입니다.

오늘 본 글 중에 Cordova의 정의, 동작원리, 장점, 단점등에 대한 내용중 장, 단점과 Phonegapcordova 에 대한 내용만 정리해보려고 합니다.

정리 내용중에는 제 경험도 일부 포함하였습니다.

원문: http://code.tutsplus.com/tutorials/an-introduction-to-cordova-basics–cms-25146

장단점

새로운 프로젝트에 cordova 또는 Phonegap을 도입하려고 한다면 아무래도 집중적으로 살펴야 하는 부분이 단점에 대한 부분인거 같습니다.

신경쓰지 않으면 나중에 아주 힘든 상황이 발생될 수 있습니다.

장점

1. 쉽게 배울 수 있다.

cordova는 HTML페이지만 잘 만든다면 바로 사용할 수 있습니다.

2. 네이티브 API 접근 가능

카메라, 연락처, 위치정보, 미디어등 기기에서 제공하는 항목에 대해 접근이 가능합니다.

3. 공짜

제가 생각하는 가장 중요한 이유입니다.

4. 오픈소스

5. 다양한 커뮤니티 및 많은 사용자

cordova에 관련된 이슈에 대해 문제 해결을 하기 위해 검색하면 많은 결과가 나옵니다. 비슷한 이슈사항을 가진 사람이 많으면 버그도 빠르게 수정될 수 있습니다.

6. 다양한 플랫폼 지원

안드로이드, iOS, Windows에 모두 지원되는 어플리케이션을 단일 코드로 작성할 수 있습니다.

단점

단점에 대한 경험을 제가 아는대로 공유해보겠습니다.

1. 문서 미흡

2. 네이티브 소스보다 느리다.

당연한 내용이지만 cordova 로 어플리케이션을 개발하면서 네이티브 어플리케이션과 동일한 성능을 나타낸다는 것은 어불성설입니다.

cordova는 하나의 소스로 다양한 플랫폼을 지원하는 편의성이 있지만 거기에 따른 비용을 무시할 수는 없습니다. 이것은 다른 하이브리드 어플리케이션도 비슷하다고 생각합니다.

3. 프레임워크

iOS 네이티브 어플리케이션의 경우 UITableView 형태의 구현이 정말 쉽습니다. 그렇지만 이와 같은 형태를 Phonegap으로 구현하기 위해서는 네이티브보다 훨씬 어려운 방법이 동원되어야 합니다.

또한 네이티브에서 사용되는 페이지 전환 애니메이션 효과를 나타내기 위해서도 많은 코드및 꼼수가 포함되어야 합니다.

cordova가 HTML 페이지를 Webview 에서 표시하는 형태이기 때문에 어플리케이션 사용시 네이티브 어플리케이션과 유사하게 만들어야 한다면 많이 어렵습니다.

4. 플러그인 버그

iOS 9 버전이 발표되며 고객사의 요구사항에 어플리케이션 대상에 iOS 9이 포함되었습니다. 요청에 대해 대응을하고자 빌드를 하였지만 iOS8에서는 정상동작을 하지만 iOS 9에서 잘못 표시되는 항목이 발생하였습니다.

원인은 포함된 플러그인의 버그 였습니다. 저희가 선택할 수 있는 방법은 플러그인이 업데이트 되기를 기다리는 방법과 플러그인을 사용하지 않고 빌드된 파일을 변경하는 방법이었습니다.

결론은 빌드된 파일을 변경하였습니다. 3rd 파티 플러그인의 경우 개발자가 빠르게 대응해준다면 고마운 일이지만 그렇지 않다면 언제 개선될지 모르기 때문입니다.

5. 모든 플랫폼에 동일하지 않다

폰갭으로 빌드한 결과물을 실제 단말에 올려보기 전에는 화면 표시 및 다른 부분이 어떻게 동작할지 예상할 수 없습니다. iOS에서 동작이 잘되니까 Android에서도 잘되겠지라는 생각은 금물입니다.

폰갭(Phonegap)과 Cordova

Phonegap API를 참조하다 보면 링크가 cordova 페이지로 넘어가는 경우가 많습니다. 간단하게 생각하면 cordova를 한번 감싼게 Phonegap이라고 생각됩니다.

두개를 로컬에서 빌드한다면 명령어만 다르고 나머지는 대동소이합니다.

2015년 정리

2015년 정리


2015년 한해는 2015년이 된지 얼마 안된거 같은데 마무리 되는 것처럼 빠르게 지나갔습니다.

블로그에 2015년 초에 게시한 내용을 토대로 정리해봤습니다.

2015년 목표


습관

  • 일반(20권)
  • 전공(10권)
  • 원서(10권)

나를 위한 투자

  • 배드민턴
  • 크로스핏
  • 등산
  • 자전거

가족

  • 아버지와 매달 목욕탕 함께 가기
  • 일주일에 하루는 가족의 날로 정하기

프로그래밍

  • AppStore에 어플리케이션 판매 하기
  • 자료구조
  • 알고리즘 정리
  • C# 개발 해보기
  • Mac용 어플리케이션 만들기

기타

  • 신규 아이템 확인

2015년 결과


습관

달성률 40%

일반서적(10권), 전공서적(5권), 원서(1권)

목표치보다 많은 책을 읽지는 않았지만 전반적으로 2014년에 비해 독서시간이 늘었습니다. 다만 원서 부분은 아직 저조합니다. 원서를 읽는 능력이 아직 많이 부족하기 때문에 초등학생, 중학생 수준의 책을 읽고 있습니다.

전공 서적은 개발 방법론과 소프트웨어 품질 향상을 위한 주제를 주로 읽었습니다.

일반 서적은 여행과 자기계발과 관련된 분야를 읽었습니다.

나를 위한 투자

달성률 20%

배드민턴, 크로스핏, 등산, 자전거등 여러 운동들을 하고 싶었지만 시간이 없다는 핑계와 부상으로 인해 대부분을 포기 했습니다.

그렇지만 약 5개월정도 1주에 3일 정도는 꾸준하게 웨이트 트레이닝을 하였습니다.

가족

  • 아버지와 매달 목욕탕 함께 가기
  • 일주일에 하루는 가족의 날로 정하기

달성률 0%

가족과 함께 하는 부분은 둘째가 나오면서 더욱 신경 써야 되는 부분이지만 가장 못한 것 같습니다. 2016년도는 더욱 노력하여 가족과 함께 하는 시간을 늘려야 될 것 같습니다.

프로그래밍

달성률 40%

[앱스토어 등록]
애플스토어에 개인용 어플리케이션은 총 2개를 등록하였습니다.

1. MERS Korea

한국에 메르스가 퍼지는 시점을 기반으로 개발하여 주춤할 시기에 개발을 완료 하였습니다. 심사중 반려되어 재심사를 요청하는 과정이 생각보다 길어져 출시가 늦었습니다. 그렇지만 서버와 클라이언트를 모두 작성하고 앱 제작 처음부터 끝까지 했기 때문에 재미는 있었습니다.

2. HOW2TOEFL

대학 친구가 기획을 하고 그 기획안대로 개발한 앱입니다. 원어민을 통해서 음원을 녹음하고 녹음된 음원 파일을 재생하는 형태로 하였는데 생각보다 시간이 많이 걸렸습니다.

판매 결과는 최악이지만 많은 교훈을 얻을 수 있었습니다.

[자료구조, 알고리즘 정리]

자료구조와 알고리즘은 따로 정리는 못했고 프로젝트오일러을 통해서 지속적으로 공부를 하고 있습니다.

[C# 개발 해보기]

회사에서 필요로 하는 타이머 어플리케이션을 개발했습니다. 간단한 어플리케이션이지만 요청사항에 까다로운 부분이 있어서 애매한 부분이 있었습니다.

다시 느낀 결론은 UI프로그램은 힘들다.

[Mac용 어플리케이션 만들기]

화면이 없는 OSX용 프로그램을 개발했지만 화면이 있는 것은 하지 않아서 안한것으로 간주하겠습니다.

기타

[신규 아이템 확인]

앱 개발의 필요성을 느끼면서 지속적으로 모니터링하고 메모하고 생각하고 있습니다. 이 글을 쓰고 있는 시점에도 어떤 어플리케이션이 좋은 아이템이 될지 생각하고 있습니다. 아마 다음에 개발하는 어플리케이션은 단순한 게임이 되지 않을까 하는 생각입니다.

결론

1년이란 시간은 연초에 보면 참으로 긴 시간인거 같지만 연말에 보면 정말 빠르게 지나가는 시간인거 같습니다. 새삼 시간관리및 자기관리의 필요성을 느낍니다.

2016년은 보다 현실적은 계획과 세운 계획을 철저히 실천하는 것을 목표로 해야겠습니다.

이상입니다.

[Nodejs] CentOS 설치

1. Download Node binary

https://nodejs.org/en/download/ 링크로 이동하여 바이너리 파일을 다운로드 합니다.

* 아래 링크는 설치 일시 및 노드 버전에 따라 달라질 수 있습니다.

$ wget https://nodejs.org/dist/v4.2.4/node-v4.2.4-linux-x64.tar.gz

2. 압축 해제 및 압축 해제 파일 이동

2.1 다운로드 파일 압축 해제

$ tar xfz node-v4.2.4-linux-x64.tar.gz

2.2 압축 해제 파일 이동

$ mv node-v4.2.4-linux-x64 /usr/local/node

3. 환경 변수 설정하기

자신의 환경 변수 파일을 수정합니다. (.profile or .bashrc)

[~/.profile]

export NODE_HOME=/usr/local/node
export PATH=$PATH:$NODE_HOME/bin

4. 설치 결과 확인

[coozplz@localhost ~]$ node
> console.log('helloworld')
helloworld
undefined

[Java] Lambda(Stream) 는 항상 좋을까?

[원글] http://www.javacodegeeks.com/2015/12/3-reasons-shouldnt-replace-loops-stream-foreach.html

Java 1.8이 출시된 후 많은 블로그 및 홈페이지에서 Lambda 를 소개하는 글들이 많이 올라와 있습니다. 그렇지만 대부분의 글에 Lambda의 장점들에만 치중하는 내용이 대부분인데 오늘 RSS 피드에 괜찮은 내용이 있어 요약해 봅니다.

1. 성능

Lambda(Stream) 와 For-loop 방식의 처리에는 성능의 차이가 발생된다.

ArrayList, for-loop : 6.55 ms
ArrayList, seq. stream: 8.33 ms
int-array, for-loop : 0.36 ms
int-array, seq. stream: 5.35 ms

위의 성능의 차이는 어찌 보면 큰 차이는 아닌것 같지만 대량의 데이터를 처리해야 하는 로직에 포함된 Stream API라면 많은 성능 차이를 일으킬 수 있습니다.

2. 가독성

저도 간단하게 Stream API를 이용하여 Filter및 Collector를 이용하면서 소스 코드가 확연히 줄어든 경험이 있지만 가독성은 Stream API를 사용하기 전보다 떨어지는 것을 느꼈습니다.

단순 리스트에서 동일한 값을 뽑아내는 형태의 것은 문제가 없다고 생각하지만 Filter를 이용해서 값을 추출하고 추출한 값을 다시 가공하고 연산하여 데이터를 취합하는 형태를 구현 한다면 훨씬 복잡하고 이해하기 힘든 코드가 될 것이라는 생각입니다.

또한 다수의 사용자가 동일한 코드를 작업하는 환경이라면 모든 사용자가 익숙하지 않다면 Stream API 사용을 자제하는 것이 좋다고 생각됩니다.

3. 유지보수

대부분의 코드는 단발성이 아닌 수정이 빈번하게 발생되는 경우가 있습니다. Stream API를 사용하는 경우 StackTrace에 불필요한 내용이 출력되어 오류 분석에 어려움이 있을 수 있습니다.

[For-loop]

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:13)

[Lambda]

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.lambda$1(Test.java:18)
    at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110)
    at java.util.stream.IntPipeline$Head.forEach(IntPipeline.java:557)
    at Test.lambda$0(Test.java:17)
    at java.util.Arrays$ArrayList.forEach(Arrays.java:3880)
    at Test.main(Test.java:16)

이상입니다.

[InnoSetup] MsgBox 일본어 깨짐

문제점

InnoSetup에서 경고창을 띄우기위해 code 섹션에 MsgBox 함수를 호출하는 경우가 있는데 iss 파일에서는 정상적으로 일본어를 입력하였으나 실제 출력되는 내용은 깨진 글자가 출력되는 현상

해결방안

아래 소스와 같이 언어 설정을 하고 커스텀메시지를 추가한다. 추가한 커스텀 메시지를 MsgBox에서 호출하도록 처리하면 일본어 글자가 정상적으로 출력된다.

[Languages]
Name: jp; MessagesFile: "compiler:Default.isl"

[CustomMessage]
jp.CheckError='権限がありません。'

[Code]
procedure InitializeWizard;
begin
MsgBox(ExpandConstant('{cm:CheckError}'), mbInformation, MB_OK);
end;

[Python] 크리스마스트리

제작 의도

어제 다음 카페 게시글을 보다 보니 각 학과별 크리스마스트리를 그리는 방법이라는 글이 올라와 있어 Python을 이용하여 크리스마스 트리를 제작해봤습니다.

모두 즐거운 성탄절 되세요..!!

소스 코드

ROW = 9


def printTree(maxCountOfSpaceNStart):
    for i in range(1, maxCountOfSpaceNStart):
        print '%*s%*s' % (ROW-i, ' ', 0, '*' * (i * 2 -1))

if __name__ == '__main__':
    print '=' * 20
    print '%2s%s' % (' ', 'Merry Christmas!!!')
    print '=' * 20
    trees = [5, 7, 9]
    for i in trees:
       printTree(i)

실행 결과

====================
  Merry Christmas!!!
====================
        *
       ***
      *****
     *******
        *
       ***
      *****
     *******
    *********
   ***********
        *
       ***
      *****
     *******
    *********
   ***********
  *************
 ***************

[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();
        }

    }

}