안녕하세요. 휴우 입니다;;; '최근 연락처 위젯' 은 휴대폰 홈 화면에 위치하는 아주 작은 위젯으로, 사용자가 가장 최근에 사용한 연락처 정보가 최대 10개 까지 리스트 형식으로 나타나는 무료 어플리케이션입니다. 사용하기 쉽고, 안드로이드의 기본 기능만을 활용하여 구현되어 있음으로 여러 가지 환경에서 잘 동작하지요. (그렇다고 믿고 싶습니다;;;)  설치 하시고 위젯을 등록하시면 음...그러니까 아주 깔끔한;;;; 디자인의 위젯이 나타납니다. 위젯에 표시되는 목록은 사용자의 주소록 정보에 따라 동적으로 변경되며, 연결할 사람의 이름을 터치하면 오른쪽 화면처럼 QuickContact 패널이 표시되도록 구현되어있습니다. (편리하겠조???)


 


 에... 쑥스러운 어플리케이션 광고는 여기까지하고;;;; 이전 작품(?) 로그 토스터와 마찬가지로, 최근 연락처 위젯은 아파치 라이센스 기반으로 소스가 공개된 1인 참가(ㅠㅠ) 오픈 소스 프로젝트입니다. 아래의 주소로 접근하시면, 웹상에서 직접 소스를 브라우즈 하실 수 있으며, SVN 클라이언트를 이용하시면 간편하게 전체 소스를 체크아웃 받으 실 수 있습니다.


코드 구글 호스팅 페이지:

http://code.google.com/p/huewu/source/browse/#svn/trunk/RecentContactsWidget


SVN 체크아웃 주소:

https://huewu.googlecode.com/svn/trunk/RecentContactsWidget

 음... 안드로이드 위젯 어플리케이션은 아이폰이나 다른 플랫폼 대비 가장 눈에 띄는 안드로이드 플랫폼만의 특징점 중의 하나로, 잘 사용하면 매우 훌륭할 거 같지만 실재로 구현하려고 하면 이리저리 헤매기 쉬운 녀석 중에 하나입니다. 저도, 위젯 어플리케이션을 처음 만들면서 나름 고생한 기억이 있는데요, 이와 관련되서 알게된 내용을 블로그에 두 차례에 나누어 포스팅하기도 했습니다. 추가로, 아주 아주 간단하지만 그래서 더 분석하기 좋은 '최근 연락처 위젯' 어플 소스를 공개하였으니, 위젯 어플리케이션 만들기에 입문하시는 분들에게 도움이 되었으면 좋겠네요. 공개한 소스 중에서 제 나름대로, 다른 분들에게 도움이 될지도 모르겠다고 생각하는 부분을 간단하게 정리해 보았습니다.

위젯 레이아웃 구성.
 위젯 어플을 만드는데 고생하는 원인 중의 99%는 바로 위젯 레이아웃 구성과 관련되지 않을까요? 가장 먼저, 위젯의 크기를 결정하는 부분부터 주위를 기울어야 합니다. 위젯의 크기는 일반적이 View 와는 달리 '셀(Cell)' 기반으로 나누어지며, 보통 홈 화면은 '4 x 4', 그러니까 총 16개의 셀로 이루어집니다. 여러분이 만들려고 하는 위젯의 크기를 위젯 프로바이더 상에 아무리 마음대로 지정하셨다고 하더라도, 여러분이 요청한 최소한의 크기를 만족하는 범위에서 셀 기반으로 결정되어집니다.


 즉, 예를 들어 개발자가 "1dp x 1dp" 짜리 위젯을 만들려고 하려고 해도, 해당 위젯은 최소한 1x1 셀의 공간 (위 그림이라면  80pixel x 100pixel..)을 차지하게 됩니다. 그런데, 셀의 크기는 단말마다 상이하기 때문에, 위젯에 사용되는 각종 이미지들은 '나인패치' 를 이용해서 작성하시는 편이 좋습니다. 또 한가지 레이아웃을 구성하실 때는 LinearLayout 의 layout_weight 속성을 적극 활용할 것을 추천 드립니다. 제가 아는한 안드로이드 상에서 유일하게 비율 기반으로 화면을 구성할 수 있는 방법이 바로 layout_weight 를 사용하는 것이며, 이를 사용하면 다양한 해상도를 지원하기 위한 걱정을 덜 수 있습니다. 그렇지 않으면 '갤S' 에서는 멀쩡히 보이는 화면이 다른 단말에서는 요상하게 찌그러져 보이는 등의 사태를 맞이하기 쉽상입니다.

스크롤 애니매이션 흉내내기.
 음...이번 위젯을 만들면서, 가장 트릭키한 부분이라면 바로 사용자가 위젯 내의 위/아래 버튼을 클릭했을 때, 마치 스크롤이 오르락 내리락 하는 듯한 애니매이션 효과를 주는 것 이었습니다. (나름 고생한 부분인데, 혹시, 이거 어떻게 구현했는지 신기하게 생각하신 개발자 분은 안계신가요;;; ㅎㅎ) 앱위젯에서 사용할 수 있는 View 의 종류에도 제한이 있으며, 호출 가능한 메서드에도 상당한 제약이 있습니다. 기본적으로 애니매이션 시작을 위한 startAnimation 메서드 등등도 활용 할 수 없지요. 대신 ViewGroup 내 자식 View 들이 처음 등록될 때, 시작되는 LayoutAnimation 을 레이아웃 파일내에 선언해 두니 잘 동작하더군요.

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName cpName = new ComponentName(context, RecentContactsWidget.class);
if(stillviews != null)
appWidgetManager.updateAppWidget(cpName, stillviews);
if(aniViews != null) //need to animate views.
appWidgetManager.updateAppWidget(cpName, aniViews);
 따라서, 위 처럼 아주 간단한 트릭이 활용되었습니다. 소스를 받아보시면 알 수 있지만, '최근 통화 위젯' 은 세 종류의 레이아웃 - 정지. 위로 움직이는 놈. 아래로 움직이는 놈. 을 갖고 있습니다. 그리고 사용자가 위로 가기 버튼을 누르는 순간! updateAppWidget 메서드를 통해, 우선 정지 레이아웃을 설정하고, 그 이후에 움직이는 레이아웃을 업데이트 해줍니다. AppWidgetManager  는 업데이트 명령을 받을 때, 기존에 사용된 레이아웃이 그대로 사용되면 View 를 다시 생성하지 않습니다. 하지만, 우선 정지 레이아웃을 임시로 설정한 후, 바로 뒤 이어 움직이는 레이아웃을 설정하면 새롭게 View 가 생성되며 애니매이션이 실행되게 됩니다.

2.1 이 후의 주소록 활용. 
 2.1 이 후로 가장 복잡하게 바뀐 컴포넌트 중 하나가 바로 Contacts 관련된 내용이라고 할 수 있습니다. 기존 Contacts 가 더 이상지원하지 않는 클래스가 되며, 새롭게 ContactsContract 가 추가되었는데요. 간단하게 여러 종류의 제한 없는 주소 정보를 하나의 클래스로 표현하려고 하다 보니 기존의 Contacts 프로바이더는 저장 가능한 데이타 종류가 이미 고정되어 있는 등 한계가 있어서, 새롭게 굉장히 일반적인 구조의 ContactsContract 를 만들었다고 생각하시면 됩니다. 그러니까 Contacts 가 그냥 커피 아니 클래스라면 ContactsContract 는 제네릭 혹은 탬플릿 클래스라고 할까요.

 '최근 연락처 위젯' 에서도 이 ContactsContract 를 사용한 부분이 몇 군데 있는데요. 우선 특정 전화번호와 연계된 주소록 정보는 다음과 같은 방법으로 알아낼 수 있습니다.
//select ContactsContract information by using phone number - originatingAddress.
Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(originatingAddress));
Cursor c = mAppContext.getContentResolver().query(uri, null, null, null, null);
if(c == null) //error happen
return;
c.close();
 그리고 전화와 관련된 주소록 만이 아니라, 이메일등 전체 주소록 목록을 확인하고자 할 때는, ContactsContract.Contacts.CONTENT_URI 경로에 접근하시면 됩니다. 
Cursor c = mAppContext.getContentResolver().query(
ContactsContract.Contacts.CONTENT_URI,null,null,null,null); 
if(c == null){
//error happen.
return new SimpleContact[0];
}
c.close();
 그 외에도, MediaStore 등의 다른 Provider 와는 달리 ContactsContract 의 경우에는 특정 데이타 로의 _ID 값이 진짜 Primary Key 로 작동하는 것이 아니기 때문에, (ContactsContract 는 말그대로 여러 Contacts 가 하나로 모인 집합이기 때문에 언제든지 재조직되거나 해체될 수 있습니다.) _ID 값 이외에도 LOOKUP_KEY 를 알고 있어야 되며, getLookupUri() 메서드를 활용하면 특정 주소록 아이템에 접근할 수 있는 올바른 URI 를 얻을 수 있다는 점도 기억해 두시면 좋습니다.

위젯 이벤트 처리.

 마지막으로, 위젯에서의 사용자 이벤트 처리에 관해 간단히 이야기해보지요. 기본적으로 위젯에서 처리 가능한 사용자 이벤트는 오직 두 가지. 일정 주기 마다 날아오는 APPWIDGET_UPDATE 브로드캐스트 인텐트와 특정 View 가 클릭시 발생시킬 수 있는 PendingIntent 입니다. 그리고 위젯은 본질적으로 BroadcastReceiver 라는 점을 활용해서 여러가지 상태 정보를 저장하거나, GUI 를 다시 그리는 작업을 수행해야 합니다. 이때, 동일한 형식의 PendingIntent 는 하나 이상 존재할 수 없다는 제약조건(신용 거품을 피하기 위하여...)이 있음을 꼭 기억하셔야합니다. 예를 들어, '최근 연락처 위젯'에서, 버튼을 누를 때 해당 주소록에 대응되는 QuickContact 를 화면에 표시할 때, 투명한 Activity 를 호출하도록 구현되어 있는데요.이 Activity 를 호출하는데 사용한 PendingIntent 는 다음과 같습니다.

private String[] mCallIntentList = new String[]{

RecentContactsWidget.ACTION_CALL_1, 

RecentContactsWidget.ACTION_CALL_2, 

RecentContactsWidget.ACTION_CALL_3};


Intent intent = new Intent(mCallIntentList[index]);

intent.setData(uri);

intent.putExtra(ContactsContract.Contacts.DISPLAY_NAME, name);

PendingIntent pendingCall = PendingIntent.getActivity(mAppContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

views.setOnClickPendingIntent(mCallItemList[index + 1], pendingCall);

 모두 전달하고자 하는 EXTRA 자료만 다른, 동일한 Activity 를 호출하고자 하는 Intent 이기 때문에, 명시적 방법으로 Intent 를 생성할 경우 중복 생성이 불가능 하며, 암시적으로 서로 다른 Action 값을 갖는 Intent 를 기반으로 PendingIntent 를 생성하고 있습니다.


 간단하게 '최근 통화 위젯' 어플리케이션 코드 부분 부분을 설명 드렸습니다. 전체 코드 라인이 500여 라인 정도인 정말 정말 작은 어플리케이션인만큼, 위젯 어플리케이션 개발에 관심있으신 분들은 한번 체크아웃 받아서 이곳 저곳 살펴보시고 버그나 개선점등을 알려주시면 정말 좋겠습니다^^ (코드 구글 페이지에 직접 남겨주셔도, 제 블로그에 남겨 주셔도 좋습니다.


PS. '최근 연락처 위젯' 어플리케이션은이 주소로 접속(안드로이드 폰만 가능) 하시면, 현재 그리고 앞으로도 안드로이드 마켓에서 무료로 다운 받으실 수 있습니다.

qrcode.png