ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
#### 5.1.2 RemoteViews在桌面小部件上的应用 AppWidgetProvider是Android中提供的用于实现桌面小部件的类,其本质是一个广播,即BroadcastReceiver,图5-2所示的是它的类继承关系。所以,在实际的使用中,把AppWidgetProvider当成一个BroadcastReceiver就可以了,这样许多功能就很好理解了。 :-: ![](https://img.kancloud.cn/02/78/0278fa8d7c2f9deacb06489d2648c6d5_1356x454.png) 图5-2 AppWidgetProvider的类继承关系 为了更好地展示RemoteViews在桌面小部件上的应用,我们先简单介绍桌面小部件的开发步骤,分为如下几步。 * 1.定义小部件界面 在res/layout/下新建一个XML文件,命名为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/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/icon1" /> </LinearLayout> * 2.定义小部件配置信息 在res/xml/下新建appwidget_provider_info.xml,名称随意选择,添加如下内容: <? xml version="1.0" encoding="utf-8"? > <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget" android:minHeight="84dp" android:minWidth="84dp" android:updatePeriodMillis="86400000" > </appwidget-provider> 上面几个参数的意义很明确,initialLayout就是指小工具所使用的初始化布局,minHeight和minWidth定义小工具的最小尺寸,updatePeriodMillis定义小工具的自动更新周期,毫秒为单位,每隔一个周期,小工具的自动更新就会触发。 * 3.定义小部件的实现类 这个类需要继承AppWidgetProvider,代码如下: public class MyAppWidgetProvider extends AppWidgetProvider { public static final String TAG = "MyAppWidgetProvider"; public static final String CLICK_ACTION = "com.ryg.chapter_5.action. CLICK"; public MyAppWidgetProvider() { super(); } @Override public void onReceive(final Context context, Intent intent) { super.onReceive(context, intent); Log.i(TAG, "onReceive : action = " + intent.getAction()); // 这里判断是自己的action,做自己的事情,比如小部件被单击了要干什么,这里是做 一个动画效果 if (intent.getAction().equals(CLICK_ACTION)) { Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { Bitmap srcbBitmap = BitmapFactory.decodeResource( context.getResources(), R.drawable.icon1); AppWidgetManager appWidgetManager = AppWidgetManager. getInstance(context); for (int i = 0; i < 37; i++) { float degree = (i * 10) % 360; RemoteViews remoteViews = new RemoteViews(context .getPackageName(), R.layout.widget); remoteViews.setImageViewBitmap(R.id.imageView1, rotateBitmap(context, srcbBitmap, degree)); Intent intentClick = new Intent(); intentClick.setAction(CLICK_ACTION); PendingIntent pendingIntent = PendingIntent .getBroadcast(context, 0, intentClick, 0); remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent); appWidgetManager.updateAppWidget(new ComponentName( context, MyAppWidgetProvider.class), remoteViews); SystemClock.sleep(30); } } }).start(); } } /** * 每次桌面小部件更新时都调用一次该方法 */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); Log.i(TAG, "onUpdate"); final int counter = appWidgetIds.length; Log.i(TAG, "counter = " + counter); for (int i = 0; i < counter; i++) { int appWidgetId = appWidgetIds[i]; onWidgetUpdate(context, appWidgetManager, appWidgetId); } } /** *桌面小部件更新 * * @param context * @param appWidgeManger * @param appWidgetId */ private void onWidgetUpdate(Context context, AppWidgetManager appWidgeManger, int appWidgetId) { Log.i(TAG, "appWidgetId = " + appWidgetId); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); // “桌面小部件”单击事件发送的Intent广播 Intent intentClick = new Intent(); intentClick.setAction(CLICK_ACTION); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0); remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent); appWidgeManger.updateAppWidget(appWidgetId, remoteViews); } private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) { Matrix matrix = new Matrix(); matrix.reset(); matrix.setRotate(degree); Bitmap tmpBitmap = Bitmap.createBitmap(srcbBitmap, 0, 0, srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true); return tmpBitmap; } } 上面的代码实现了一个简单的桌面小部件,在小部件上面显示一张图片,单击它后,这个图片就会旋转一周。当小部件被添加到桌面后,会通过RemoteViews来加载布局文件,而当小部件被单击后的旋转效果则是通过不断地更新RemoteViews来实现的,由此可见,桌面小部件不管是初始化界面还是后续的更新界面都必须使用RemoteViews来完成。 * 4.在AndroidManifest.xmI中声明小部件 这是最后一步,因为桌面小部件本质上是一个广播组件,因此必须要注册,如下所示。 <receiver android:name=".MyAppWidgetProvider" > <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_provider_info" > </meta-data> <intent-filter> <action android:name="com.ryg.chapter_5.action.CLICK" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> </receiver> 上面的代码中有两个Action,其中第一个Action用于识别小部件的单击行为,而第二个Action则作为小部件的标识而必须存在,这是系统的规范,如果不加,那么这个receiver就不是一个桌面小部件并且也无法出现在手机的小部件列表里。 AppWidgetProvider除了最常用的onUpdate方法,还有其他几个方法:onEnabled、onDisabled、onDeleted以及onReceive。这些方法会自动地被onReceive方法在合适的时间调用。确切来说,当广播到来以后,AppWidgetProvider会自动根据广播的Action通过onReceive方法来自动分发广播,也就是调用上述几个方法。这几个方法的调用时机如下所示。 * onEnable:当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用。 * onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。 * onDeleted:每删除一次桌面小部件就调用一次。 * onDisabled:当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。 * onReceive:这是广播的内置方法,用于分发具体的事件给其他方法。 关于AppWidgetProvider的onReceive方法的具体分发过程,可以参看源码中的实现,如下所示。通过下面的代码可以看出,onReceive中会根据不同的Action来分别调用onEnable、onDisable和onUpdate等方法。 public void onReceive(Context context, Intent intent) { // Protect against rogue update broadcasts (not really a security issue, // just filter bad broacasts out so subclasses are less likely to crash). String action = intent.getAction(); if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { Bundle extras = intent.getExtras(); if (extras ! = null) { int[] appWidgetIds = extras.getIntArray(AppWidgetManager. EXTRA_APPWIDGET_IDS); if (appWidgetIds ! = null && appWidgetIds.length > 0) { this.onUpdate(context, AppWidgetManager.getInstance (context), appWidgetIds); } } } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) { Bundle extras = intent.getExtras(); if (extras ! = null && extras.containsKey(AppWidgetManager.EXTRA_ APPWIDGET_ID)) { final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_ APPWIDGET_ID); this.onDeleted(context, new int[] { appWidgetId }); } } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals (action)) { Bundle extras = intent.getExtras(); if (extras ! = null && extras.containsKey(AppWidgetManager.EXTRA_ APPWIDGET_ID) && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ OPTIONS)) { int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_ APPWIDGET_ID); Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_ APPWIDGET_OPTIONS); this.onAppWidgetOptionsChanged(context, AppWidgetManager. getInstance(context), appWidgetId, widgetExtras); } } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { this.onEnabled(context); } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) { this.onDisabled(context); } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) { Bundle extras = intent.getExtras(); if (extras ! = null) { int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_ APPWIDGET_OLD_IDS); int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_ APPWIDGET_IDS); if (oldIds ! = null && oldIds.length > 0) { this.onRestored(context, oldIds, newIds); this.onUpdate(context, AppWidgetManager.getInstance (context), newIds); } } } } 上面描述了开发一个桌面小部件的典型过程,例子比较简单,实际开发中会稍微复杂一些,但是开发流程是一样的。可以发现,桌面小部件在界面上的操作都要通过Remote-Views,不管是小部件的界面初始化还是界面更新都必须依赖它。