Widget是一種小巧但是功能強大的程序,使用戶能夠方便快捷的獲取信息,在PC上被廣泛的使用,現在隨着OPhone的推出,widget也進入到了手機領域,爲用戶帶來了方便的同時也爲開發者實現更多很酷想法的可能。在OPhone中有兩種widget開發方式,一種是以HTML+CSS+JavaScript的開發方式,另一種是沿用Android平臺的開發方式,本文介紹的是後面一種,在OPhone平臺上開發App widget。
widget一般開發方式介紹
下面以編寫一個時鐘的小程序來介紹如何編寫widget。
(1)創建一個類,讓其繼承類AppWidgetProvider,在AppWidgetProvider類中有許多的方法,例如onDelete(Context, int[]),onEnable(Context)等等,一般情況下我們紙需要重寫onUpdate(Context, AppWidgetManager, int[])這個方法就可以了,這個方法是當觸發器更新widget時候執行的操作。
(2)在項目的AndroidMenifest.xml文件中添加一個receiver標籤,讓其指向前面創建的AppWidgetProvider子類,內容如下:
- <receiver android:name="widget"
- android:label="@string/app_name"
- android:icon="@drawable/icon">
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider"
- android:resource="@xml/widget_setting" />
- </receiver>
<receiver android:name="widget" android:label="@string/app_name" android:icon="@drawable/icon"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_setting" /> </receiver>
intent-filter中過濾了APPWIDGET_UPDATE事件,這個事件是由系統觸發的更新事件,每個widget必須包含這個事件;meta-data標籤描述的是widget的配置文件指向,該文件描述了widget的一些基本信息。
(3)編寫widget的provider文件信息,本例中該文件名叫做widget_setting.xml,開發者可以隨便取名,只要在AndroidMenifest.xml中寫正確就行。
- <?xml version="1.0" encoding="utf-8"?>
- <appwidget-provider
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="100dp"
- android:minHeight="100dp"
- android:initialLayout="@layout/main"
- android:updatePeriodMillis="1000" >
- </appwidget-provider>
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="100dp" android:minHeight="100dp" android:initialLayout="@layout/main" android:updatePeriodMillis="1000" > </appwidget-provider>
minWidth和minHeight是widget的最小寬度和高度,這個值是一個參考值,系統會根據實際情況進行改變,initialLayout屬性指明瞭widget的視圖佈局文件,updatePeriodMillis屬性是widget每隔多久更新一次的時間,單位爲毫秒。
(4)接下來就是界面佈局,在這個示例中只需要一個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="fill_parent"
- >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/hello" android:id="@+id/text"/>
- </LinearLayout>
<?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="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" android:id="@+id/text"/> </LinearLayout>
準備工作完畢,接下來完善繼承自AppWidgetProvider的自定義類,重寫onUpdate(Context, AppWidgetManager,int[])函數,代碼如下:
- package com.dt.time;
- import java.util.Date;
- import android.appwidget.AppWidgetManager;
- import android.appwidget.AppWidgetProvider;
- import android.content.Context;
- import android.widget.RemoteViews;
- public class widget extends AppWidgetProvider {
- @Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
- // TODO Auto-generated method stub
- super.onUpdate(context, appWidgetManager, appWidgetIds);
- //1. 獲取當前時間
- Date now = new Date();
- String strNow = now.toLocaleString();
- //2. 獲取RemoteViews對象
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
- //3. 顯示時間到widget
- views.setTextViewText(R.id.text, strNow);
- //4. 更新widget
- appWidgetManager.updateAppWidget(appWidgetIds, views);
- }
- }
package com.dt.time; import java.util.Date; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.widget.RemoteViews; public class widget extends AppWidgetProvider { @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // TODO Auto-generated method stub super.onUpdate(context, appWidgetManager, appWidgetIds); //1. 獲取當前時間 Date now = new Date(); String strNow = now.toLocaleString(); //2. 獲取RemoteViews對象 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main); //3. 顯示時間到widget views.setTextViewText(R.id.text, strNow); //4. 更新widget appWidgetManager.updateAppWidget(appWidgetIds, views); } }
之後運行寫好的widget查看下成果,widget的啓動與普通程序不同,它不會在程序列表中顯示,而是要長按桌面在彈出的列表中選擇Widgets項目,之後選擇本示例time,下圖就是widget運行時的截圖:
widget的擴展更新方法
在上例中widget更新是通過定時方式實現的,在普通情況下這種更新方式已經足夠了,但是對於某些應用使用定時方式更新顯得就不夠用了。比如短信提示,當有新的短信到來時我們希望能夠實時的更新widget,如果還是用定時更新顯然是不行的,那麼能不能讓widget接受除appwidget_update之外的系統消息呢?答案是可以的。
仔細查看下文檔後可以發現,widget只是一個receiver,既然是receiver那麼也就可以接受所有的系統消息了,接下來使用短信提醒示例來演示widget接受系統其他消息的方式,本例中將只前一示例進行修改。
(1)修改AndroidMenifest.xml文件,向其中添加android.provider.Telephony.SMS_RECEIVED監聽事件,代碼如下:
- <receiver android:name=".widget" >
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- <action android:name="android.provider.Telephony.SMS_RECEIVED" />
- </intent-filter>
- <meta-data android:name="android.appwidget.provider"
- android:resource="@xml/widget_provider" />
- </receiver>
<receiver android:name=".widget" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget_provider" /> </receiver>
添加了這個短信監聽事件後,我們就已經向widget添加了監聽短信的功能了,實際操作非常簡單。
接下來需要添加一個閱讀短信的權限,在AndroidMenifest.xml中任意位置添加<uses-permission android:name="android.permission.RECEIVE_SMS" />
(2)然後還需要修改下widget_setting.xml文件,將其中的updatePeriodMillis屬性設爲0,也就是不定時更新,這樣可以展示這個widget是實時更新的。
(3)之後修改AppWidgetProvider的子類,使其將短信內容顯示到widget上。在本示例中我們將不再修改onUpdate(Context, AppWidgetManager,int[])函數,而是重寫onReceive(Context context, Intent intent)函數,這個函數其實能夠實現包括onUpdate在內的所有函數功能,代碼如下:
- package com.dt.time;
- import android.appwidget.AppWidgetManager;
- import android.appwidget.AppWidgetProvider;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.widget.RemoteViews;
- public class widget extends AppWidgetProvider {
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO Auto-generated method stub
- super.onReceive(context, intent);
- //1. 獲取RemoteViews對象
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
- //2. 顯示新消息提醒
- views.setTextViewText(R.id.text, "New message!");
- //3. 更新widget
- AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
- appWidgetManager.updateAppWidget(new ComponentName(context, widget.class), views);
- }
- }
package com.dt.time; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; public class widget extends AppWidgetProvider { @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub super.onReceive(context, intent); //1. 獲取RemoteViews對象 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main); //2. 顯示新消息提醒 views.setTextViewText(R.id.text, "New message!"); //3. 更新widget AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); appWidgetManager.updateAppWidget(new ComponentName(context, widget.class), views); } }
代碼與上一示例差別不多,唯一有區別的地方便是appWidgetManager的實例化,在上一示例中由於onUpdate()方法中有一個參數是appWidgetManager因此我們不需要單獨實例化該對象,但是在onReceive()方法中並沒有這個參數,因此我們需要實例化一個appWidgetManager,通過AppWidgetManager.getInstance(Context)方法就可以獲得一個appWidgetManager實例。當然由於onReceive()函數還缺少appWidgetIds這個參數,因此我們也不能直接使用updateAppWidget(int[] appWidgetIds, RemoteViews views)這個函數,而要改用updateAppWidget(ComponentName provider, RemoteViews views)這個函數,其中provider參數是一個ComponentName類型的值,簡單的說就是組件名,通過實例化一個組件便可;第二個參數是是一個視圖對象,就是想要展示的widget視圖。
通過這樣簡單的改動,當有新的短信息發到手機上時就能第一時間展示在widget中,做到了實時更新。
widget的進階更新方法
上面的示例已經讓widget可以實時的更新內容,但是如果要顯示電池電量的話,上面的方法還是不行的,爲什麼呢?原來android中並沒有爲獲取電池信息設計單獨的api,必須註冊爲一個service才能獲取。按照上面的思路那也簡單,只要我們寫個service,然後在系統中廣播更新消息就可以了;但是根據文檔中的說明,要獲取電池信息的service必須是通過Context.registerReceiver()這個函數來註冊一個監聽電池變化的事件才能獲取,這樣一來對我們設計就帶來了麻煩。
解決的方法倒也很簡單,就是單獨寫個service在裏面註冊一個監聽事件,當電池電量發生變化的時候就更新widget,看起來與我們上面的例子很相似,就是多了一個service,但是這裏還有一個不同的是,我們更新widget的方法不再是在onUpdate()或者是onReceive中進行,而是在service中直接對widget修改,似乎跟文檔上說的出入很大,但是看看上面顯示短信的例子我們會發現,在那個示例中似乎widget的更新與appWidgetProvider已經沒有什麼關係了,我們即沒有從參數獲得appWidgetManager實例,也沒從參數中獲得appWidgetIds,一切都是我們自己新建了一個,因而不再widget更新的方法完全是可行的。下面來介紹下代碼:
(1)本示例中沿用第二個示例的代碼,添加一個名叫service.java類
- package com.dt.time;
- import android.app.Service;
- import android.appwidget.AppWidgetManager;
- import android.content.BroadcastReceiver;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.IntentFilter;
- import android.graphics.Color;
- import android.os.IBinder;
- import android.widget.RemoteViews;
- public class mService extends Service {
- @Override
- public void onStart(Intent intent, int startId) {
- // TODO Auto-generated method stub
- super.onStart(intent, startId);
- registerReceiver(this.mBR, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- }
- //聲明一個廣播接受對象,用接受電池信息
- private BroadcastReceiver mBR = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // TODO Auto-generated method stub
- if(Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction()))
- {
- //這裏添加相關處理動作
- bLevel = intent.getIntExtra("level", 0); //獲取當前電量
- String value = String.valueOf(bLevel) + "%"; //顯示電量的文字
- AppWidgetManager awm = AppWidgetManager.getInstance(context);
- RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.main);
- views.setTextViewText(R.id.text, value);
- awm.updateAppWidget(new ComponentName(context, widget.class), views);
- }
- }
- };
- }
package com.dt.time; import android.app.Service; import android.appwidget.AppWidgetManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Color; import android.os.IBinder; import android.widget.RemoteViews; public class mService extends Service { @Override public void onStart(Intent intent, int startId) { // TODO Auto-generated method stub super.onStart(intent, startId); registerReceiver(this.mBR, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } //聲明一個廣播接受對象,用接受電池信息 private BroadcastReceiver mBR = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub if(Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { //這裏添加相關處理動作 bLevel = intent.getIntExtra("level", 0); //獲取當前電量 String value = String.valueOf(bLevel) + "%"; //顯示電量的文字 AppWidgetManager awm = AppWidgetManager.getInstance(context); RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.main); views.setTextViewText(R.id.text, value); awm.updateAppWidget(new ComponentName(context, widget.class), views); } } }; }
(2)修改AndroidMenifest.xml文件,向其中添加一個<service android:name=".service"/>,這樣就註冊了一個系統服務,當widget啓動的時候這個服務也會跟着啓動,而當widget刪除的時候是否也會自動刪除呢?經過試驗大多數情況下OPhone會自動刪除這個service,如果你爲了安全也可以手動刪除。
之後就可以運行看看效果了,這裏展示的截圖是我作的一個顯示電池電量的widget,目前除了能夠顯示電量外還添加了其他信息的顯示。