Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android學習筆記:Home Screen Widgets(2):關於Widget

Android學習筆記:Home Screen Widgets(2):關於Widget

編輯:關於Android編程

通過widget定義,我們在widget列表中看到了我們的TestWidget,當我們拖拽widget到主頁時,如果在appwidet-provider中定義了android:configure的java類,在widget實例創建後會馬上喚起配置activity。這個activity主要完成兩個任務:1、配置初始化數據;2、將配置數據適配到widget實例中。

利用preference中存貯配置數據

widget數據可以保持在文件、Share preference,或者SQLite3中。widget作為小工具配置數據量小,通常可以方便地存貯在preference中。preference中數據存貯和讀取使用BirthDayStoreData類來處理。我們在Pro Android學習筆記(六三):Preferences(7):代碼控制首選項中的“利用preference保存狀態”已經介紹過如何實現,在此,復習一下。

我們需要存貯的內容有widgetID,名字,生日,Preference是以鍵值對的方式保存,我們以name_widgetID作為key,生日作為value來進行信息存貯。

public class BirthDayStoreData {
private final static String BIRTHDAY_WIDGET_PROVIDER_NAME = "cn.wei.flowingflying.testwidget.provider";

//保存配置數據:創建widget實例,通過configure activity進行配置時,保存相關配置數據
public static void storeData(Context context,int widgetId, String name,String value){
String key = getKey(widgetId,name);
//第一個參數是preferences文件,如果不存在則創建之。具體為/data/data/cn.wei.flowingflying.testwidget/shared_prefs/cn.wei.flowingflying.testwidget.provider.xml,可以在DDMS中查看。
Editor editor = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE).edit();
editor.putString(key, value);
editor.commit();

}

//刪除配置數據:刪除widget實例的同時,需要刪除該實例的相關數據
public static void removeData(Context context, int widgetId){
String key = getKeyById(context, widgetId);
if(key == null)
return;
Editor editor = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE).edit();
editor.remove(key);
editor.commit();
}
//清空全部的配置數據
public static void removeAllData(Context context){
Editor editor = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE).edit();
editor.clear();
editor.commit();
}

//顯示配置數據:用於我們在LogCat中進行跟蹤,在此,我們也演示了如何通過輪詢方式,顯示全部的數據,通過類似的方式,我們可以同widgetId查得對應的名字和生日,通過類似的方法,可根據widgetId查詢key,名字,生日,相關代碼從略。
public static void showData(Context context){
SharedPreferences prefs = context.getSharedPreferences(BIRTHDAY_WIDGET_PROVIDER_NAME, Context.MODE_PRIVATE);
Map pairs = prefs.getAll();
Log.d("DATA","Total " + pairs.size() + " widgets:");
for(String key:pairs.keySet()){
String value = (String)pairs.get(key);

Log.d("DATA",key + " - " + value);
}
}

public static String getNameById(Context context, int widgetId){
… …
}

public static String getDateById(Context context ,int widgetId){
… …
}

private static String getKey(int widgetId, String name){
return name + "_" + widgetId;
}

private static String getKeyById(Context context,int widgetId){
… …
}

}

配置初始化數據

配置configure activity的代碼如下:

public class ConfigBirthDayWidgetActivity extends Activity{
private static String tag = "ConfigActivity";
private int myWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;

@Override //配置activity的操作和普通activity的一樣,但在被AppWidgetManage喚起時,intent是攜帶widgetId的信息,我們在onCreate()中獲取Widget ID。
protected void onCreate(Bundle savedInstanceState) {
… …
Intent intent = getIntent();
Bundle b = intent.getExtras();
if(b != null){
myWidgetId = b.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);
}

if(myWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID){
Toast.makeText(this, "Widget Error : 無有效widget ID", Toast.LENGTH_LONG).show();
finish();
}

}

.. ….

//點擊配置button後調用的方法
private void getAndStoreConfigInfo(){
… … String name為用戶輸入名字,String date為用戶輸入的有效日期
//【1】在preference中保持數據,並顯示所有數據
BirthDayStoreData.storeData(this, myWidgetId, name, date);
BirthDayStoreData.showData(this);
//【2】將配置數據與具體的widget實例相關聯,具體實現見後面
BirthDayStoreData.updateAppWidget(this, myWidgetId,name, date);

//【3】將結果返回給AppWidget Manager,以通知它Configurator已經完成。作用如同startActivityForResult()給出返回值,通知AppWidgetManager某個widgetId已經完成配置,可以在主頁上顯示創建的widget實例
Intent resultIntent = new Intent();
resultIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, myWidgetId);
setResult(RESULT_OK, resultIntent);

//【4】關閉activity
finish();
}
}

配置數據適配到widget實例中

Widget實例的view要通過RemoteViews進行控制,小例子采用靜態方法的方式,代碼片段如下:

public class BirthDayStoreData {
... ...

public static void updateAppWidget(Context context,int widgetId,String name, String date){
//【1】設置Remote view的信息
// 1.1)、獲得remote view對象
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.birday_widget);
// 1.2)、對remote view進行setText()設置
views.setTextViewText(R.id.bd_name, widgetId + ":" + name);
views.setTextViewText(R.id.bd_date, date);
views.setTextViewText
(R.id.bd_days, Long.toString(Utils.howFarInDays(Utils.getDate(date))));//Utils是處理日期的類
// 1.3)、通過PendingIntent設置某個view的點擊處理,采用intent方式,可以打開activity,service,receiver等等。本小例子將打開某個網頁
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("http://www.taobao.com"));
PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0);
views.setOnClickPendingIntent(R.id.bd_buy, pi);


//【2】通過AppWidgetManger,具體實施到widgetId實例上。
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(widgetId,views);

}

public static void updateAppWidget(Context context,int widgetId){
… …
}

}

根據widget定義,我們App Widget Provider的Java類為BirthDayWidgetProvider,這個類用於管理Widget的各個生命周期。

public class BirthDayWidgetProvider extends AppWidgetProvider{
private static String tag = "BirthDayWidgetProvider";

@Override /* 在3種情況下會調用OnUpdate()。onUpdate()是在main線程中進行,因此如果處理需要花費時間多於10秒,處理應在service中完成。
(1)在時間間隔到時調用,時間間隔在widget定義的android:updatePeriodMillis中設置;
(2)用戶拖拽到主頁,widget實例生成。
無論有沒有設置Configure activity,我們在Android4.4的測試中,當用戶拖拽圖片至主頁時,widget實例生成,會觸發onUpdate(),然後再顯示activity(如果有)。這點和資料說的不一樣,資料認為如果設置了Configure acitivity,就不會在一開始調用onUpdate(),而實驗顯示當實例生成(包括創建和重啟時恢復),都會先調用onUpate()。在本例,由於此時在preference尚未有相關數據,創建實例時不能有效進行數據設置。
(3)機器重啟,實例在主頁上顯示,會再次調用onUpdate()*/
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.d(tag,"onUpdate() called. 有 " + appWidgetIds.length + "個widgets");
for(int i = 0 ; i< appWidgetIds.length; i ++){
Log.d(tag,"update widget ID " + appWidgetIds[i]);
BirthDayStoreData.updateAppWidget(context, appWidgetIds[i]);
}
}

@Override /* 某個/些widget從主頁中刪除,在此刪除該widget的相關數據 */
public void onDeleted(Context context, int[] appWidgetIds) {
Log.d(tag,"onDeleted() called");
for(int i = 0 ; i < appWidgetIds.length; i ++){
Log.d(tag,"delete widget " + appWidgetIds[i] + " data");
BirthDayStoreData.removeData(context, appWidgetIds[i]);
}
BirthDayStoreData.showData(context);
}

@Override /* 一般無需重寫此方法。App Widget provider本質是receiver,在此可以跟蹤收到什麼消息,這些消息包括AppWidgetManager.ACTION_APPWIDGET_DELETED/UPDATE/ENABLED/DISABLED,super.onReceiver()會根據消息類型觸發不同的回調函數。如果采用AlarmManager或者自定義的廣播,可以再次進行處理。 */
public void onReceive(Context context, Intent intent) {

Log.i(tag,"onReceive() : " + intent);
super.onReceive(context, intent);
}

@Override /* 表明至少有一個widget實例被拖到主頁上,即當第一個widget出現時的回調函數。我們需要允許廣播接收器接收消息,第一個widget出現了。我們可以在此注冊其它感興趣的自定義的廣播*/
public void onEnabled(Context context) {

Log.d(tag,"onEnabled() called, context " + context.toString());
// setComponentEnabledSetting相當於在AndriodMenifest.xml文件中隊組件設置android:enabled為true|false。此處是對receiver進行設置,如果true,則允許進行監聽,包括開機重啟。
PackageManager pm = context.getPackageManager();
/*使用new ComponentName("cn.wei.flowingflying.testwidget",".BirthDayWidgetProvider")出現不明原因錯誤,
* 可對類名采用完全名稱,及new ComponentName("cn.wei.flowingflying.testwidget",
* "cn.wei.flowingflying.testwidget.BirthDayWidgetProvider"),
* 或通過系統獲取組件名的方式new ComponentName(context, getClass())*/
pm.setComponentEnabledSetting(new ComponentName(context, getClass()),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
}

@Override /*最後一個widget已從主頁中刪除,在此,確保刪除所有配置數據,無需進行廣播監聽,色織enabled=false,如果有注冊的自定義廣播,在此unregister */
public void onDisabled(Context context) {
BirthDayStoreData.removeAllData(context);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(new ComponentName(context, getClass()),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
}

}

如果主頁沒有實例,新實例的生成觸發順序為:onEnabled() –>onUpdate() –>Configure Activity,頭兩個順序可能會出現變化。估計是AppWidgetManager的異步處理導致廣播消息出現的先後順序問題。如果已經有實例,新實例生成觸發順序為onUpdate() –> Configure activity。配置後,等待定義的時間間隔,進行定期觸發onUpdate()。

機器重啟 onEnabled() –>onUpdate() –> onUpdate(),同樣頭兩個順序可能會交換,此後,等待widget定義的時間間隔,進行定期觸發onUpdate()。

如果我們更新或重裝apk,實例並不會被刪除,會觸發onUpdate()。

補充:Widget圖標

Widget在widget列表中顯示的通常都是widget的外貌,Android模擬器有一個應用Widget Preview可以幫助我們獲取widget的外觀圖標,如下:

\

通過adb pull將存貯在模擬器SD卡Download路徑下的preview圖片獲取,作為列表顯示圖標。



  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved