2010년 12월 30일 목요일

맥에서 아뒤노에 라이브러리 추가하기 (Add libraries into the arduino in the Mac OS X)

아뒤노에 외부 라이브러리를 추가하는 방법은 다음과 같다.

먼저 원하는 라이브러리를 다운받아 압축을 풀어준다.


Finder를 열어 Applications 폴더에서 Arduino.app를 찾는다.





Arduino.app 폴더 위에서 마우스 오른쪽 버튼을 클릭해 메뉴가 나오면 'Show Package Contents'를 선택한다.


 Contents -> Resources -> Java로 찾아간다.


압축을 풀어놓은 라이브러리를 libraries 폴더에 집어넣어 주면 된다.
아뒤노를 종료했다가 다시 재시작 하면 Files -> Examples 에서 추가된 라이브러리를 확인할 수 있다.

2010년 12월 8일 수요일

임베디드 무선랜 모듈 Wiz 610wi (Embedded Wireless LAN module Wiz 610wi)

임베디드 시스템에 무선 인터페이스를 추가하려고 할 때 블루투스나 Zigbee같은 경우는 저렴한 가격에 다양한 모듈을 구할 수 있어 쉽게 추가할 수 있는데 비해 무선랜의 경우는 가격도 비싸고 그나마도 쉽게 구할 수 없어 임베디드 시스템에 추가하기가 쉽지 않다.

하지만 국내 회사인 Wiznet에서 다양한 종류의 임베디드 통신 모듈(Ethernet module, Ethernet-to-serial, WLAN module)을 출시하여 대부분의 필요한 모듈을 쉽게 찾을 수 있다. 그래서 얼마전에 Wiznet에서 임베디드 DIY를 활성화하기 위해 무료로 제공해 준 Wiz 610wi 무선랜 모듈을 mbed 보드에 붙여 테스트를 진행해 보았다.



802.11b/g를 지원하는 모듈로 Serial I/F와 MII I/F를 제공해 주기 때문에 임베디드 보드에 시리얼로 연결하거나 이더넷 포트에 다른 PC들을 연결해 무선 AP나 Gateway로 동작할 수도 있는 다기능 무선랜 모듈이다.

상세 사양은 다음과 같다.

Detailed Technical Specification


610wi는 3가지 동작 모드를 가지고 있다.


이 중 여기서는 mbed 모듈을 무선랜에 연결하는게 목적이기 때문에 client mode를 사용할 것이다.

모듈의 Factory Default 설정은 다음과 같다.


핀 배치는 다음과 같다.


두개의 커넥터(J19, J20)가 있지만 J19(36핀)으로만 신호선이 나오고 J20(20핀)은 단지 보드 고정을 위해 있을 뿐 신호선은 연결되어 있지 않다.

모듈 소형화를 위해 어쩔 수 없는 선택이긴 해도 DIY를 하는 사람들에게 치명적인 단점이 하나 있는데 커넥터의 피치가 일반적으로 많이 사용하는 100mil(2.54mm)이 아닌 50mil(1.27mm)라는 것이다. 직접 전용 PCB를 뜨지 않는 한 만능기판에 실장하기도 힘들고 1.27피치의 커넥터나 만능기판은 구하기도 힘들고 가격도 비싸다.
가능하면 J19, J20 모두 신호선으로 사용하는 대신 100mil(2.54mm) 피치의 커넥터를 사용했으면 더 좋았을거라고 생각한다.

하지만 땜질을 하다 보니 1.27피치가 2.0피치보다는 훨씬 더 좋다는걸 알게 되었다.

 Pin No.
Name
I/O
Description
 Pin No.
Name
I/O
Description
 1 CTS I UART:CTS 2 RTS O UART:RTS
 3 -   4 HW_trig I HW Trigger
 5 GPIO7 I/O Reserved 6 GPIO5 I/O Reserved
 7
 SOUT O UART:Tx 8 SIN I UART:Rx
 9 DC_IN  3.3V 10 DC_IN  3.3V
 11 GND   12 GND  
 13 RxERR I MII Rx Data Error
 14 COL I MII Collision
 15 /W_LEDO
 Wireless LED
 16 MDC I SMI CLK
 17 /RESET I RESET 18 MDIO I/O SMI I/O Data
 19 GND   20 GND  
 21 RXC I MII Rx CLK
 22 RXDV I MII Rx Data Valid
 23 RXD2 I MII Rx Data 24 RXD0 I MII Rx Data
 25 RXD1 I MII Rx Data 26 RXD1 I MII Rx Data
 27 GND   28 GND  
 29 TXC O MII Tx CLK
 30 TXEN O MII Tx Enable
 31 TDX3 O MII Tx Data
 32 TDX2 O MII Tx Data
 33 TDX0 O MII Tx Data 34 TDX1 O MII Tx Data
 35 GND   36 CRS I Carrier Sense

* 모든 신호레벨은 3.3V
* RESET은 최소 1.2usec이상 L로 유지해 줘야 함. 3초 이상 L로 유지되면 factory reset 됨
* HW Trigger가 L면 serial command mode로 들어가고 H면 serial command mode에서 빠져나옴

이 중 시리얼 포트를 통해 연결하는 경우 SIN, SOUT, DC_IN, GND, HW_Trig, /RESET만 있으면 된다.

일반적으로 사용하는 2.54 피치의 만능기판에 직접 연결할 수가 없기 때문에 디바이스마트에서 1.27피치의 만능기판을 구입하여 시리얼 인터페이스를 위해 필요한 핀만 연결해 주었다.


6핀만 연결하여 2.54피치의 핀헤더로 연결하여 mbed 보드와 커넉터로 연결할 생각이다.
보드 제작이 끝나고 나면 방향에 맞게 모듈을 꼽아주면 된다.




전원이 들어온걸 눈으로 쉽게 확인할 수 있도록 녹색의 LED를, 무선랜에 연결된 상태를 확인할 수 있도록 노란색의 LED를 보드에 실장해 주었다.


이 상태에서 보드에 전원을 공급하면 factory default로 설정되어 있는 Access Point 모드로 동작하게 되기 때문에 PC에서 확인해 보면 SSID가 WLANAP인 무선랜이 보이게 된다.

* 무선랜 모듈에 흐르는 전류가 크기 때문에 (최대 450mA) 충분한 전류를 흘려줄 수 있는 전원소스를 사용하여야 한다.


모듈의 IP주소는 192.168.1.254니까 WLANAP에 연결한 다음 컴퓨터의 IP를 수동으로 192.168.1.x 로 맞춰주고 브라우져에서 192.168.1.254로 접속하면 된다.
그럼 아이디와 암호를 물어보는데 디폴트는 admin/admin이다.


이 중 시리얼 포트의 상세 설정은 다음과 같다.


목적대로 사용하기 위해서는 먼저 Wireless settings에서 동작모드를 client mode로 변경해 줘야 한다.


Client 모드로 변경한 다음 client 모드의 설정을 변경하기 위해 'Setup' 버튼을 눌러주었다.


'Setup' 버튼을 누르면 설정 변경을 위해 잠시 기다려야 한다.


문제는 설정이 변경되면 디바이스 타입이 AP에서 무선랜 클라이언트 디바이스가 되기 때문에 무선 네트웍이 사라져서 아래와 같이 홈페이지를 억세스 할 수 없게 된다.


이 시점부터는 시리얼 케이블을 연결해서 설정을 변경해 주어야 한다.


디폴트 설정대로 38400 bps로 속도를 설정하고 시리얼 터미널을 연결하면 위와 같이 명령을 입력할 수 있게 된다. 명령어에 대한 상세한 설명은 메뉴얼을 참고하면 된다.

설정을 끝내고 <DI> 명령에서 보이는 네트웍들중에 하나에 연결시켜 주면 된다.

실제 설정 명령어들과 테스트 결과는 다음 포스트에 올리도록 하겠다.

2010년 10월 15일 금요일

안드로이드 에뮬레이터 네트워킹 설정 (Android Emulator Networking)

Network address space

실행되는 각 에뮬레이터 인스턴스는 개발머신의 네트웍 인터페이스나 설정과 분리된 가상 라우터/방화벽 뒤에서 실행된다. 에뮬레이트 되는 디바이스는 개발머신이나 다른 에뮬레이터 인스턴스를 볼 수가 없다. 이더넷을 통해 라우터/방화벽에 연결된걸로만 보이게 된다.

각 인스턴스를 위한 가상 라우터는 10.0.2/24 네트웍 주소 공간을 관리한다. - 라우터가 관리하는 모든 주소는 10.0.2.<xx>의 형태를 가지고 여기서 <xx>는 숫자이다. 이 공간내의 주소는 에뮬레이터/라우터에 의해 다음과 같이 미리 할당되어 있다.

Network Address      Description
-------------------  ------------
10.0.2.1             Router/gateway address
10.0.2.2             Special alias to your host loopback interface (i.e., 127.0.0.1 on your development machine)
10.0.2.3             First DNS server
10.0.2.4 / 10.0.2.5 / 10.0.2.6 Optional second, third and fourth DNS server (if any)
10.0.2.15            The emulated device's own network/ethernet interface
127.0.0.1            The emulated device's own loopback interface 

실행되는 모든 에뮬레이터 인스턴스는 동일한 주소 할당을 사용하고 있음을 주의해라. 즉 한 개발머신에 두개의 에뮬레이터 인스턴스가 동시에 실행되고 있을 때 각각의 에뮬레이터는 자신의 라우터를 가지고 그 라우터 뒤에서 동일한 IP 주소인 10.0.2.15를 가지게 된다. 에뮬레이터 인스턴스는 라우터로 분리되고 서로를 볼 수 없다.

또한 개발머신의 127.0.0.1은 에뮬레이터의 자체 루프백 인터페이스에 대응한다. 개발머신의 루프백 인터페이스(127.0.0.1)에서 실행되고 있는 서비스를 억세스 하고 싶으면 127.0.0.1 대신 특별 주소인 10.0.2.2를 사용해야 한다.

마지막으로 각 에뮬레이터 디바이스의 미리 할당된 주소들은 안드로이드 에뮬레이터에 한정된 것이고 실제 디바이스에서는 완전히 다를 수도 있음을 주의해야만 한다.


Local Networking Limitations

각 에뮬레이터 인스턴스는 가상 라우터 뒤에서 실행되지만 실제 라우터를 통해 인터넷에 연결되는 실제 디바이스와는 다르게 에뮬레이터 디바이스는 물리 네트웍을 억세스 할 수 없다. 그 대신 개발머신상의 일반적 어플리케이션의 일부분으로 실행된다. 즉 개발머신에서 실행되는 다른 어플리케이션같이 동일 네트웍 제한을 받게 된다.

   * 에뮬레이터 디바이스와의 통신은 개발머신에서 실행되는 방화벽 프로그램에 의해 막힐 수도 있다.
   * 에뮬레이터 디바이스와의 통신은 개발머신이 연결되어 있는 다른 (물리적) 방화벽/라우터에 의해 막힐 수도 있다.

에뮬레이터의 가상 라우터는 개발머신의 네트웍 환경이 허용해주면 모든 outbound TCP/UDP 커넥션/메시지를 에뮬레이터 디바이스의 대리로 처리할 수 있어야 한다. 호스트 OS와 네트웍에 의해 제한되지 않으면 포트번호나 범위에 대한 별다른 제한은 없다.

환경에 따라 에뮬레이터는 다른 프로토콜을 지원하지 않을 수 있다. 현재 에뮬레이터는 IGMP나 멀티캐스트를 지원하지 않는다


Using Network Redirections

가상 라우터 뒤에 있는 에뮬레이터 인스턴스와 통신하기 위해서는 가상 라우터의 네트웍 리다이렉션을 설정해 줘야 한다. 그러면 클라이언트는 라우터의 지정된 게스트 포트로 연결하고 라우터가 그 포트로 오가는 트래픽을 에뮬레이터 디바이스의 호스트 포트로 포워딩하게 된다.

네트웍 리다이렉션을 설정하려면 에뮬레이터 인스턴스에서 호스트/게스트 포트/주소를 매핑해야 한다. 네트웍 리다이렉션을 설정하는데 두가지 방법이 있다. 에뮬레이터 콘솔 명령어를 사용하거나 ADB 툴을 사용하면 된다.


Setting up Redirections through the Emulator Console

각 에뮬레이터 인스턴스는 그 인스턴스에 명령어를 실행할 수 있도록 연결할 수 있는 제어 콘솔을 제공한다. 리다이렉션을 설정하려면 redir 콘솔 명령어를 사용할 수 있다.

먼저 타겟 에뮬레이터 인스턴스의 콘솔 포트 번호를 알아야 한다. 예를 들어 첫번째 실행된 에뮬레이터 인스턴스를 위한 콘솔포트는 5554이다. 다음 명령어를 사용해서 타겟 에뮬레이터 인스턴스를 위한 콘솔에 연결한다.

% telnet localhost 5554

연결되면 redir 명령어를 사용해서 리다이렉션을 설정할 수 있다. 리다이렉션을 추가하려면 다음과 같다.

redir add <protocol>:<host-port>:<guest-port>

여기서 <protocol>은 tcp 또는 udp, <host-port>와 <guest-port>는 각각 개발머신과 에뮬레이터 시스템간의 매핑을 설정한다.

예를 들어 호스트(개발머신)의 127.0.0.1:5000으로 들어오는 모든 TCP 커넥션을 에뮬레이터 디바이스의 10.0.2.15:6000으로 리다이렉션 하도록 설정하려면 다음의 명령어를 사용하면 된다.

redir add tcp:5000:6000

리다이렉션을 삭제하려면 redir del 명령어를 사용하면 된다. 특정 에뮬레이터 인스턴스의 모든 리다이렉션 목록을 보고 싶으면 redir list명령어를 사용한다.

포트번호는 로컬 환경에 의해 제한된다는걸 주의해라. 즉 일반적으로 관리자 권한이 없으면 1024번 이하의 포트 번호는 사용할수가 없다. 또한 호스트의 다른 프로세스가 이미 사용하고 있는 호스트 포트를 리다이렉션 하려고 하면 redir은 에러메시지를 발생한다.


Setting Up Redirections through ADB

ADB 툴로 포트 포워딩을 설정할수도 있다.  또한 현재 ADB는 ADB서버를 kill하는것 이외에는 리다이렉션을 없앨 수 있는 방법을 제공하지 않는다. 상세한 내용은 ADB 문서의 Forwarding Ports를 참고하면 된다.


Configuring the Emulator's DNS Settings

처음 시작될 때 에뮬레이터는 호스트가 현재 사용하고 있는 DNS 서버 목록을 읽는다. 그래서 그 목록중에 최대 4개까지의 서버 주소를 기록하고 각각의 주소를 에뮬레이터의 10.0.2.3, 10.0.2.4, 10.0.2.5, 10.0.2.6으로 alias한다.

리눅스와 OS X에서 에뮬레이터는 /etc/resolv.conf 파일을 파싱해서 DNS 서버 주소를 가져온다. 윈도우에서는 GetNetworkParams() API를 호출해서 주소를 얻어온다. 즉 에뮬레이터는 hosts 파일(리눅스나 OS X에서는 /etc/hosts, 윈도우는 %WINDOWS%\system32\HOSTS)의 내용을 무시한다는 것이다.

명령어 라인에서 에뮬레이터를 시작할 때 -dns-server <serverList> 옵션을 사용해서 수동으로 사용할 DNS 서버 주소를 지정할수도 있다. <serverList>는 콤마로 구분된 서버 이름 또는 IP 주소 목록이다.


Using the Emulator with a Proxy

에뮬레이터가 프락시 서버를 통해 인터넷을 억세스 해야 하는 경우 에뮬레이터를 시작할 때 -http-proxy <proxy> 옵션을 사용해 적절하게 리다이렉션 하도록 설정할 수 있다. 이 경우 <proxy>의 프락시 정보는 다음중의 한가지 포맷을 사용할 수 있다.

http://<machineName>:<port> 또는 http://<username>:<password>@<machineName>:<port>

-http-proxy 옵션은 에뮬레이터가 모든 outgoing TCP 커넥션을 지정된 HTTP/HTTPS 프락시를 사용하도록 해 준다. UDP 리다이렉션은 현재 지원하지 않는다.

다른 방법으로 http_proxy 환경변수에 <proxy>의 값을 넣어 정의할수도 있다. 이 경우 -http-proxy 명령어에 <proxy> 값을 지정할 필요가 없다. 에뮬레이터가 시작될 때 http_proxy 환경변수의 값을 확인해서 변수값이 정의되어 있으면 자동으로 그 값을 사용한다.

프락시 커넥션 문제를 검사하려면 -debug-proxy 옵션을 사용할 수도 있다.


Interconnecting Emulator Instances

하나의 에뮬레이터 인스턴스가 다른 인스턴스와 통신을 할 수 있도록 하려면 다음과 같이 필요한 네트웍 리다이렉션을 설정해 줘야만 한다.

환경이 다음과 같다고 가정하겠다.

   * A는 개발머신(호스트)
   * B는 A에서 실행되는 첫번째 에뮬레이터 인스턴스
   * C는 A에서 실행되는 두번째 에뮬레이터 인스턴스

그리고 C가 연결할 서버는 B에서 실행한다고 가정한다.

  1. B에서 서버를 설정해서 10.0.2.15:<serverPort>를 listen하도록 한다.
  2. B의 콘솔에서 A:localhost:<localPort>에서 B:10.0.2.15:<serverPort>로 리다이렉션을 설정한다.
  3. C에서 클라이언트가 10.0.2.2:<localPort>로 접속한다.
예를 들면 HTTP 서버를 실행하기를 원하는데 <serverPort>는 80번으로 하고 <localPort>는 8080으로 하는 경우의 설정은 다음과 같다.

   * B는 10.0.2.15:80을 listen
   * B의 콘솔에서 redir add tcp:8080:80 을 실행
   * C가 10.0.2.2:8080으로 접속

2010년 10월 14일 목요일

CdS 광센서 사용하기 (Using CdS photoresistor)


CdS는 빛의 세기에 따라 저항값이 변하는 센서이다.

밝기에 따른 저항값의 변화는 다음과 같다. 참고로 y-축(저항값)은 로그 스케일이다.

아뒤노등의 마이크로 컨트롤러에 연결할 때는 다음과 같이 회로를 구성하고 V1을 마이크로컨트롤러의 Analog input에 연결해주면 된다.




위와 같은 회로일 때 출력 전압값을 구하는 공식은 다음과 같다.

여기서는 Vcc=5V, R1=10Kohm을 사용하고 있기 때문에 그 값을 위의 식에 대입해주면 된다.



즉 10 Lux정도의 밝기라 CdS의 저항값이 10Kohm인 경우 V1은 2.5V가 되고 매우 어두워서 CdS의 저항값이 200Kohm인 경우 V1은 4.76V가 된다.

2010년 10월 12일 화요일

Parcelable을 사용한 오브젝트 전달 (Object serialization using Parcelable)



앱을 만들다 보면 인텐트를 통해 단순히 String, int, boolean 같은 기본 타입 뿐 아니고 커스텀 클래스나 오브젝트를 다른 컴포넌트에 전달해 줘야 할 경우가 많다. 그 경우 단순히 그냥 인텐트에 putExtra() 로는  넣어줄 수가 없다.
안드로이드에서는 그런 경우를 위해 자바의 Serialization 개념과 유사한 Parcelable이라는 클래스가 있다.

먼저 이런것이 왜 필요한가 살펴보겠다. 예를 들어 다음과 같은 클래스가 있다고 하자.

public class BookData {
  int _id;
  String title;
  String author;
  String publisher;
  int price;
}

도서관리 앱에서 ListView로 화면에 표시하기 위해 ArrayList<BookData>에 책들의 정보를 넣어 인텐트로 넘겨주려고 하면 BookData 클래스를 그대로 사용할수는 없다.

오브젝트를 Parcelable 클래스로 만들어 주려면 android.os.Parcelable 인터페이스를 구현해야 한다. 그러므로 아래와 같이 클래스 정의를 변경한다.

public class BookData implements Parcelable {
  int _id;
  String title;
  String author;
  String publisher;
  int price;
}

그리고 android.os.Parcelable 인터페이스에 있는 2개의 메소드를 오버라이드 해 줘야만 한다.

describeContents() - Parcel 하려는 오브젝트의 종류를 정의한다.
writeToParcel(Parcel dest, int flags) - 실제 오브젝트 serialization/flattening을 하는 메소드. 오브젝트의 각 엘리먼트를 각각 parcel해줘야 한다.

public void writeToParcel(Parcel dest, int flags) {
  dest.writeInt(_id);
  dest.writeString(title);
  dest.writeString(author);
  dest.writeString(publisher);
  dest.writeInt(price);
}

다음으로 해야 할 일은 Parcel에서 데이터를 un-marshal/de-serialize하는 단계를 추가해줘야 한다. 그러기 위해서 Parcelable.Creator 타입의 CREATOR라는 변수를 정의해야 한다. 이 변수를 정의하지 않으면 안드로이드는 다음과 같은 익셉션을 발생한다.

Parcelable protocol requires a Parcelable.Creator object called CREATOR

아래는 위의 예제인 BookData 클래스를 위한 Parcelable.Creator<BookData>의 코드이다.

public class CustomCreator implements Parcelable.Creator<BookData> {
  public BookData createFromParcel(Parcel src) {
    return new BookData(src);
  }

  public BookData[] newArray(int size) {
    return new BookData[size];
  }
}

BookData.java에 모든 parcel된 데이터를 복구하는 생성자를 정의해 줘야만 한다.

  public BookData(Parcel src) {
    _id = src.readInt();
    title = src.readString();
    author = src.readString();
    publisher = src.readString();
    price = src.readInt();
  }

주의할것은 writeToParcel() 메소드에서 기록한 순서와 동일하게 복구해야만 한다.

전체 코드는 다음과 같다.

...
public class BookData implements Parcelable {
    private String title;
    private String author;
    private String publisher;
    private String isbn;
    private String description;
    private int price;
    private String photoUrl;
   
    public BookData() {
    }
   
    public BookData(Parcel in) {
       readFromParcel(in);
    }

    public BookData(String _title, String _author, String _pub, String _isbn, String _desc, int _price, String _photoUrl) {
         this.title = _title;
         this.author = _author;
         this.publisher = _pub;
         this.isbn = _isbn;
         this.description = _desc;
         this.price = _price;
         this.photoUrl = _photoUrl;
    }

// -------------------------------------------------------------------------
// Getters & Setters section - 각 필드에 대한 get/set 메소드들
// 여기서는 생략했음

// ....
// ....
// -------------------------------------------------------------------------
  

   public void writeToParcel(Parcel dest, int flags) {
           dest.writeString(title);
           dest.writeString(author);
           dest.writeString(publisher);
           dest.writeString(isbn);
           dest.writeString(description);
           dest.writeString(photoUrl);
           dest.writeInt(price);
   }

   private void readFromParcel(Parcel in){
           title = in.readString();
           author = in.readString();
           publisher = in.readString();
           isbn = in.readString();
           description = in.readString();
           photoUrl = in.readString();
           price = in.readInt();
   }
   
   public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        public BookData createFromParcel(Parcel in) {
             return new BookData(in);
       }

       public BookData[] newArray(int size) {
            return new BookData[size];
       }
   };
}

Parcelable 오브젝트를 인텐트로 보내는 경우는 다음과 같이 하면 된다.

BookData book = new BookData();
// 각 필드에 값을 넣어줌

Intent i = new Intent(this, ShowBook.class);
i.putExtra("bookInfo", book);
startActivity(i);

인텐트를 받을 ShowBook.java에서는 다음과 같이 Parcelable 오브젝트를 복구하면 된다.

Bundle bundle = getIntent().getExtras();
BookData book = bundle.getParcelable("bookInfo");

ArrayList<BookData>인 경우는 Intent를 만들어 보내는 쪽에서는 다음과 같이 하면 된다.

ArrayList<BookData> bookList = new ArrayList<BookData>();
...
// bookList.add() 메소드를 사용해서 bookList에 BookData 엔트리를 추가
...

Intent i = new Intent(this, BookList.class);
i.putParcelableArrayListExtra("myBooks", bookList);
startActivity(i);

BookList.java (인텐트에 의해 호출되는 액티비티)에서는 다음과 같이 오브젝트를 복구하면 된다.

ArrayList<BookData> bookList;
...
Intent i = getIntent();
bookList = i.getParcelableArrayListExtra("myBooks");




2010년 9월 13일 월요일

커스텀 ArrayAdapter 사용하기 (Use custom ArrayAdapter)

이전 포스트에서 /res/layout에 레이아웃 xml 파일을 사용하여 ListView의 레이아웃을 변경해 보았었다.
하지만 그 경우에도 ListView의 각 엔트리에 한개의 값만 넣을 수 있었고 텍스트 뷰 이외를 사용하지 못했었다. 그래서 이번 포스팅에서는 ArrayAdapter를 상속받은 커스텀 ArrayAdapter를 만들고 getView() 메소드를 오버라이드 하여 TextView 외에 ImageView도 같이 집어넣어 보도록 하겠다.



간단한 주소록을 보여주는 앱을 만들것이므로 먼저 String 대신 주소록 엔트리를 위한 클래스를 정의한다.

/src/.../ABEntry.java

package app.arsviator;

public class ABEntry {
    private String name;
    private String phoneNo;
    private int photo;
   
    public ABEntry(String _name, String _pn, int _photo) {
        this.name = _name;
        this.phoneNo = _pn;
        this.photo = _photo;
    }
   
    public String getName() {
        return name;
    }

    public String getPhoneNo() {
        return phoneNo;
    }

    public int getPhotoId() {
        return photo;
    }
}

위의 소스코드를 src 디렉토리에 ABEntry.java 란 이름으로 저장해주면 된다.

그 다음은 ListView의 각 엔트리를 위한 레이아웃을 정의한다.


여기서는 위와 같이 레이아웃을 정의했다.

/res/layout/entry.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:paddingTop="5px"
    android:paddingBottom="5px"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >
  <ImageView android:id="@+id/ePhoto"
    android:layout_width="48px"
    android:layout_height="48px"
    android:src="@drawable/nophoto" />
  <LinearLayout android:orientation="vertical"
      android:paddingLeft="10px"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView android:id="@+id/eName"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="22sp" />
    <TextView android:id="@+id/ePhoneNo"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="16sp" />
  </LinearLayout>
  <TextView android:id="@+id/eNull"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content" />
</LinearLayout>

위의 소스를 /res/layout/entry.xml 로 저장해주면 된다.

    private class ABArrayAdapter extends ArrayAdapter<ABEntry> {
        private ArrayList<ABEntry> items;
        private int rsrc;
       
        public ABArrayAdapter(Context ctx, int rsrcId, int txtId, ArrayList<ABEntry> data) {
            super(ctx, rsrcId, txtId, data);
            this.items = data;
            this.rsrc = rsrcId;
        }
       
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            View v = convertView;
            if (v == null) {
                LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                v = li.inflate(rsrc, null);
            }
            ABEntry e = items.get(position);
            if (e != null) {
                ((TextView)v.findViewById(R.id.eName)).setText(e.getName());
                ((TextView)v.findViewById(R.id.ePhoneNo)).setText(e.getPhoneNo());
                if (e.getPhotoId() != -1) {
 ((ImageView)v.findViewById(R.id.ePhoto)).setImageResource(e.getPhotoId());               
                } else {
 ((ImageView)v.findViewById(R.id.ePhoto)).setImageResource(R.drawable.nophoto); 
                }
            }
            return v;
        }
    }
}

이번 포스팅의 가장 핵심 부분인 ArrayAdapter를 상속받은 ABArrayAdapter 클래스이다. 오버라이드 한 getView() 안에서 우선 LayoutInflator를 사용해서 /res/layout/entry.xml을 View로 inflate한다.

LayoutInflater li = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = li.inflate(rsrc, null);

그 다음 ABEntry 오브젝트에 있는 이름, 전화번호, 사진을 각각 eName, ePhoneNo, ePhoto에 넣어준다. 사진이 없는 경우(e.getPhotoId() == -1)는 디폴트 사진(여기서는 강아지 그림)을 표시하고 사진이 지정되어 있는 경우는 /res/drawable에 있는 사진을 가져오도록 해 놓았다.

CustomAA.java의 전체 소스는 다음과 같다.

펼쳐두기..


그 이외에 필요한 파일은 다음과 같다.

펼쳐두기..


프로젝트에 필요한 이미지 파일은 아래 zip 파일을 다운받아 res 디렉토리에서 압축을 풀면 drawable디렉토리 아래에 만들어진다.



실행하면 위에서같이 간단한 주소록이 나오게 되고 그 중 한 항목을 선택하면 전화를 걸거나 항목을 삭제할 수 있도록 AlertDialog가 나오게 된다.


전화를 걸기 위해서는 AndroidManifest.xml  파일에 CALL_PHONE permission을 추가하는걸 잊으면 안된다.

펼쳐두기..