您好,登錄后才能下訂單哦!
轉自我的新浪博文
一、概念
首先要區分widget和AppWidget這兩個概念。
1、Widget
widget可以直譯為小部件,它在Android中代表視圖的概念,如TextView、Button、EditText等widget視圖控件,及LinearLayout等視圖布局。
2、AppWidget
AppWidget是放置在手機屏幕的桌面小組件應用,如時鐘、日歷、天氣等組件,與一般應用程序有所不同。一般應用雖也可以以圖標的形式(快捷方式)放在桌面,但必須點擊運行和查看;而AppWidget一般不須點擊即直觀呈現其主要內容。當然,AppWidget也可以被設置為點擊打開其它屏幕或應用等。
而且,AppWidget可以被定時更新,如日歷每天更新,時鐘每分鐘更新等。當然,也可以在AppWidget的視圖界面中加入類似刷新的小按鈕,以進行實時更新,如天氣預報。
一般在提到Widget部件或Widget程序時,指的是AppWidget;如果說到widget控件,則可能是指視圖控件,如Button等。
3、操作
通過在桌面(HomeScreen)中長按,在彈出的對話框中選擇AppWidget部件來進行創建;或者在應用程序列表的AppWidget程序列表中選擇并長按來創建。同一個AppWidget部件可以在桌面同時創建多個。每新建一個,實際上是生成了一個新的AppWidget實例。
要刪除桌面的Widget部件,只需長按并拖動到垃圾箱即可。
二、一個簡單的AppWidget應用
1、簡單AppWidget組成
一個簡單的AppWidget應用只需包括以下部分:
AppWidgetProviderInfo對象:
這個對象為AppWidget提供元數據,包括布局、更新頻率等信息,這個對象定義在xml文件中,不需要自己編寫,由系統根據XML文件生成。
AppWidgetProvider類:
如圖所示,AppWidgetProvider類,繼承自BroadcastReceiver,可以接收并處理廣播事件。這個類定義了AppWidget的基本生命周期函數:
onReceive(Context, Intent) 接收廣播事件。
onUpdate(Context , AppWidgetManager, int[] appWidgetIds) 到達指定的更新時間或用戶向桌面添加widget時調用;實際是接受并處理“android.appwidget.action.APPWIDGET_UPDATE”廣播事件。appWidgetIds保存著已創建的各(桌面)AppWidget實例編號。在onUpdate方法中可以依次更新所有實例的界面內容。
onEnabled(Context) 當AppWidget實例第一次被創建時調用
onDeleted(Context, int[] appWidgetIds) 當一個AppWidget實例被刪除時調用
onDisabled(Context) 當最后一個AppWidget實例被刪除時調用
2、一個簡單應用開發
該應用很簡單,只是在桌面顯示一行文字。
(1)應用的界面布局文件res/layout/appwidget_provider_layout.xml:
(2)應用的元數據定義文件res/xml/appwidget_provider.xml:
(3)Widget實例提供程序SimpleWidgetProvider.java文件:
package com.example.simpleappwidget;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.util.Log;
import android.widget.RemoteViews;
import com.example.simpleappwidget.R;
public class SimpleWidgetProvider extends AppWidgetProvider {
private String TAG = "widgetexample";
周期更新時調用
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
final int N = appWidgetIds.length;
Log.i(TAG,String.valueOf(N));
for (int i = 0; i < N; i++)
{
int appWidgetId = appWidgetIds[i];
String message = "目前有"+N+"個AppWidget實例";
構建RemoteViews對象來對桌面部件進行更新
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
更新文本內容,指定布局的組件
views.setTextViewText(R.id.appwidget_text, message);
將RemoteViews的更新傳入AppWidget進行更新
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
該應用比較簡單,只是為了說明程序的結構。Provider類中僅提供了更新事件處理方法。運行界面如下圖:
可以創建多個實例:
但并沒有如估計的顯示“目前有2個實例”。
關閉模擬器并重新啟動打開,才顯示有兩個實例:
示例程序下載
問題解決:前面更新多個實例的問題,后面通過將以實例ID為參數逐一修改Widget組件實例的以下方法:
appWidgetManager.updateAppWidget(appWidgetId, views);
改為調用組件管理器的修改所有小組件實例的方法:
ComponentName myComponentName = new ComponentName(context, SimpleWidgetProvider.class);
appWidgetManager.updateAppWidget(myComponentName,views);
3、為AppWidget程序添加按鈕事件處理
天氣預報等桌面組件有類似功能,即點擊一個小圖標(按鈕)刷新數據顯示。這里只是簡單模擬一下類似功能。
點擊按鈕刷新數據,可以有多種方式實現。如點擊按鈕打開一個Activity、點擊發送廣播消息、點擊啟動一個服務等。下面首先看一下點擊打開Activity的關鍵代碼實現:
(1)按鈕事件處理可以在SimpleWidgetProvider類的onUpdate方法中實現:
......
for (int i = 0; i < N; i++)
{
int appWidgetId = appWidgetIds[i];
String message = "目前有"+N+"個AppWidget實例";
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
views.setTextViewText(R.id.appwidget_text, message);
為按鈕綁定點擊事件處理器
Intent intent = new Intent(context, MyActivity.class);
intent.putExtra("appWidgetId", appWidgetId);
Log.i(TAG,"ID:"+(intent.getExtras()).getInt("appWidgetId"));
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
views.setOnClickPendingIntent(R.id.mybutton, pendingIntent);
將RemoteViews的更新傳入AppWidget進行更新
appWidgetManager.updateAppWidget(appWidgetId, views);
}
......
需要指出的是如圖所示的PendingIntent的幾個常量值(用于getActivity等方法的參數):
這里因為Intent帶有數據,使用了PendingIntent.FLAG_CANCEL_CURRENT。
(2)MyActivity類的代碼:
......
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
Bundle bundle = getIntent().getExtras();
int appWidgetID = bundle.getInt("appWidgetId");
Log.i(TAG,"another ID:"+appWidgetID);
final Context context = this;
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
更新文本內容,指定布局的組件
views.setTextViewText(R.id.appwidget_text, "點擊按鈕更新內容");
取得AppWidgetManager實例
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(appWidgetID, views);
this.finish();
}
......
示例程序×××
上述功能也可以通過廣播消息進行處理。因為AppWidgetProvider本身就繼承自BroadcastReceiver,所以可以在SimpleWidgetProvider類的onReceive方法中實現對自定義消息的處理。關鍵代碼如下:
(1)SimpleWidgetProvider類代碼:
......
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
final int N = appWidgetIds.length;
Log.i(TAG,String.valueOf(N));
for (int i = 0; i < N; i++)
{
int appWidgetId = appWidgetIds[i];
......
Intent intent = new Intent("update_appwidget_textview");
intent.putExtra("appWidgetId", appWidgetId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
點擊按鈕將觸發廣播,當前接收器將即時接收和處理廣播消息
views.setOnClickPendingIntent(R.id.mybutton, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
......
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if(action.equals("update_appwidget_textview"))
{
Log.i(TAG,"update_appwidget_textview");
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
views.setTextViewText(R.id.appwidget_text, "點擊按鈕更新內容");
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int appWidgetId = (intent.getExtras()).getInt("appWidgetId");
appWidgetManager.updateAppWidget(appWidgetId, views);
}
else
super.onReceive(context, intent);
}
......
(2)AndroidManifest.xml文件:
(3)應用的界面布局文件res/layout/appwidget_provider_layout.xml:
(4)應用的元數據定義文件res/xml/appwidget_provider.xml:
示例程序代碼下載
另外,從資料中還查到一種利用ComponentName類修改AppWidget實例的方法,只需對上面代碼稍加改動:
onUpdate方法:
for (int i = 0; i < N; i++)
{
為了看到每次調用該方法時內容的變化
String message = System.currentTimeMillis()+"";
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
views.setTextViewText(R.id.appwidget_text, message);
......
Intent intent = new Intent("update_appwidget_textview");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
views.setOnClickPendingIntent(R.id.mybutton, pendingIntent);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
onReceive方法:
......
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName componentName = new ComponentName(context, SimpleWidgetProvider.class);
appWidgetManager.updateAppWidget(componentName, views);
......
使用廣播消息處理的方式當然也可以另外創建一個接收器,不再具體分析。示例程序下載
使用本地Service服務更新Widget實例的代碼下載
4、一個較實用的例子
例子比較簡單,只是在HomeScreen桌面實時顯示時間。
(1)SimpleWidgetProvider類關鍵代碼(onUpdate方法):
......
int appWidgetId = appWidgetIds[i];
Intent intent = new Intent("com.example.updatetime");
intent.putExtra("appWidgetId", appWidgetId);
context.startService(intent);
......
(2)ExampleService類代碼:
......
static int appWidgetId;
static RemoteViews views;
static AppWidgetManager appWidgetManager;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
views = new RemoteViews(this.getPackageName(),
R.layout.appwidget_provider_layout);
appWidgetManager = AppWidgetManager.getInstance(this);
appWidgetId = (intent.getExtras()).getInt("appWidgetId");
new TimeThread().start();
return super.onStartCommand(intent, flags, startId);
}
class TimeThread extends Thread
{
@Override
public void run ()
{
do{
try
{
Thread.sleep(1000);
views.setTextViewText(R.id.appwidget_text, DateFormat.format("hh:mm:ss",
System.currentTimeMillis()));
appWidgetManager.updateAppWidget(appWidgetId, views);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
} while(true);
}
}
......
桌面顯示時鐘的示例程序下載
該程序的更有效的代碼
(5)時鐘顯示程序的另一種解決方法
主要變化是使用android.content.Intent.ACTION_TIME_TICK時鐘服務,主要代碼如下:
SimpleWidgetProvider類:
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
final int N = appWidgetIds.length;
Log.i(TAG,String.valueOf(N));
String message = N+"個Widget實例";
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
views.setTextViewText(R.id.appwidget_text, message);
MyReceiver myReceiver = new MyReceiver();
IntentFilter myFilter = new IntentFilter();
myFilter.addAction(android.content.Intent.ACTION_TIME_TICK);
context.getApplicationContext().registerReceiver(myReceiver, myFilter);
ComponentName myComponentName = new ComponentName(context, SimpleWidgetProvider.class);
appWidgetManager.updateAppWidget(myComponentName,views);
}
public static void updateWidget(Context context,RemoteViews views)
{
ComponentName myComponentName = new ComponentName(context, SimpleWidgetProvider.class);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(myComponentName,views);
}
MyReceiver類:
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action.equals("android.intent.action.TIME_TICK"))
{
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
views.setTextViewText(R.id.appwidget_text, DateFormat.format("hh:mm",
System.currentTimeMillis()));
SimpleWidgetProvider.updateWidget(context, views);
}
}
示例程序×××
需要說明的是ACTION_TIME_TICK這個廣播消息是系統時鐘消息,該消息只能由系統以每分鐘一次的形式發送。不能在自定義類中通過sendbroadcast方法發出,否則會拋出“Permission Denial: not allowed to send broadcast android.intent.action.TIME_TICK”異常。
而且,程序中不能通過在manifest.xml里注冊的方式接收到這個廣播,只能在代碼里通過registerReceiver()方法注冊。
SDK文檔原文內容:Broadcast Action: The current time has changed. Sent every minute. You can not receive this through components declared in manifests, only by exlicitly registering for it withContext.registerReceiver().
通過測試,發現在配置文件中設置小組件更新周期不起作用。android:updatePeriodMillis="1000"設置一秒更新一次,完全沒有反應。只在長按程序生成桌面組件時,重新啟動模擬器后,才會調用onUpdate方法。
以上測試驗證了其它資料中提到新版本的Android屏蔽了小組件更新周期設置的說法。如果需要修改組件界面,需要在程序中使用如updateAppWidget(componentName, views)語句主動更新。
參考文章:
App Widgets
Android—AppWidget技術路線
Appwidget深入 -- 按鈕事件
Android之桌面組件App Widget案例
Android Service學習之本地服務
TextView顯示系統時間
解析APP觸發Widget實例
android.content.ReceiverCallNotAllowedException: 解決方法
android之IntentFilter的用法_Intent.ACTION_TIME_TICK在manifest.xml不起作用
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。