Jollen 的 Android 教學,#19: 什麼是App Widget?

App Widget是Cupcake(Android 1.5)所提供的一個功能,這是一個很實用而且能有很大創意想像空間的功能。什麼是App Widget呢?請看底下的操作示範。

在Android桌面長壓約3秒,出現一個選單,如圖1。


圖1:新增項目至桌面

2. 選擇「Widget」,加入”HelloWidget”


圖2:加入自行設計的Widget

桌面上出現了一個「Widget」


圖3:在Android桌面上出現我們自已設計的App Widget


圖4:加入了音樂撥放器App Widget至桌面

這就是App Widget的應用,可以將一個小塊程式(program piece)嵌入到桌面上。App Widget也是一種UI組件,先前所介紹的TextView、WebView等也泛稱為Widget,二者在應用上的差異該怎麼思考呢?以下是幾點看 法:

1. App Widget是有生命的UI組件,他會自動更新本身的內容
2. Widget是沒有生命的UI組件,它不會自我更新,只能等待使用者的操作
3. 應用上,App Widget能提供不斷更新的內容,很適合用來設計天氣、時鐘、新聞等主動式應用程式
4. Widget應用上只用來製作UI,而UI因為只能等待使用者來操作,所以過去我們所撰寫的Android應用程式都是屬於被動式應用程式

讓App Widget能「主動」更新自身內容的方法是透過一個「時間觸發裝置」,Android框架會根據我們設定的時間間隔,不斷地callback我們的 App Widget。後續將再說明App Widget的做法,並解釋這個部份。

Android的ApiDemo範例庫提供了一個很不錯的App Widget範例;不過,對初學者來說,這個範例可能稍嫌繁瑣。在這裡另外提供一個HelloAppWidget範例如下:

/* 範例:HelloAppWidget.java */
package com.moko.hellowidget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.widget.RemoteViews;

public class HelloAppWidgetProvider extends AppWidgetProvider {

public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}

public void onDeleted(Context context, int[] appWidgetIds) {
}

static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
int appWidgetId) {
CharSequence text;

text = "www.jollen.org";

RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
views.setTextViewText(R.id.appwidget_text, text);

appWidgetManager.updateAppWidget(appWidgetId, views);
}
}

一個很簡單的App Widget就是只需要這麼幾行程式碼,HelloAppWidget範例就是先前的App Widget操作示範使用的範例,HelloAppWidget會在桌面嵌進一個TextView組件,並顯示”www.jollen.org”字串。

由此範例可以了解,App Widget使用到Android提供的AppWidgetProvider類別,建議可以先行快速瀏覽Android Reference文件,以了解此類別的大略用法。

App Widget的設計流程

設計一個App Widget的流程,並不是由寫程式開始,所以上述的範例程式,並不是首要的重點。實作一個App Widget的過程,用到了過去教學裡的所有觀念,因此對以下的流程述描有不了解的地方,可以再回頭覆習過去的教學。

App Widget設計流程:

1. 規劃App Widget的大小以及更新時間,在res/xml/裡新增一份XML文件,命名為appwidget_provider.xml
2. 規劃App Widget的UI,修改res/layout/main.xml
3. 撰寫App Widget主程式,如上例
4. 編輯AndroidManifest.xml,設定App Widget可接受App Widget的更新事件:android.appwidget.action.APPWIDGET_UPDATE

換個角度來看,設計一個陽春版的App Widget至少需要以下4個檔案:

  • res/xml/appwidget_provider.xml
  • res/layout/main.xml
  • src//HelloAppWidgetProvider.java
  • AndroidManifest.xml

有了設計流程後,接下來一一說明每個步驟的實作,以及技術重點。

以下分別說明HelloAppWidget的實作,以及技術重點。

appwidget_provider.xml-描述App Widget屬性的資源檔

這個檔案主要描述App Widget的幾個屬性:

  • 長度(width)
  • 高度(height)
  • 更新頻率
  • UI layout檔

以下是appwidget_provider.xml的完整內容:

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

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="85dp"
android:minHeight="30dp"
android:updatePeriodMillis="3000"
android:initialLayout="@layout/main"
>
</appwidget-provider>

說明如下:

1. <appwidget-provider>標籤定義App Widget的屬性
2. android:minWidth定義寬度
3. android:minHeight屬性定義長度
4. android:updatePeriodMillis定義App Widget的更新頻率,Android框架每隔這段時間,會callback AppWidgetProvider類別的onUpdate()事件;此屬性的時間單位為1/1000秒,以上述的定義來說,等於3秒鐘的時間(3000 /1000=3)
5. android:initialLayout屬性指定此App Widget的UI layout定義檔,”@”符號在Android的XML定義檔案,代表「目錄」之意,因此”@layout/main”表示「layout目錄下的 main.xml檔案」

以上共四項屬性,是App Widget最基本的屬性,必須良好定義。其中android:updatePeriodMillis屬性可省略,代表不更新App Widget,即Android框架將不callback appWidgetProvider類別的onUpdate()事件。

onUpdate()事件負責更新App Widget的顯示內容。

設計App Widget的第一件工作,就是定義它的大小,以及更新頻率。由於手機的螢幕比較小,再加上桌面的空間有限,因此就要很小心定義App Widget的長度以及寬度。

在Android的Dev Guide文件裡,有一個App Widget設計原則的章節,描述了App Widget的標準大小;當然,這只是建議,我們可以任意定義App Widget的大小,因此不依照Google提供的設計原則也不會有什麼問題。但是,若是能遵循設計原則的指示,桌面的空間安排會較有效率,桌面的整體呈 現也會比較美觀。

App Widget的設計需要考量螢幕的方向,若是直向顯示(portrait),則App Widget的大小建議如下:


(圖片來源:Android Dev Guide;點擊看全圖)

橫向顯示(landscape)的設計建議如下:


(圖片來源:Android Dev Guide;點擊看全圖)

在後面的教學裡,我們會再詳細說明App Widget的美工設計原則。

接下來要接著進行的工作,即是在”@layout/”裡建立main.xml檔案,以描述App Widget的UI。

main.xml-描述App Widget的UI

這個檔案在前面的教學裡介紹過了,它的主要用途是描述UI。我們想要設計一個能嵌進桌面,並顯示文字的App Widget,因此必須使用Android的TextView類別。

以下是main.xml的完整內容:

<?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="fill_parent"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/appwidget_text"
android:textColor="#ff000000"
/>
</LinearLayout>

我們的App Widget使用LinearLayout來安排佈局,而UI為一個TextView物件。在這裡,我們將此TextView物件的id定義 為”appwidget_text”。

HelloAppWidgetProvider.java-實作App Widget供應者

程式碼已經在前面的教學裡展示過了,當時只提到一個很基本的重點:使用AppWidgetProvider類別。在這裡,我們先說明設計的部份,才 能了解程式如何實作。程式碼的說明稍後再做補充。


圖1:設計App Widget

從圖1的設計裡可以知道(配合查詢Android Reference文件),當程式繼承了AppWidgetProvidr類別後,也繼承了二個主要的method:

  • onUpdate()
  • onDelete()

App Widget使用AppWidgetProvider類別,即App Widget的「供應者」,供應什麼東西給誰呢?可以想像成是,我們的應用程式,供應App Widget給Android桌面。

到目前為止,我們知道只需要AppWidgetProvider即可實成一個很陽春的App Widget。而完整的App Widget應該包含三個單元(unit):

1. Provider:此處說明的「供應者」
2. Configure:App Widget的設定單元,用途是提供一個「介面」供使用者輸入資料
3. Receiver:繼承自BroadcastReceiver的單元,即廣播接收器,用來接收Android框架所送出的事件(event)

在後續的教學裡,我們會繼續說明configure與receiver單元的觀念與實作

AndroidManifest.xml-加入App Widget的程式資訊

AndroidManifest.xml檔案的用途在前面的教學裡介紹過了。以下是其完整內容:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.moko.hellowidget"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<receiver android:name=".HelloAppWidgetProvider">
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider" />
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>

</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>

說明如下:

1. 在<application>裡加入<receiver>標籤,指定android:name屬性為主要的provider類 別,即”HelloAppWidgetProvider”,請注意,「.」表示後面的字串為一個「類別名稱」,不要忽略了這個重要的小數點

2. 在<receiver>裡加入<meta-data>標籤,指定android:resource屬性為App Widget的資源檔名稱,以我們的範例來說,就是「@xml/appwidget_provider」,即「xml目錄下的 appwidget_provider.xml檔案」

3. 在<receiver>裡加入<intent-filter>標籤,讓我們的App Widget可以接收APPWIDGET_UPDATE事件(event)

一個很簡單的App Widget完成了。接下來,就是針對HelloAppWidgetProvider.java程式碼做細部說明。

HelloAppWidgetProvider.java 程式碼說明


圖1: HelloAppWidgetProvider的設計

圖1是目前我們的HelloAppWidget範例設計,說明如下:

  • onUpdate(): 收到ACTION_APPWIDGET_UPDATE廣撥時,框架會callback此method
  • onDelete(): 收到ACTION_APPWIDGET_DELETE廣撥時,框架會callback此method
  • AppWidgetManager: 管理App Widget的類別

先前,在AndroidManifest.xml裡我們讓HelloAppWidgetProider類別可以接收 ACTION_APPWIDGET_UPDATE廣撥事件;ACTION_APPWIDGET_UPDATE是最主要的App Widget事件,當AppWidgetProvider被要求為App Widget提供”RemoteView”時,就會收到這個事件。

什麼是RemoteViews?

什麼是RemoteView呢?先看一下框架的設計,如圖2。

簡單來說,「RemoteViews」就是表示UI的類別。res/layout/main.xml描述了應用程式的UI,UI裡當然包含許多組件 (Widget),而在先前的教學裡講到了一個觀念「Android應用程式的UI就是一個View tree」,view tree就是「View Hierarchy」。

總結來說,RemotViews是一個用來表示View Hierarchy的類別。透過RemoteViews可以找到UI裡的每一個組件。


圖2: RemoteView的設計(點擊看全圖)

程式說明: HelloAppWidgetProvider.java

onUpdate()的程式實作:

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
final int N = appWidgetIds.length;
for (int i=0; i<N; i++) {
int appWidgetId = appWidgetIds[i];
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}

說明如下:

1. onUpdate()負責更新「已經安裝」在桌面上的App Widget內容,因此我們實作一個updateAppWidget()來進行真正更新的工作

2. onUpdate()的第二個參數為AppWidgetManager,這是一個「管理AppWidgetProvider」的類別,我們必須透過框架 callback本方法時回傳給我們的AppWidgetProvider物件,來更新桌面上的App Widget

3. onUpdate()的第三個參數appWidgetIds陣列,存放需要更新的App Widget ID;框架會將需要更新的App Widget之ID回傳給onUpdate(),程式必須負責「更新每一個需要更新的App Widget。」

更新App Widget的方式是透過AppWidgetManager來完成,程式實作:

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
CharSequence text;

text = "www.jollen.org";

RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
views.setTextViewText(R.id.appwidget_text, text);

appWidgetManager.updateAppWidget(appWidgetId, views);
}
}

說明如下:

1. 透過UI layout取得自已的「View Hierarchy」(UI),以前面介紹的RemoteViews物件表示

2. 如圖2,呼叫RemoteView的setTextViewText()方法,修改UI裡的「R.id.appwidget_text」組件,變更文字內 容

3. 呼叫AppWidgetProvider的updateAppWidget()方法,更新我們所指定的App Widget,將其UI更新為RemoteView的UI

4. updateAppWidget()的第二個參數為RemoteView,即說明1.取得的UI,說明2.修改了此UI裡 的”R.id.appwidget_text”組件,最後透過App Widget Manager更新App Widget的UI


作者: landuochong   发布时间: 2010-10-19