原文:https://blog.csdn.net/harvic880925/article/details/41445407
http://www.cnblogs.com/AndyGe/archive/2013/11/26/3442833.html
一、概述
AppWidget是應用程序窗口小部件(Widget)是微型的應用程序視圖,它可以被嵌入到其它應用程序中(比如桌面)並接收週期性的更新。你可以通過一個App Widget Provider來發佈一個Widget。官方文檔地址:《App Widgets》 這裏涉及到兩個方面的內容:AppWidgetProvider類和appwidget-provider標籤;
1、appwidget-provider標籤:
用來定義桌面widget的大小,初始狀態等等信息的,它的位置應該放在res/xml文件夾下,具體的xml參數如下:
android:minWidth: 最小寬度
android:minHeight: 最小高度
android:updatePeriodMillis: 更新widget的時間間隔(ms),"86400000"爲1個小時
android:previewImage: 預覽圖片
android:initialLayout: 加載到桌面時對應的佈局文件
android:resizeMode: widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以豎直拉伸
android:widgetCategory: widget可以被顯示的位置。home_screen表示可以將widget添加到桌面,keyguard表示widget可以被添加到鎖屏界面。
android:initialKeyguardLayout: 加載到鎖屏界面時對應的佈局文件
2、AppWidgetProvider類:
當widget要實時更新,要響應用戶操作時,就需要額外的類來輔助處理了,這個類就是AppWidgetProvider。
由於AppWidgetProvider要接收到當前widget的狀態(是否被添加,是否被刪除等),所以要接收通知,必然是派生自BroadcastReceiver。
AppWidgetProvider中的廣播處理函數如下:(根據不同的使用情況,重寫不同的函數)
onUpdate():
在3種情況下會調用OnUpdate()。onUpdate()是在main線程中進行,因此如果處理需要花費時間多於10秒,處理應在service中完成。
(1)在時間間隔到時調用,時間間隔在widget定義的android:updatePeriodMillis中設置;
(2)用戶拖拽到主頁,widget實例生成。無論有沒有設置Configureactivity,我們在Android4.4的測試中,當用戶拖拽圖片至主頁時,widget實例生 成,會觸發onUpdate(),然後再顯示activity(如果有)。這點和資料說的不一樣,資料認爲如果設置了Configure acitivity,就不會在一開始調
用onUpdate(),而實驗顯示當實例生成(包括創建和重啓時恢復),都會先調用onUpate()。在本例,由於此時在preference尚未有相關數據,創建
實例時不能有效進行數據設置。
(3)機器重啓,實例在主頁上顯示,會再次調用onUpdate()
onDeleted(Context,int[]):
當 widget 被刪除時被觸發。
onEnabled(Context):
當第1個 widget 的實例被創建時觸發。也就是說,如果用戶對同一個 widget 增加了兩次(兩個實例),那麼onEnabled()只會在第一次增加widget時觸發。
onDisabled(Context):
當最後1個 widget 的實例被刪除時觸發。
onReceive(Context,Intent):
在接收到廣播時,調用。
3,清單配置
<receiver
android:name=".LedClockWidget"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_lock"/>
</receiver>
二、實戰
com/example/administrator/LedClockWidget.java
packagecom.example.administrator;
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.widget.RemoteViews;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class LedClockWidgetextends AppWidgetProvider {
private Timer mTimer = new Timer();
private AppWidgetManager mAppWidgerManager;
private Context mContext;
//將0-9的液晶數字圖片定義爲數組
private int[] digits = new int[]{R.drawable.p0, R.drawable.p1, R.drawable.p2, R.drawable.p3, R.drawable.p4, R.drawable.p5, R.drawable.p6, R.drawable.p7, R.drawable.p8, R.drawable.p9,};
//將顯示小時、分鐘、秒鐘的ImageView定義爲數組
private int[] digitViews = new int[]{R.id.img01, R.id.img02, R.id.img04, R.id.img05, R.id.img07, R.id.img08,};
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
this.mAppWidgerManager =appWidgetManager;
this.mContext =context;
//定義計時器
mTimer = new Timer();
//啓動週期性調度
mTimer.schedule(new TimerTask() {
@Override
public void run() {
//發送空消息,通知界面更新
handler.sendEmptyMessage(0x123);
}
}, 0, 1000);
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.main);
//定義SimpleDateFormat對象
SimpleDateFormat df = new SimpleDateFormat("HHmmss");
//將當前時間格式化爲HHmmss的形式
String timeStr = df.format(new Date());
for (int i = 0; i <timeStr.length(); i++) {
//將第i個數字字符zh轉換爲對應的數字
int num = timeStr.charAt(i) - 48;
//將第i個圖片設爲對應的液晶數字圖片
views.setImageViewResource(digitViews[i], digits[num]);
}
//將APPWidgetProvider子類實例包裝成ComponentName對象
ComponentName componentName = new ComponentName(mContext, LedClockWidget.class);
//調用APPWidgetManager將RemoteViews添加到ComponentName中
mAppWidgerManager.updateAppWidget(componentName, views);
}
super.handleMessage(msg);
}
};
}
layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/img01"
android:layout_width="30dp"
android:layout_height="30dp" />
<ImageView
android:id="@+id/img02"
android:layout_width="30dp"
android:layout_height="30dp" />
<ImageView
android:layout_width="10dp"
android:layout_height="30dp"
android:background="@drawable/maohao" />
<ImageView
android:id="@+id/img04"
android:layout_width="30dp"
android:layout_height="30dp" />
<ImageView
android:id="@+id/img05"
android:layout_width="30dp"
android:layout_height="30dp" />
<ImageView
android:layout_width="10dp"
android:layout_height="30dp"
android:background="@drawable/maohao" />
<ImageView
android:id="@+id/img07"
android:layout_width="30dp"
android:layout_height="30dp" />
<ImageView
android:id="@+id/img08"
android:layout_width="30dp"
android:layout_height="30dp" />
</LinearLayout>
xml/my_lock.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/main"
android:minHeight="70dp"
android:minWidth="150dp"
android:previewImage="@drawable/bbb"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="1000" />
app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.administrator">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".LedClockWidget"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_lock" />
</receiver>
</application>
</manifest>
三、Android4.0新增的顯示數據集:
setRemoteAdapter(intviewId,Intentintent):該方法可以使用Intent更新RemoteViews中viewId對應的組件。
上面方法的Intent參數應該封裝一個RemoteViewsService參數,RemoteViewsService雖然繼承了Service組件,但它的主要作用是爲RemoteViews中viewId對應的組件提供列表項。
由於Intent參數負責提供列表項,因此viewId參數對應的組件可以是ListView、GridView、StackView和AdapterViewFlipper等,這些組件都是AdapterView的子類,由此可見RemoteViewsService負責提供的對象,應該是一個類似於Adapter的對象。
RemoteViewsService通常用於被繼承,繼承該基類時需要重寫它的onGetViewFactory()方法,該方法就需要返回一個類似於Adapterr對象——但不是Adapter,而是RemoteViewsFactory對象,RemoteViewsFactory的功能完全類似於Adapter。
實例
StackWidgetService.java
package org.crazyit.desktop;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
public class StackWidgetService extends RemoteViewsService{
// 重寫該方法,該方法返回一個RemoteViewsFactory對象。
// RemoteViewsFactory對象的的作用類似於Adapter,
// 它負責爲RemoteView中指定組件提供多個列表項。
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent){
return new StackRemoteViewsFactory(this.getApplicationContext(),
intent); //①
}
class StackRemoteViewsFactory implements
RemoteViewsService.RemoteViewsFactory{
// 定義一個數組來保存該組件生成的多個列表項
private int[] items = null;
private Context mContext;
public StackRemoteViewsFactory(Context context, Intent intent)
{
mContext = context;
}
@Override
public void onCreate()
{
// 初始化items數組
items = new int[] { R.drawable.bomb5, R.drawable.bomb6,
R.drawable.bomb7, R.drawable.bomb8, R.drawable.bomb9,
R.drawable.bomb10, R.drawable.bomb11, R.drawable.bomb12,
R.drawable.bomb13, R.drawable.bomb14, R.drawable.bomb15,
R.drawable.bomb16
};
}
@Override
public void onDestroy()
{
items = null;
}
// 該方法的返回值控制該對象包含多少個列表項
@Override
public int getCount()
{
return items.length;
}
// 該方法的返回值控制各位置所顯示的RemoteViews
@Override
public RemoteViews getViewAt(int position)
{
// 創建RemoteViews對象,加載/res/layout目錄下widget_item.xml文件
RemoteViews rv = new RemoteViews(mContext.getPackageName(),
R.layout.widget_item);
// 更新widget_item.xml佈局文件中的widget_item組件
rv.setImageViewResource(R.id.widget_item,
items[position]);
// 創建Intent、用於傳遞數據
Intent fillInIntent = new Intent();
fillInIntent.putExtra(StackWidgetProvider.EXTRA_ITEM, position);
// 設置當單擊該RemoteViews時傳遞fillInIntent包含的數據
rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
// 此處使用讓線程暫停0.5秒來模擬加載該組件
try
{
System.out.println("加載【" + position + "】位置的組件");
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return rv;
}
@Override
public RemoteViews getLoadingView()
{
return null;
}
@Override
public int getViewTypeCount()
{
return 1;
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public boolean hasStableIds()
{
return true;
}
@Override
public void onDataSetChanged()
{
}
}
}
widget_item.xml
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget_item"
android:layout_width="120dp"
android:layout_height="120dp"
android:gravity="center"/>
StackWidgetProvider.java
package org.crazyit.desktop;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.Toast;
public class StackWidgetProvider extends AppWidgetProvider
{
public static final String TOAST_ACTION
= "org.crazyit.desktop.TOAST_ACTION";
public static final String EXTRA_ITEM
= "org.crazyit.desktop.EXTRA_ITEM";
@Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
// 創建RemoteViews對象,加載/res/layout目錄下的widget_layout.xml文件
RemoteViews rv = new RemoteViews(context.getPackageName(),
R.layout.widget_layout);
Intent intent = new Intent(context, StackWidgetService.class);
// 使用intent更新rv中stack_view組件(StackView)
rv.setRemoteAdapter(R.id.stack_view, intent); //①
// 設置當StackWidgetService提供的列表項爲空時,直接顯示empty_view組件
rv.setEmptyView(R.id.stack_view, R.id.empty_view);
// 創建啓動StackWidgetProvider組件(作爲BroadcastReceiver)的Intent
Intent toastIntent = new Intent(context,
StackWidgetProvider.class);
// 爲該Intent設置Action屬性
toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
// 將Intent包裝成PendingIntent
PendingIntent toastPendingIntent = PendingIntent
.getBroadcast(context, 0, toastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// 將PendingIntent與stack_view進行關聯
rv.setPendingIntentTemplate(R.id.stack_view,
toastPendingIntent);
// 使用AppWidgetManager通過RemteViews更新AppWidgetProvider
appWidgetManager.updateAppWidget(
new ComponentName(context, StackWidgetProvider.class), rv); //②
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds)
{
super.onDeleted(context, appWidgetIds);
}
@Override
public void onDisabled(Context context)
{
super.onDisabled(context);
}
@Override
public void onEnabled(Context context)
{
super.onEnabled(context);
}
// 重寫該方法,將該組件當成BroadcastReceiver使用
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(TOAST_ACTION))
{
// 獲取Intent中的數據
int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
// 顯示Toast提示
Toast.makeText(context, "點擊第【" + viewIndex + "】個列表項",
Toast.LENGTH_SHORT).show();
}
super.onReceive(context, intent);
}
}
widget_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp">
<StackView
android:id="@+id/stack_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:loopViews="true" />
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="#ff0f"
android:textColor="#ffffff"
android:textStyle="bold"
android:text="@string/no_item"
android:textSize="20sp" />
</FrameLayout>
Manifest.xml
<?xml version="1.0" encoding="utf-8" ?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="org.crazyit.desktop"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:label="@string/app_name">
<!-- 配置AppWidgetProvider,即配置桌面控件 -->
<receiver android:name=".StackWidgetProvider">
<!-- 通過該intent-filter指定該Receiver作爲桌面控件 -->
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<!-- 爲桌面控件指定meta-data -->
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/stackwidgetinfo" />
</receiver>
<!-- 配置RemoteViewsService
必須指定權限爲android.permission.BIND_REMOTEVIEWS
-->
<service
android:name=".StackWidgetService"
android:permission="android.permission.BIND_REMOTEVIEWS"
android:exported="false" />
</application>
</manifest>
stackwidgetinfo.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="110dp"
android:minHeight="110dp"
android:updatePeriodMillis="3600000"
android:previewImage="@drawable/ic_launcher"
android:initialLayout="@layout/widget_layout"
android:resizeMode="horizontal|vertical"
android:autoAdvanceViewId="@id/stack_view">
</appwidget-provider>
四、可能出現的錯誤:
1、有關佈局錯誤
在構造Widget佈局時,AppWidget支持的佈局和控件非常有限,有如下幾個:
AppWidget支持的佈局:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
AppWidget支持的控件:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
除此之外的所有控件(包括自定義控件)都無法顯示,無法顯示時,添加出來的widget會顯示“加載佈局出錯”
2、appwidget-provider出現錯誤
如果appwidget-provider頁面出現錯誤提示:error:No resource identifier found for attribute 'widgetCategory' in package'android'
這是由於buildtarget應該在17以上