作者:徐建祥(netpirate@gmail.com)
時間: 2010/12/15
來自: http://www.anymobile.org
Widget雜談:最早Widget是指在PC的案頭上的小視窗程序;Web上的先行者似乎是Yahoo!;當然,OPhone也搞了一套Widget,HTML+CSS的東東。
我們這裡談的所謂Widget,就是視窗小組件,Android SDK從1.5版本開始支援AppWidget framework,返個架構允許開發人員開發Widgets,這些Widgets可以被使用者通過長按案頭進行添加,與應用程式進行資料互動。
需求:
在案頭上開發一個Widget,可以即時顯示IM軟體的狀態更新變化;可以通過左右按鈕,查看上次或下調更新內容。
(參考)
設計思路:
(參考設計順序圖表)
代碼:
Java:
/src/org.anymobile.demo.Globals//Intent.action 聲明
/src/org.anymobile.demo.service.UpdateService extends Service //同步、更新Widget布局資料的Service
/src/org.anymobile.demo.widget.UpdateAppWidgetProvider extends AppWidgetProvider //Widget,接收器
XML:
/res/layout/update_appwidget.xml //布局設計
/res/values/strings.xml //常量聲明
/res/xml/update_appwidget_info.xml//app widget定義
AndroidManifest.xml
#AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><br /><manifest xmlns:android="http://schemas.android.com/apk/res/android"<br /> package="org.anymobile.demo"<br /> android:versionCode="1"<br /> android:versionName="1.0"><br /> <application android:icon="@drawable/icon" android:label="@string/app_name"></p><p><receiver android:name=".widget.UpdateAppWidgetProvider"<br /> android:label="@string/app_widget_label" ><br /><intent-filter><br /><action android:name="android.appwidget.action.APPWIDGET_UPDATE" /><br /></intent-filter><br /> <meta-data android:name="android.appwidget.provider"<br /> android:resource="@xml/update_appwidget_info" /><br /></receiver></p><p> <service android:name=".service.UpdateService" android:label="@string/app_name"><br /> <intent-filter><br /> <action android:name="org.anymobile.demo.service.IMM_UPDATE_SERVICE" /><br /> <category android:name="android.intent.category.DEFAULT" /><br /> </intent-filter><br /> </service><br /> </application><br /></manifest>
#strings.xml
<?xml version="1.0" encoding="utf-8"?><br /><resources><br /> <string name="app_name">AnymobileDemo</string></p><p> <string name="app_widget_label">AnymobileDemo Widget</string><br /> <string name="app_widget_title">Updates</string><br /> <string name="app_widget_error_message">No messages, please check to login.</string></p><p></resources><br />
#update_appwidget_info.xml
<?xml version="1.0" encoding="utf-8"?><br /><appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:minWidth="294dip"<br /> android:minHeight="120dip"<br /> android:updatePeriodMillis="0"<br /> android:initialLayout="@layout/update_appwidget"><br /></appwidget-provider>
#update_appwidget.xml
<?xml version="1.0" encoding="UTF-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"><br /> <LinearLayout<br /> android:id="@+id/app_widget_top"<br /> android:gravity="center_vertical"<br /> android:orientation="horizontal"<br /> android:background="@drawable/widget_titlebar"<br /> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content"><br /> </LinearLayout> </p><p> <LinearLayout<br /> android:id="@+id/app_widget_body"<br /> android:orientation="horizontal"<br /> android:background="@drawable/widget_body"<br /> android:layout_width="fill_parent"<br /> android:layout_height="100dip"><br /> <LinearLayout<br /> android:id="@+id/app_widget_message"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"><br /> <TextView<br /> android:id="@+id/widget_message"<br /> android:text="@string/app_widget_error_message"<br /> android:paddingRight="5dip"<br /> android:paddingLeft="5dip"<br /> android:layout_width="wrap_content"<br /> android:layout_height="wrap_content"><br /> </TextView><br /> </LinearLayout><br /> </LinearLayout> </p><p> <LinearLayout<br /> android:id="@+id/app_widget_bottom"<br /> android:gravity="right"<br /> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content"><br /> </LinearLayout><br /></LinearLayout>
#Globals.java
package org.anymobile.demo;<br />public final class Globals<br />{<br />public static final String ACTION_APP_WIDGET_SERVICE= "org.anymobile.demo.service.IMM_UPDATE_SERVICE";</p><p>public static final String ACTION_APP_WIDGET_PREV= "org.anymobile.demo.intent.action.APP_WIDGET_PREV";<br />public static final String ACTION_APP_WIDGET_NEXT= "org.anymobile.demo.intent.action.APP_WIDGET_NEXT";</p><p>public static final String ACTION_APP_WIDGET_RELOAD= "org.anymobile.demo.intent.action.APP_WIDGET_RELOAD";</p><p>}<br />
#UpdateService.java
package org.anymobile.demo.service;<br />import java.util.ArrayList;<br />import android.app.Service;<br />import android.appwidget.AppWidgetManager;<br />import android.content.BroadcastReceiver;<br />import android.content.ComponentName;<br />import android.content.Context;<br />import android.content.Intent;<br />import android.content.IntentFilter;<br />import android.os.IBinder;<br />import android.util.Log;<br />import android.view.View;<br />import android.widget.RemoteViews;<br />import org.anymobile.demo.Globals;<br />import org.anymobile.demo.R;<br />import org.anymobile.demo.widget.UpdateAppWidgetProvider;<br />public class UpdateService extends Service<br />{<br />public static final String TAG = "ANYMOBILE-DEMO--UpdateService";</p><p>private ArrayList<String> mList;<br />private int mCount;<br />private int mId;</p><p>private BroadcastReceiver mIntentReceiver = new BroadcastReceiver()<br />{<br /> @Override<br /> public void onReceive(Context context, Intent intent)<br /> {<br /> String action = intent.getAction();<br /> Log.d(TAG, "onReceive() " + action);</p><p> if (action.equals(Globals.ACTION_APP_WIDGET_RELOAD))<br /> {<br /> doReload();<br /> }<br /> }<br />};<br />@Override<br />public void onCreate()<br />{<br /> Log.d(TAG, "onCreate()");<br />super.onCreate();</p><p>reloadQueue();</p><p>IntentFilter filter = new IntentFilter();<br />filter.addAction(Globals.ACTION_APP_WIDGET_RELOAD);<br />registerReceiver(mIntentReceiver, filter);<br />}</p><p>@Override<br />public void onStart(Intent intent, int startId)<br />{<br />super.onStart(intent, startId);<br /> String action = intent.getAction();<br /> Log.d(TAG, "onStart() " + action);<br /> if (action.equals(Globals.ACTION_APP_WIDGET_PREV))<br /> {<br /> doPrev();<br /> }<br /> else if (action.equals(Globals.ACTION_APP_WIDGET_NEXT))<br /> {<br /> doNext();<br /> }<br /> else// if (action.equals(Globals.ACTION_APP_WIDGET_SERVICE))<br /> {<br /> notifyWidget();<br /> }<br />}</p><p>private void notifyWidget()<br />{<br /> Log.d(TAG, "notifyWidget()");</p><p>ComponentName widget = new ComponentName(this, UpdateAppWidgetProvider.class);<br />RemoteViews updateViews = buildUpdate(this);</p><p>AppWidgetManager manager = AppWidgetManager.getInstance(this);<br />manager.updateAppWidget(widget, updateViews);<br />}<br />@Override<br />public void onDestroy()<br />{<br /> Log.d(TAG, "onDestroy()");</p><p> unregisterReceiver(mIntentReceiver);</p><p>super.onDestroy();<br />}<br />@Override<br />public IBinder onBind(Intent intent)<br />{<br /> Log.d(TAG, "onBind()");<br />return null;<br />}</p><p>private RemoteViews buildUpdate(Context context)<br />{<br />RemoteViews updateViews =<br />new RemoteViews(context.getPackageName(), R.layout.update_appwidget);<br />String item = null;</p><p>if (mCount > 0)<br />{<br />item = mList.get(mId);<br />if (item != null)<br />{<br />updateViews.setViewVisibility(R.id.app_widget_content, View.GONE);<br />updateViews.setViewVisibility(R.id.app_widget_message, View.VISIBLE);</p><p>//updateViews.setViewVisibility(R.id.app_widget_content, View.VISIBLE);<br />//updateViews.setViewVisibility(R.id.app_widget_message, View.GONE);<br />//<br />//updateViews.setImageViewResource(R.id.update_appwidget_icon, item.getTypeIconId());<br />//updateViews.setTextViewText(R.id.update_appwidget_name, item.getNickName());<br />//updateViews.setTextViewText(R.id.update_appwidget_time, item.getModifyTime());<br />//updateViews.setTextViewText(R.id.update_appwidget_content, item.getMessage());</p><p>updateViews.setTextViewText(R.id.widget_message, item);<br />}<br />}<br />if (item == null)<br />{<br />updateViews.setViewVisibility(R.id.app_widget_content, View.GONE);<br />updateViews.setViewVisibility(R.id.app_widget_message, View.VISIBLE);</p><p>updateViews.setTextViewText(R.id.widget_message,<br />context.getText(R.string.app_widget_error_message));<br />}<br /> Log.d(TAG, "buildUpdate: layoutId = " + updateViews.getLayoutId() +<br /> "; count = " + mCount + "; id = " + mId);</p><p>return updateViews;<br />}</p><p>private void doReload()<br />{<br /> Log.d(TAG, "doReload()");<br />reloadQueue();</p><p>notifyWidget();<br />}</p><p>private void reloadQueue()<br />{<br />mList = new ArrayList<String>();<br />String[] arr = {"aa", "bb", "cc", "dd"};<br />for (int i = 0; i < arr.length; i++)<br />{<br />mList.add(arr[i]);<br />}</p><p>if (mList != null)<br />{<br />mCount = mList.size();<br />}<br />else<br />{<br />mCount = 0;<br />}<br />mId = 0;</p><p>//TODO check login and poll updates from buddie list<br />}</p><p>private void doPrev()<br />{<br /> Log.d(TAG, "doPrev()");<br />mId -= 1;<br />if (mId < 0)<br />{<br />mId = mCount - 1;<br />}<br />notifyWidget();<br />}</p><p>private void doNext()<br />{<br /> Log.d(TAG, "doNext()");<br />mId += 1;<br />if (mId > mCount - 1)<br />{<br />mId = 0;<br />}<br />notifyWidget();<br />}<br />}<br />
#UpdateAppWidgetProvider.java
package org.anymobile.demo.widget;</p><p>import android.app.PendingIntent;<br />import android.appwidget.AppWidgetManager;<br />import android.appwidget.AppWidgetProvider;<br />import android.content.ComponentName;<br />import android.content.Context;<br />import android.content.Intent;<br />import android.util.Log;<br />import android.view.View;<br />import android.widget.RemoteViews;</p><p>import org.anymobile.demo.Globals;<br />import org.anymobile.demo.R;<br />import org.anymobile.demo.service.UpdateService;</p><p>public class UpdateAppWidgetProvider extends AppWidgetProvider<br />{<br />public static final String TAG = "ANYMOBILE-DEMO-UpdateAppWidgetProvider";</p><p> public static final String APP_WIDGET_UPDATE = "appwidgetupdate";<br /> public static final ComponentName APPWIDGET_COMPONENT_NAME =<br /> new ComponentName("org.anymobile.demo",<br /> "org.anymobile.demo.widget.UpdateAppWidgetProvider");<br />@Override<br />public void onReceive(Context context, Intent intent)<br />{<br /> Log.d(TAG, "onReceive() " + intent.getAction());<br />super.onReceive(context, intent);<br />}<br />@Override<br />public void onEnabled(Context context)<br />{<br /> Log.d(TAG, "onEnabled()");<br />super.onEnabled(context);<br />}<br />@Override<br />public void onDisabled(Context context)<br />{<br /> Log.d(TAG, "onDisabled()");<br />super.onDisabled(context);<br />}<br />@Override<br />public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)<br />{<br /> Log.d(TAG, "onUpdate()");</p><p> defaultAppWidget(context, appWidgetIds);</p><p> context.startService(new Intent(Globals.ACTION_APP_WIDGET_SERVICE));<br />}</p><p>private void defaultAppWidget(Context context, int[] appWidgetIds)<br />{<br /> final RemoteViews views =<br /> new RemoteViews(context.getPackageName(), R.layout.update_appwidget);</p><p>views.setViewVisibility(R.id.app_widget_content, View.GONE);<br />views.setViewVisibility(R.id.app_widget_message, View.VISIBLE);</p><p> // Link actions buttons to intents<br /> linkButtons(context, views);</p><p> pushUpdate(context, appWidgetIds, views);<br />}</p><p>private void linkButtons(Context context, RemoteViews views)<br />{<br /> Intent intent;<br /> PendingIntent pendingIntent;<br /> final ComponentName serviceName = new ComponentName(context, UpdateService.class);</p><p> intent = new Intent(Globals.ACTION_APP_WIDGET_PREV);<br /> intent.setComponent(serviceName);<br /> pendingIntent = PendingIntent.getService(context,<br /> 0 /* no requestCode */, intent, 0 /* no flags */);<br /> views.setOnClickPendingIntent(R.id.widget_btn_prev_page, pendingIntent);<br /> intent = new Intent(Globals.ACTION_APP_WIDGET_NEXT);<br /> intent.setComponent(serviceName);<br /> pendingIntent = PendingIntent.getService(context,<br /> 0 /* no requestCode */, intent, 0 /* no flags */);<br /> views.setOnClickPendingIntent(R.id.widget_btn_next_page, pendingIntent);<br />}</p><p>private void pushUpdate(Context context, int[] appWidgetIds, RemoteViews views)<br />{<br />final AppWidgetManager gm = AppWidgetManager.getInstance(context);<br /> if (appWidgetIds != null)<br /> {<br /> gm.updateAppWidget(appWidgetIds, views);<br /> }<br /> else<br /> {<br /> gm.updateAppWidget(APPWIDGET_COMPONENT_NAME, views);<br /> }<br />}</p><p>void notifyChange(UpdateService service, String what)<br />{<br />//<br />}<br />}<br />
日誌:
#init
12-15 19:23:09.479 D/ANYMOBILE-DEMO--UpdateAppWidgetProvider( 585): onReceive() android.appwidget.action.APPWIDGET_UPDATE
12-15 19:23:09.509 D/ANYMOBILE-DEMO--UpdateAppWidgetProvider( 585): onUpdate()
12-15 19:23:09.549 D/ANYMOBILE-DEMO--UpdateService( 585): onCreate()
12-15 19:23:09.579 D/ANYMOBILE-DEMO--UpdateService( 585): onStart()
#add widget
12-15 19:24:23.780 D/ANYMOBILE-DEMO--UpdateAppWidgetProvider( 585): onReceive() android.appwidget.action.APPWIDGET_UPDATE
12-15 19:24:23.780 D/ANYMOBILE-DEMO--UpdateAppWidgetProvider( 585): onUpdate()
12-15 19:24:23.850 D/ANYMOBILE-DEMO--UpdateService( 585): onStart()
#receive software event, reload and update widget
12-15 19:24:58.150 D/ANYMOBILE-DEMO--UpdateService( 585): onReceive() Activation
12-15 19:24:58.150 D/ANYMOBILE-DEMO--UpdateService( 585): doReload()
12-15 19:24:58.150 D/ANYMOBILE-DEMO--UpdateService( 585): notifyWidget()
12-15 19:24:58.200 D/ANYMOBILE-DEMO--UpdateService( 585): buildUpdate: layoutId = 2130903068; count = 11; id = 0
#click widget button, new start the bind service
12-15 19:25:49.260 D/ANYMOBILE-DEMO--UpdateService( 585): onStart()
12-15 19:24:58.150 D/ANYMOBILE-DEMO--UpdateService( 585): notifyWidget()
12-15 19:24:58.200 D/ANYMOBILE-DEMO--UpdateService( 585): buildUpdate: layoutId = 2130903068; count = 11; id = 0
OVER!
參考:
com.android.music/.MediaAppWidgetProvider
com.android.music/.MediaPlaybackService