레이블이 안드로이드인 게시물을 표시합니다. 모든 게시물 표시
레이블이 안드로이드인 게시물을 표시합니다. 모든 게시물 표시

2015년 10월 1일 목요일

안드로이드 BluetoothLeScanner 클래스 사용하기



안드로이드에서 Bluetooth LE (Low Energy)를 사용할 때 가장 기본적인 샘플 예제 코드로 https://github.com/googlesamples/android-BluetoothLeGatt 를 많이 사용한다.

하지만 API level 21부터는 API가 약간 바뀌어 LE 디바이스를 스캔할 때 사용하는 BluetoothAdapter 클래스의 startLeScan(), stopLeScan() 메소드가 deprecated 되어 빌드해 실행하는데 문제는 없어도 android studio에서 소스를 보면  mBluetoothAdapter.startLeScan(mLeScanCallback); 과 mBluetoothAdapter.stopLeScan(mLeScanCallback); 부분에 메소드 이름에 빨간색 줄이 쳐져 있는걸 보게 될 것이다.
API level 21부터는 BluetoothAdapter 클래스에서 스캔을 하지 않고 BluetoothLeScanner라는 클래스가 추가되어 이 클래스의 startScan(), stopScan() 메소드를 사용해 BLE 디바이스를 검색하도록 변하였다.
콜백 역시 Bluetooth.LeScanCallback 대신 ScanCallback 클래스를 사용해야 한다.

그러므로 android-BluetoothLeGatt 예제를 API level 21의 변화에 맞게 수정하려면 다음과 같다.

먼저 사용할 클래스  import를 추가한다. DeviceScanActivity.java 소스코드 위쪽의 import 부분에 아래 내용을 추가한다.

import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import java.util.List;

다음 DeviceScanActivity 클래스의 private 멤버 선언부에 밑줄 친 빨간색 내용을 추가한다.

public class DeviceScanActivity extends ListActivity {
    private LeDeviceListAdapter mLeDeviceListAdapter;
    private BluetoothAdapter mBluetoothAdapter;
    private boolean mScanning;
    private Handler mHandler; 
    private BluetoothLeScanner mBLEScanner;

다음 onCreate 메소드의 맨 뒤쪽 부분에 다음 줄을 추가해 BluetoothLeScanner 클래스의 인스턴스를 얻는다.

    if (mBluetoothAdapter == null) {
        Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
        finish();
        return;
    }
    mBLEScanner = mBluetoothAdapter.getBluetoothLeScanner();
    // Checks if Bluetooth LE Scanner is available.    if (mBLEScanner == null) {
        Toast.makeText(this, "Can not find BLE Scanner", Toast.LENGTH_SHORT).show();
        finish();
        return;
    }
}

다음은 onListItemClick() 메소드와 scanLeDevice() 메소드 안에 있는 빨간색 줄이 쳐져 있는 startLeScan(), stopLeScan() 부분을 각각 다음 내용으로 바꿔준다.

mBLEScanner.startScan(mScanCallback); // mBluetoothAdapter.startLeScan() 부분
 
mBLEScanner.stopScan(mScanCallback);  // mBluetoothAdapter.stopLeScan() 부분

그리고 기존의 콜백 정의부분은 없애고 다음의 내용을 추가한다.

즉 아래 부분은 통채로 삭제

// Device scan callback.private BluetoothAdapter.LeScanCallback mLeScanCallback =        new BluetoothAdapter.LeScanCallback() {
    @Override    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {        runOnUiThread(new Runnable() {            @Override            public void run() {                mLeDeviceListAdapter.addDevice(device);                mLeDeviceListAdapter.notifyDataSetChanged();            }        });    }};
 
위쪽 내용을 삭제한 부분에 아래 내용을 추가


    private ScanCallback mScanCallback = new ScanCallback() {
        @Override        public void onScanResult(int callbackType, ScanResult result) {
            processResult(result);
        }

        @Override        public void onBatchScanResults(List<ScanResult> results) {
            for (ScanResult result : results) {
                processResult(result);
            }
        }

        @Override        public void onScanFailed(int errorCode) {
        }

        private void processResult(final ScanResult result) {
            runOnUiThread(new Runnable() {
                @Override                public void run() {
                    mLeDeviceListAdapter.addDevice(result.getDevice());
                    mLeDeviceListAdapter.notifyDataSetChanged();
                }
            });
        }
    };
















2015년 8월 20일 목요일

Blynk를 사용해 아두이노에서 IoT 맛보기

Blynk (http://www.blynk.cc)를 사용하면 아두이노를 스마트폰이나 PC와 연결하여 매우 쉽게 제어가 가능해진다. 물론 아두이노 뿐 아니고 ESP8266이나 Raspberry Pi등에서도 사용이 가능하다. 현재 사용할 수 있는 하드웨어 목록은 http://community.blynk.cc/t/hardware-supported-by-blynk/16 에서 확인할 수 있다.


여기서는 아두이노 우노에 CC3000 Wifi shield를 사용해 보도록 한다.



먼저 CC3000 Wifi shield를 위해 Adafruit_CC3000 Library를 설치해준다. https://github.com/adafruit/Adafruit_CC3000_Library 에서 다운받을 수 있다. (라이브러리 설치 방법은 이전 포스트 (http://arsviator.blogspot.kr/2015/07/install-library-in-arduino.html)

다음은 Blynk 라이브러리를 설치해 줘야 한다. https://github.com/blynkkk/blynk-library 에서 다운받으면 된다.

정상적으로 설치를 완료했으면 아두이노를 재시동하고 예제 -> Blynk -> BoardsAndShields 에서 CC3000을 선택한다.


코드 구조는 매우 간단하다. 단 코드중에 아래 표시한 부분은 각자 자신에 맞게 변경해 줘야만 한다.


일단 여기까지 진행하였으면 아두이노는 일단 잠시 놔두고 스마트폰을 설정해 준다.



Blynk 홈페이지에 보면 iOS와 안드로이드용 앱을 다운받을 수 있는 링크가 있다. 자신의 폰에 맞는걸 선택해 자신의 폰에 Blynk 앱을 설치해 준다.


앱을 실행해 서비스에 가입해주면 된다. 가입 후 로그인 되면 다음과 같은 초기화면을 볼 수 있을 것이다.


Create New Project를 눌러 새 프로젝트를 생성한다.


프로젝트 이름(여기서는 FirstTest)을 써주고, 사용할 보드(여기서는 아두이노 우노)를 선택한다. 그 아래쪽의 Auth Token이 아두이노 스케치 코드에 넣어줘야 하는 값이다. E-mail 버튼을 누르면 가입시 사용한 메일주소로 이 토큰값이 메일로 보내지기 때문에 메일에서 복사해 붙이면 된다. 이제 Create Project 버튼을 눌러주면 프로젝트가 만들어진다.


이제 화면을 디자인해야 하므로 화면 검은색 부분중에 원하는 부분을 터치한다.


그럼 위와 같이 설치할 수 있는 위젯 목록이 나타난다. 여기서는 단순히 아두이노에 있는 LED on/off를 제어하기 위해 Button을 선택한다.


원하는 위치에 버튼이 추가되었다. 이제 버튼의 설정을 변경해 주기 위해 버튼을 터치한다.


버튼 설정 화면이다. 여기서 버튼의 이름(여기서는 LED)을 지정하고, 이 버튼이 아두이노의 어떤 핀과 연동될 것인지 알려준다. (아두이노 보드에 붙어있는 LED는 D13에 연결되어 있으므로 Digital에 D13을 선택)


설정이 끝나면 스위치의 이름과 연동된 핀 번호가 스위치에 표시된다. 디자인이 다 끝나고 실제 동작을 시키려면 맨 오른쪽 위의 삼각형 버튼을 선택해주면 된다.


아두이노 코드에 Auth Token 값을 넣어주고, setup() 함수에 pinMode(13, OUTPUT); 을 추가한 다음 아두이노에 업로드 하면 된다.

업로드 후 시리얼 모니터를 열어봤을 때 다음과 같이 출력되면 된다.


이제 스마트폰의 버튼을 눌러보면 그에 따라 아두이노의 LED가 on/off 되는걸 볼 수 있을 것이다.

Blynk에는 단순히 스위치 뿐 아니고 슬라이드, 조이스틱, 그래프등의 다양한 위젯이 있어 스마트폰에서 아두이노에 연결된 액츄에이터를 제어하거나, 반대로 아두이노에 연결되어 있는 센서들의 상태를 다양한 방법으로 확인이 가능하다. 또한 Push notification이나 Twitter 위젯도 있어 아두이노가 특정 상태가 되면 폰으로 push 메시지를 보내거나 트위터에 메시지를 트윗하는것도 가능하다. 다음 포스트에서 blynk를 좀 더 다양하게 사용하는걸 다뤄보도록 하겠다.

























2011년 2월 25일 금요일

Motorola XOOM 태블릿 체험(?)기



일단 만져본 느낌으로는 아이패드보다 무겁다. 그리고 아이패드에 베젤이 크다고 말이 많았는데 XOOM의 경우 아이패드보다 베젤이 좁아 한 손으로 잡는 경우 약간 불안한 느낌이 들었다. 안정적으로 잡기 위해 손을 좀 안쪽으로 하면 손이 그렇게 큰 편이 아닌데도 의도하지 않은 터치가 일어난다.




하드웨어 버튼은 뒷면의 렌즈 옆쪽에 있는 전원버튼과 옆쪽의 볼륨버튼 2개가 전부이다. Back, Home등의 모든 버튼은 소프트버튼이 되어 버렸다.


화면 좌측 하단 맨 오른쪽의 Space(?) 버튼을 누르면 아래와 같이 맥에서 space 개념같은 가상 화면을 선택할 수 있다.


아래는 Setttings 화면이다. 스크린이 커진 덕에 왼쪽의 메뉴항목을 선택하면 그 항목의 서브항목이 오른쪽에 나타난다.


윈도우와 비슷하게 좌측 하단의 시계가 나오는 부분을 누르면 조그마한 창이 열려 현재 음악 플레이어의 상태와 시간, 배터리 잔량, 무선랜 연결상태 등을 자세히 볼 수 있게 된다.


캘린더 화면이다. 기본화면은 weekly view로 설정되어 있는거 같았다. 그리고 화면 우측 상단에 메뉴버튼이 있다. 항상 나오는건 아니고 메뉴가 있을때만 나타난다.

주소록이다. 역시 화면이 크니 한 눈에 들어오는게 보기 편했다.


카메라 앱을 구동한 화면이다.


음악 플레이어 앱이다. 크게 인터페이스가 달라지지는 않았지만 역시 화면이 커지니 앨범 브라우징 하는데 훨씬 편하다.


화면 우측 상단 메뉴버튼 옆에 있는 버튼을 누르면 나오는 화면인데 여기서 위젯, 월페이퍼, 앱 숏컷등을 설정할 수 있다.


Books 앱을 실행해보려고 했지만 계정이 로그인 되지 않으면 아예 동작을 하지 않아 확인을 해 볼 수가 없었다.

아래쪽 포트는 다음과 같다. 좌측부터 마이크로 USB, 외부 비디오 출력, 독에 연결할 때 전원 공급을 위한 접점, 아답터 커넥터 순이다.


매장에 전시되어 있는걸 혼자 동작시켜보며 한손으로 사진을 찍다보니 화면에 손자국도 많고 사진 품질도 엉망이긴 해도 대강 보는데 큰 문제는 없으리라 본다.

만져본 소감은 확실히 UI도 많이 부드러워 지고 타블릿용으로 많이 다듬은게 느껴졌다. 다만 XOOM의 경우 실질적으로 하드웨어 버튼이 하나도 없는 셈이라 어딘가 좀 아쉬웠다. 또한 아이패드에 비해 비싼 가격(약정 없이 $799/Verizon 2년 약정하면 $599, 아이패드 3G 32GB모델의 경우 $729), 무게등은 약점이다. 그리고 시간이 가면 차이가 줄어들긴 하겠지만 아직까진 안드로이드 타블릿 전용앱이 거의 없는점 등이 문제이다. 안드로이드 타블릿을 구입했는데 동작시킬 수 있는 앱은 거의 스마트폰용이라면 타블릿의 장점이 많이 희석되어 버릴것이다.

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년 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을 추가하는걸 잊으면 안된다.

펼쳐두기..







ListView의 레이아웃 변경 (Change Layout for ListView)

안드로이드에서는 ListView가 화면에 보이는 레이아웃을 XML로 정의할 수 있다.

대부분의 책에 보면 ListView의 예제 코드에서는 android.R.layout.simple_list_item_1을 사용하고 있다. 그 경우 레이아웃은 아래와 같다.

    adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, R.id.listentry, data);
 
    setListAdapter(adapter);


android.R로 시작하는 경우 안드로이드에 기본으로 들어있는 리소스들을 참조하는 것이다. 안드로이드에 기본으로 들어있는 layout의 목록은 다음과 같다.

android.R.layout.activity_list_item
android.R.layout.browser_link_context_header
android.R.layout.expandable_list_content
android.R.layout.preference_category
android.R.layout.select_dialog_item
android.R.layout.select_dialog_multichoice
android.R.layout.select_dialog_singlechoice
android.R.layout.simple_dropdown_item_1line
android.R.layout.simple_expandable_list_item_1
android.R.layout.simple_expandable_list_item_2
android.R.layout.simple_gallery_item
android.R.layout.simple_list_item_1
android.R.layout.simple_list_item_2
android.R.layout.simple_list_item_checked
android.R.layout.simple_list_item_multiple_choice
android.R.layout.simple_list_item_single_choice
android.R.layout.simple_spinner_dropdown_item
android.R.layout.simple_spinner_item
android.R.layout.test_list_item
android.R.layout.two_line_list_item


아래는 android.R.layout.simple_expandable_list_item_1을 사용한 경우이다.


아래는 android.R.layout.simple_expandable_list_item_single_choice를 사용한 경우이다.


안드로이드에 기본적으로 들어있는 레이아웃이 마음에 드는게 없으면 직접 레이아웃을 정의해 사용할 수도 있다.

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/listentry"
      android:textSize="16sp"
      android:textColor="#ff0000"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

위와같이 /res/layout 디렉토리에 xml파일을 넣어주고 ArrayAdapter를 만들때 레이아웃 파일을 지정해 주면 된다.

    adapter = new ArrayAdapter<String>(this, R.layout.simple_layout, R.id.listentry, data);
 
    setListAdapter(adapter);



레이아웃에 단순히 TextView 하나만 넣지 않고 컨테이너를 사용해서 여러 위젯을 집어넣을 수도 있다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >
  <TextView android:text="Airport:"
          android:textSize="14sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
  <TextView android:id="@+id/listentry"
          android:textSize="18sp"
          android:textColor="#ff0000"
          android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

    adapter = new ArrayAdapter<String>(this, R.layout.entrylayout, R.id.listentry, data);
 
    setListAdapter(adapter);








2010년 8월 30일 월요일

안드로이드 WebView에서 커스텀 scheme 사용하기 (Use custom scheme in WebView)


안드로이드에서는 WebView에서 링크에 tel: 같은 태그(<a href="tel:01034567890">)가 먹지 않는다. 이런 식으로 WebView가 지원하지 않는 커스텀 scheme을 지원하려면 WebViewClient를 사용하면 된다.

    private class CustomScheme extends WebViewClient {
       public boolean shouldOverrideUrlLoading(WebView view, String url) {
          ...
       }
   }

이런식으로 WebViewClient 오브젝트를 정의하고 WebView에 setWebViewClient() 메소드를 사용해 등록해 주면 된다. 그러면 WebView에 있는 링크가 클릭될 때 마다 WebViewClient 오브젝트의 shouldOverrideUrlLoading() 메소드가 호출된다.

  WebView wv = (WebView)findViewById(R.id.myWebView);
  wv.setWebViewClient(new CustomScheme());

shouldOverrideUrlLoading() 메소드가 호출될 때 클릭된 링크의 url 스트링이 argument로 넘겨지기 때문에 url중에서 scheme 부분을 비교해서 원하는 동작을 하도록 해 주면 된다.

        if (url.startsWith("tel:")) {
         Intent i = new Intent(Intent.ACTION_CALL, Uri.parse(url));
          startActivity(i);
          return true;
        }
        return false;