桌面控件是通過 BroadcastReceiver
的形式來進行控制的壶冒,因此每個桌面控件都對應(yīng)于一個 BroadcastReceiver
。 為了簡化桌面控件的開發(fā)咸作,Android 系統(tǒng)提供了一個 AppWidgetProvider
類记罚,它就是 BroadcastReceiver
的子類桐智。也就是說然磷,開發(fā)者開發(fā)桌面控件只要繳承 AppWidgetProvider
類即可姿搜。
public class DesktopApp extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 加載指定界面布局文件舅柜,創(chuàng)建RemoteViews對象
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget); // ①
// 為show ImageView設(shè)置圖片
remoteViews.setImageViewResource(R.id.show, R.drawable.image); // ②
// 將AppWidgetProvider的子類實例包裝成ComponentName對象
ComponentName componentName = new ComponentName(context, DesktopApp.class); // ③
// 調(diào)用AppWidgetManager將remoteViews添加到ComponentName中
appWidgetManager.updateAppWidget(componentName, remoteViews); // ④
}
}
layout/my_widget.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="vertical">
<ImageView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:src="@drawable/logo" />
</LinearLayout>
xml/appwidget_provider.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- 指定該桌面控件的基本配置信息:
minWidth:桌面控件的最小寬度礁扮。
minWidth:桌面控件的最小高度雇锡。
updatePeriodMillis:更新頻率
initialLayout:初始時顯示的布局 -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/my_widget"
android:minWidth="150dip"
android:minHeight="70dip"
android:updatePeriodMillis="1000" />
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
tools:targetApi="31">
<receiver
android:name=".DesktopApp"
android:exported="true"
android:label="@string/app_name"
tools:ignore="IntentFilterExportedReceiver">
<!-- 將該BroadcastReceiver當成桌面控件 -->
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<!-- 指定桌面控件的meta-data -->
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider" />
</receiver>
</application>
</manifest>
把應(yīng)用安裝到 Android 系統(tǒng)上芳悲,然后桌面長按谅年,選擇 Widgets
拖動控件到桌面
實例:液晶時鐘
LedClock
public class LedClock extends AppWidgetProvider {
private Timer timer = new Timer();
private AppWidgetManager appWidgetManager;
private Context context;
// 將0~9的液晶數(shù)字圖片定義成數(shù)組
private int[] digits = new int[]{R.drawable.su01, R.drawable.su02,
R.drawable.su03, R.drawable.su04, R.drawable.su05,
R.drawable.su06, R.drawable.su07, R.drawable.su08,
R.drawable.su09, R.drawable.su10};
// 將顯示小時超燃、分鐘区拳、秒鐘的ImageView定義成數(shù)組
private int[] digitViews = new int[]{R.id.img01, R.id.img02, R.id.img04,
R.id.img05, R.id.img07, R.id.img08};
static class MyHandler extends Handler {
private WeakReference<LedClock> ledClock;
public MyHandler(WeakReference<LedClock> ledClock) {
this.ledClock = ledClock;
}
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
RemoteViews views = new RemoteViews(ledClock.get().context.getPackageName(), R.layout.clock);
// 定義SimpleDateFormat對象
SimpleDateFormat df = new SimpleDateFormat("HHmmss");
// 將當前時間格式化成HHmmss的形式
String timeStr = df.format(new Date());
for (int i = 0; i < timeStr.length(); i++) {
// 將第i個數(shù)字字符轉(zhuǎn)換為對應(yīng)的數(shù)字
int num = timeStr.charAt(i) - 48;
// 將第i個圖片設(shè)為對應(yīng)的液晶數(shù)字圖片
views.setImageViewResource(ledClock.get().digitViews[i],
ledClock.get().digits[num]);
}
// 將AppWidgetProvider子類實例包裝成ComponentName對象
ComponentName componentName = new ComponentName(ledClock.get().context,
LedClock.class);
// 調(diào)用AppWidgetManager將remoteViews添加到ComponentName中
ledClock.get().appWidgetManager.updateAppWidget(
componentName, views);
}
super.handleMessage(msg);
}
}
private Handler handler = new MyHandler(new WeakReference<>(this));
@Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager, int[] appWidgetIds) {
System.out.println("--onUpdate--");
this.appWidgetManager = appWidgetManager;
this.context = context;
// 定義計時器
timer = new Timer();
// 啟動周期性調(diào)度
timer.schedule(new TimerTask() {
@Override
public void run() {
// 發(fā)送空消息,通知界面更新
handler.sendEmptyMessage(0x123);
}
}, 0, 1000);
}
}
layout/clock.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">
<!-- 定義8個ImageView來顯示液晶數(shù)字 -->
<ImageView
android:id="@+id/img01"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/img02"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/img03"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/su00" />
<ImageView
android:id="@+id/img04"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/img05"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/img06"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/su00" />
<ImageView
android:id="@+id/img07"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/img08"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
xml/my_clock.xml
<?xml version="1.0" encoding="utf-8"?><!-- 指定該桌面組件的基本配置信息:
minWidth:桌面控件的最小寬度意乓。
minWidth:桌面控件的最小高度樱调。
updatePeriodMillis:更新頻率
initialLayout:初始時顯示的布局 -->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/clock"
android:minWidth="200dp"
android:minHeight="20dp"
android:updatePeriodMillis="1000" />
AndroidManifest.xml
<receiver
android:name=".LedClock"
android:exported="true"
android:label="@string/name"
tools:ignore="IntentFilterExportedReceiver">
<!-- 將該BroadcastReceiver當成桌面控件 -->
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<!-- 指定桌面控件的meta-data -->
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/my_clock" />
</receiver>
顯示帶數(shù)據(jù)集的桌面控件
StackWidgetService
public class StackWidgetService extends RemoteViewsService {
// 重寫該方法,該方法返回一個RemoteViewsFactory對象
// RemoteViewsFactory對象的作用類似于Adapter
// 它負責為RemoteView中的指定組件提供多個列表項
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new StackRemoteViewsFactory(this.getApplicationContext(), intent); // ①
}
class StackRemoteViewsFactory implements RemoteViewsFactory {
private Context mContext;
private Intent intent;
StackRemoteViewsFactory(Context mContext, Intent intent) {
this.mContext = mContext;
this.intent = intent;
}
// 定義一個數(shù)組來保存該組件生成的多個列表項
private int[] items;
@Override
public void onCreate() {
// 初始化items數(shù)組
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) {
// 創(chuàng)建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]);
// 創(chuàng)建Intent笆凌,用于傳遞數(shù)據(jù)
Intent fillInIntent = new Intent();
fillInIntent.putExtra(StackWidgetProvider.EXTRA_ITEM, position);
// 設(shè)置當單擊該RemoteViews時傳遞fillInIntent包含的數(shù)據(jù)
rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
// 此處讓線程暫停0.2秒來模擬加載該組件
System.out.println("加載【" + position + "】位置的組件");
try {
Thread.sleep(200);
} 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() {
}
}
}
layout/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
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) {
// 創(chuàng)建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); // ①
// 設(shè)置當StackWidgetService提供的列表項為空時跪妥,直接顯示empty_view組件
rv.setEmptyView(R.id.stack_view, R.id.empty_view);
// 創(chuàng)建啟動StackWidgetProvider組件(作為BroadcastReceiver)的Intent
Intent toastIntent = new Intent(context, StackWidgetProvider.class);
// 為該Intent設(shè)置Action屬性
toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
// 將Intent包裝成PendingIntent
PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context,
0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 將PendingIntent與stack_view進行關(guān)聯(lián)
rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);
// 使用AppWidgetManager通過RemoteViews更新AppWidgetProvider
appWidgetManager.updateAppWidget(new ComponentName(context,
StackWidgetProvider.class), rv); // ②
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
// 重寫該方法罐韩,將該組件當成BroadcastReceiver使用
@Override
public void onReceive(Context context, Intent intent) {
if (TOAST_ACTION.equals(intent.getAction())) {
// 獲取Intent中的數(shù)據(jù)
int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
// 顯示Toast提示
Toast.makeText(context, "點擊第" + viewIndex + "個列表項",
Toast.LENGTH_SHORT).show();
}
super.onReceive(context, intent);
}
}
layout/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:background="#ff0f"
android:gravity="center"
android:text="@string/no_item"
android:textColor="#ffffff"
android:textSize="20sp"
android:textStyle="bold" />
</FrameLayout>
AndroidManifest.xml
<!-- 配置AppWidgetProvider枚冗,即配置桌面控件 -->
<receiver android:name=".StackWidgetProvider"
android:exported="true">
<!-- 通過該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
必須指定權(quán)限為android.permission.BIND_REMOTEVIEWS
-->
<service
android:name=".StackWidgetService"
android:exported="false"
android:permission="android.permission.BIND_REMOTEVIEWS" />
摘抄至《瘋狂Android講義(第4版)》