본문 바로가기

프로그래밍/Android

AppWidget 여러 개의 위젯을 가진 앱위젯

   

1. 앱위젯과 프로바이더

   

앱위젯 프로바이더는 앱위젯 호스트와 커뮤니케이셔을 위한 중간개체이다.

 

그러다 보니 실제로 이미지를 그리거나 이벤트를 처리하는게 아닌

이러저러하게 처리해 달라고 호스트에 요구하고, 해당 요청에 따라

앱위젯 호스트가 적당한 처리를 하게 된다.

   

아래와 같이 여러개의 컨트롤(위젯)을 가진 앱위젯을 구성하려고 할때 필요한 사항들을

포스트한다.

   

2. ui 구성

오디오 재생기 앱위젯이다. 테스트용이라 아무 이미지나 붙여봤다.

   

   

보면 재생을 위한 컨트롤 이 4개 보이고, 상단에 미디어 스캔으로 얻은 썸네일 이미지와 좌,우 버튼이

보인다.

   

3. 레이아웃 ( widget_control_panel.xml )

위 위젯은 아래와 같은 레이아웃으로 구성되어 있다.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:dayside="http://schemas.android.com/apk/res/com.hopefullife.player"

    android:id="@+id/widget_control_panel"

    android:layout_width="250dp"

    android:layout_height="wrap_content"

    android:layout_weight = "1"

    android:orientation = "vertical"    

    android:layout_margin="0px" 

    android:padding="0px"

    android:background="#00000000"

    > 

    <LinearLayout

     android:layout_width="fill_parent"

     android:layout_height="wrap_content"

     android:layout_weight="1"

   

     android:background="#00000000">

      

     <FrameLayout

     android:layout_width="wrap_content"

     android:layout_height="wrap_content"

     android:layout_weight="1"

     android:layout_gravity="center"

     android:background="#00000000">

        

      <ImageView 

       android:id="@+id/widget_prev_title"

       android:layout_gravity = "center_vertical|left"

       android:layout_marginLeft = "20dp"

       android:layout_width = "100dp"

       android:layout_height = "100dp"

       android:src="@drawable/player_basic"/>

      <ImageView 

       android:id="@+id/widget_next_title"

       android:layout_gravity = "center_vertical|right"

       android:layout_marginRight = "20dp"

       android:layout_width = "100dp"

       android:layout_height = "100dp"

       android:src="@drawable/player_basic"/>

      <ImageView 

       android:id="@+id/widget_main_title"

       android:layout_gravity = "center"

       android:layout_width = "150dp"

       android:layout_height = "150dp"

       android:src="@drawable/player_basic"/> 

        

      <ImageButton 

      android:id="@+id/widget_prev_content"

      android:layout_width="50dp"

      android:layout_height="50dp"

      android:background="@drawable/widget_prev_selector"

      android:layout_gravity="center|left"

      />

        

      <ImageButton 

      android:id="@+id/widget_next_content"

      android:layout_width="50dp"

      android:layout_height="50dp"

      android:background="@drawable/widget_next_selector" 

      android:layout_gravity="center|right"

      />

        

     </FrameLayout>     

    </LinearLayout>

    <LinearLayout

     android:layout_width="fill_parent"

     android:layout_height="50dp"

     android:layout_weight = "1"

     android:orientation = "horizontal"

     android:layout_marginTop="10dp"

     android:padding="0px"

     android:background="#90303090" >         

     <ImageButton

         android:id="@+id/widget_prev_button"

         android:layout_width="0dp"

         android:layout_height="wrap_content"

         android:layout_weight = "1"

         android:background="@drawable/widget_prev_selector"/>

     <FrameLayout

         android:layout_width="0dp"

         android:layout_weight="1"

         android:layout_height="wrap_content"

          >

         <ImageButton

          android:id="@+id/widget_play_button"

          android:layout_width="fill_parent"

          android:layout_height="fill_parent"

          android:background="@drawable/widget_play_selector"/>

             

       <ImageButton

          android:id="@+id/widget_pause_button"

          android:layout_width="fill_parent"

          android:layout_height="fill_parent"

          android:visibility = "gone"

          android:background="@drawable/widget_pause_selector"/>

               

     </FrameLayout>   

      

     <ImageButton

         android:id="@+id/widget_next_button"

         android:layout_width="0dp"

         android:layout_height="wrap_content"

         android:layout_weight = "1"

         android:background="@drawable/widget_next_selector"/>

           

     <ImageButton

         android:id="@+id/widget_contentbox_button"

         android:layout_width="0dp"

         android:layout_height="wrap_content"

         android:layout_weight = "1"

         android:background="@drawable/widget_next_selector"/>

           

   </LinearLayout> 

</LinearLayout>

   

3. 프로바이더 ( res/xml/player_widget_provider.xml )

프로바이더를 설정하는데 여기서 initialLayout으로 위의 레이아웃을 설정한다.

   

<?xml version="1.0" encoding="utf-8"?>

<appwidget-provider

  xmlns:android="http://schemas.android.com/apk/res/android"

  android:minWidth="200dp"

  android:minHeight="150dp"

  android:initialLayout="@layout/widget_control_panel"

/>

   

4. 메니페스트 추가

앱위젯 프로바이더는 리시버이다. 따라서, 메니페스트에 리시버에 대해 기술해 주어야 한다.

여기서 결정할 사항이 있는데, 각 버튼 클릭시 호스트에게 특정 액션을 브로드 캐스팅 하라고

요청해야 한다. 이 요청을 명시적으로 할 것인지, 묵시적으로 할 것인지에 따라

intent-filter를 선언하게 된다.

   

아래는 묵시적 호출로 해당 액션에 대해 기술된 모든 어플은 아래 액션을 받을 수 있게 된다.

여러개의 어플이 위젯의 행위에 따라 연동되게 하려면 각 어플에서 아래 액션을 수신하게

작성하면 된다.

   

물론, 명시적 호출에서는 제거해도 된다.( 소스참조 )

   

그리고, 메타 데이터 정보에 위에 작성한 프로바이더 xml 파일을 기술해 준다.

   

<receiver android:name=".widget.PlayerWidgetProvider" >

   <intent-filter>

    <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />

    <action android:name="com.hopefullife.player.widget.PLAY" />

    <action android:name="com.hopefullife.player.widget.NEXT" />

    <action android:name="com.hopefullife.player.widget.PREV" />

    <action android:name="com.hopefullife.player.widget.NEXT_THUMB" />

    <action android:name="com.hopefullife.player.widget.PREV_THUMB" />

    <action android:name="com.hopefullife.player.widget.UPDATE" />

     <category android:name="android.intent.category.DEFAULT" />

   </intent-filter>    

   <meta-data android:name="android.appwidget.provider"               

    android:resource="@xml/player_widget_provider" />

  </receiver>

   

   

5. 액션 정의

public static final String WIDGET_UPDATE_ACTION = "com.hopefullife.player.widget.UPDATE";

 public static final String WIDGET_PLAY_ACTION = "com.hopefullife.player.widget.PLAY";

 public static final String WIDGET_NEXT_ACTION = "com.hopefullife.player.widget.NEXT";

 public static final String WIDGET_PREV_ACTION = "com.hopefullife.player.widget.PREV";

.

.

.

public static final String WIDGET_SELECT_ACTION = "com.hopefullife.player.widget.SELECT";

   

6. 프로바이더 작성

public class PlayerWidgetProvider extends AppWidgetProvider {

 @Override

 public void onUpdate(Context context, AppWidgetManager appWidgetManager,int[] appWidgetIds) {

   

   

}

   

@Override

 public void onReceive(Context context, Intent intent) {

   

}

   

@Override

 public void onEnabled(Context context) {

   

}

   

@Override

 public void onDisabled(Context context) {

   

}

   

}

   

7. onUpdate

업데이트는 앱위젯 호스트로부터 발생하는데, 앱 위젯 호스트 입장에서

"내가 너를 그릴려고 하는데, 어떻게 그려줘야 하긋냐?" 라고 묻는 콜백이다.

따라서, 앱위젯이 그려져야 하는 내용을 전달해 주어야 한다.

이때 사용되는 객체가 RemoteViews 객체로 기능은 ViewGroup에 비해 제한적이다.

(Object에서 바로 상속된 객체로 실제 View, ViewGroup과는 관련이 없다.)

   

RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_control_panel );

   

   

* 브로드 캐스팅을 위한 각 펜딩 인텐트 정의.  : 앱위젯의 버튼 이벤트도 직접 처리할 수 없다.

이유는 역시나 이 뷰를 가진 녀석이 내가 아닌 어느누군가(앱 위젯 호스트) 이기 때문이다.

   

따라서, 프로바이더 입장에서는 이게 클릭하면 어떤 인텐트나 보내줘.. 정도의 소심한 요청을 할 수 밖에

없어진다.

   

   

명시적 인텐트( 메니페스트에 등록할 필요없이 특정 객체로 전송할 경우) 

PendingIntent play = PendingIntent.getBroadcast(context, 0,  

new Intent(WIDGET_PLAY_ACTION,

Uri.EMPTY, 

context.

context , 

PlayerWidgetProvider.class), 

PendingIntent.FLAG_UPDATE_CURRENT);

   

아래는 묵시적 인텐트( 다른 어플리케이션에서도 모두 액션을 수신하고자 할때 사용. manifest에 등록해 사용하거나

별도의 브로드 캐스트 리시버 등록 절차 필요하다.)

   

PendingIntent next = PendingIntent.getBroadcast(context, 0,

new Intent(WIDGET_NEXT_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);

   

PendingIntent prev = PendingIntent.getBroadcast(context, 0, 

new Intent(WIDGET_PREV_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);

   

PendingIntent prevThumb = PendingIntent.getBroadcast(context, 0,

new Intent(WIDGET_NEXT_THUMB_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);

   

PendingIntent nextThumb = PendingIntent.getBroadcast(context, 0,

new Intent(WIDGET_PREV_THUMB_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);

   

PendingIntent contentbox = PendingIntent.getBroadcast(context, 0,

new Intent(WIDGET_CONTENTBOX_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);

   

PendingIntent selectThumb = PendingIntent.getBroadcast(context, 0,

new Intent(WIDGET_SELECT_ACTION), PendingIntent.FLAG_UPDATE_CURRENT);

   

   

각 인텐트를 셋한다.

앱위젯 호스트는 아래 정보를 바탕으로 버튼 클릭시 해당 인텐트를 전송하게 된다.

views.seton_clickPendingIntent(R.id.widget_play_button, play );

views.seton_clickPendingIntent(R.id.widget_next_button, next );

views.seton_clickPendingIntent(R.id.widget_prev_button, prev );

views.seton_clickPendingIntent(R.id.widget_next_content, nextThumb);

views.seton_clickPendingIntent(R.id.widget_prev_content, prevThumb);

views.seton_clickPendingIntent(R.id.widget_contentbox_button, contentbox);

views.seton_clickPendingIntent(R.id.widget_main_title, selectThumb);

   

   

앱위젯은 여러 호스트에서 사용가능하므로, 현재 등록된 위젯 수만큼 반복해가며

업데이트를 한다.

final int N = appWidgetIds.length;

    

for(int i=0;i<N;i++ ){

   int appWidgetId = appWidgetIds[i];

   appWidgetManager.updateAppWidget(appWidgetId, views);

}

   

8. onReceive

브로드 캐스트와 동일하게 버튼이 클릭되면 위에 정의된 액션이 전달됨을 확인할 수 있다.

각 전달된 액션에 따라 필요한 처리를 하면 된다.

   

9. 실제 재생은 ?

실제 미디어를 재생하는 객체는 어디에 있을까?

프로바이더는 콜백 발생시 인스턴스가 생성되는 임시 객체이다.

따라서, 이곳에 선언된 변수들은 매번 초기화 되어 버리는데, 미디어 클래스를 static 형태로 구성해도

되지만 이역시 안정성에 문제가 있다.

   

대부분 별도의 로컬 서비스 객체를 하나 생성해 처리하게 된다.

(이부분은 길어지니 대강 설명만)

onEnable 시에 로컬 서비스 객체를 시작시킨다.

로컬 서비스객체의 인스턴스에 접근하기 위해서 바인딩 처리를 추가한다.

이러면 프로바이더의 어느곳에서도 바인더를 통해 서비스 객체의 접근이 가능해진다.

   

또는, 별도의 미디어 서비스 어플리케이션을 만들고, 이 녀석은 실제 재생만 담당하도록

구성한다. 그리고, 각 커맨드를 브로드 캐스팅을 통해 수신하게 해서

앱위젯의 버튼 클릭시 해당하는 커맨드를 브로드 캐스팅 해서 동작시킬 수 있다.

   

10. 이미지 변경? 에니메이션?

앱위젯은 에니메이션을 지원하지 않는다. 간단한 에니메이션(구동 에니라던가, 흐름글, 변경 에니)은

updata를 통해 구현해야 하는데, 이역시 주기적인 update가 일어나도록 해야한다.

앱위젯 프로바이더 설정에 update 주기를 설정 할 수 있는데, 

그 단위가 원하는만큼 짧지않다.

   

결국, 위 처리를 위해서는 ui 업데이트용 로컬 서비스와 쓰레드가 필요하게 된다.

(비용은 발생한다. 앱 위젯이 많이 업데이트 될 수록 작업량은 증가된다..)

   

업데이트를 위한 액션을 하나 정의하고, onReceive시 업데이트를 하게 한다.

@Override

public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

    

if( action.equalsIgnoreCase( WIDGET_UPDATE_ACTION)) {

AppWidgetManager manager = AppWidgetManager.getInstance(context);

this.onUpdate(context, manager,

manager.getAppWidgetIds(new ComponentName("com.hopefullife.player",

"com.hopefullife.player.widget.PlayerWidgetProvider")));

return;

}

   

.

.

.

}

   

   

11. 기타 서비스 관련 예

// 서비스 객체 

public static PlayerWidgetService mService = null;

   

@Override

public void onEnabled(Context context) {

   

PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(

new ComponentName(context.getPackageName(),".PlayerWidgetProvider"),

          PackageManager.COMPONENT_ENABLED_STATE_ENABLED,

          PackageManager.DONT_KILL_APP);

// static 인스턴스에 서비스 객체를 저장하는 형태로 사용했다.

// 구동중인 서비스를 얻기 위해선 여러가지 방법이 존재

// 아래의 커넥션 객체에서 바인더를 사용해 객체를 얻고 있다.

if( PlayerWidgetProvider.mServiceConnection == null ) {

Intent i = new Intent();

i.setAction("android.intent.action.WIDGET_SVC");

PlayerWidgetProvider.mServiceConnection = new PlayerWidgetConnection();

context.getApplicationContext().bindService(i,

PlayerWidgetProvider.mServiceConnection,

Context.BIND_AUTO_CREATE);

}

}

   

   

@Override

public void onDisabled(Context context) {

PackageManager pm = context.getPackageManager();       

pm.setComponentEnabledSetting( new omponentName(context.getPackageName(),".PlayerWidgetProvider"),

                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,

                0);

          

if( PlayerWidgetProvider.mServiceConnection != null ) {

Intent i = new Intent();

i.setAction("android.intent.action.WIDGET_SVC");

context.unbindService(PlayerWidgetProvider.mServiceConnection);

           

PlayerWidgetProvider.mServiceConnection = null;

}

}

// 서비스에 바인딩 하기 위한 커넥션 객체

public class PlayerWidgetConnection implements ServiceConnection 

{

@Override

public void onServiceConnected(ComponentName name, IBinder service) {

PlayerWidgetProvider.mService =

((PlayerWidgetService.PlayerWidgetServiceBinder)service).getService();

PlayerWidgetProvider.mService.sendBroadcast(

new Intent(PlayerWidgetService.WIDGET_UPDATE_ACTION));

     

Intent i = new Intent();

i.setAction("android.intent.action.MEDIA_SVC");

PlayerWidgetProvider.mService.sendBroadcast( i );

}

   

@Override

public void onServiceDisconnected(ComponentName name) {

PlayerWidgetProvider.mService = null;

}

}

   

'프로그래밍 > Android' 카테고리의 다른 글

Drag&drop listview  (0) 2010.10.06
뷰 전환시 3차원 에니메이션 적용하기  (0) 2010.09.17
안드로이드 위젯 배경 이미지, 나인패치  (0) 2010.08.17
옵션메뉴 배경 변경  (0) 2010.08.10
갤러리, 커버플로우 구현  (0) 2010.07.27
OpenGL 사용하기  (0) 2010.06.27
소켓통신 기본사항  (0) 2010.06.11
OpenCore Codec 연동  (0) 2010.03.27
GDB 기본환경  (0) 2010.03.22
오픈코어 코덱 연동 시퀀스  (0) 2010.03.18