AppWidgetProvider
- android系统提供用于实现桌面小部件的类,继承BroadcastReceiver,所以其本质是一个广播,我们再使用的时候可以根据广播的使用方式来开发
RemoteViews
- 用于通知栏和桌面小部件,提供了一系列的set方法来跨进程更新界面,并且这些方法只是View全部方法的子集,支持的View类型也有限,RemoteViews的内部机制这里不讨论
PendingIntent
- 表示一种处于pending状态(待定、等待、即将发生)的意图,接下来将在某个特定的时刻发生,而Intent是立刻发生,PendingIntent的典型使用场景就是给RemoteViews设置点击事件
- PendingIntent.genActivity()相当于Contex.startActivity(),PendingIntent.genSerivice()相当于Contex.startSerivice(),PendingIntent.genBroadcast()相当于Contex.sendBroadcast()
桌面小部件的开发步骤
- 在新建一个布局文件比如lcq_widget.xml放在res/layout下
<?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/lcq_img" android:layout_width="match_parent" android:layout_height="match_parent" android:contentDescription="@string/todo" android:src="@drawable/lcq_img" /> </LinearLayout>
- 定义小部件的配置信息比如lcq_appwidget_provider_info.xml,并放在res/xml下
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/lcq_widget" android:minWidth="100dp" android:minHeight="100dp" android:updatePeriodMillis="888000000" />
initialLayout:桌面小工具使用的初始化布局;minHeight、minWidth为小工具的最小尺寸,updatePeriodMillis为自动更新的周期,单位毫秒
- 定义小部件的实现类比如LcqAppWidgetProvider
package com.lcq.lcqappwidgetprovider; 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.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.widget.RemoteViews; import android.widget.Toast; public class LcqAppWidgetProvider extends AppWidgetProvider { public static final String ACTION_ROTATE = "com.lcq.action.rotate.CLICK"; @Override public void onDeleted(Context context, int[] appWidgetIds) { //每删除一次桌面小部件就调用一次 super.onDeleted(context, appWidgetIds); Toast.makeText(context, "onDeleted", Toast.LENGTH_SHORT).show(); } @Override public void onEnabled(Context context) { //当窗口小部件第一次添加到桌面是调用该方法,可添加多次但是只在第一次添加时调用 super.onEnabled(context); Toast.makeText(context, "onEnabled", Toast.LENGTH_SHORT).show(); } @Override public void onDisabled(Context context) { //当最后一个改类型的桌面小部件被删除时调用该方法,注意是最后一个 super.onDisabled(context); Toast.makeText(context, "onDisabled", Toast.LENGTH_SHORT).show(); } @Override public void onReceive(Context context, Intent intent) { //这是广播的内置方法,用于分发据图的事件给其他方法 super.onReceive(context, intent); Toast.makeText(context, "onReceive:aciton:" + intent.getAction(), Toast.LENGTH_SHORT).show(); if (intent.getAction().equals(ACTION_ROTATE)) { Toast.makeText(context, "点击了图片,开始旋转", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.lcq_img); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); for (int i = 0; i < 37; i++) {//循环36次,将图片旋转360度 float degree = (i * 10) % 360; RemoteViews remoteViews = obtainRemoteViews(context); remoteViews.setImageViewBitmap(R.id.lcq_img, getBitmap(bitmap, degree)); appWidgetManager.updateAppWidget(new ComponentName(context, LcqAppWidgetProvider.class), remoteViews); try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } } //获取旋转后的bitmap,degree为旋转角度 private Bitmap getBitmap(Bitmap bitmap, float degree) { Matrix matrix = new Matrix(); matrix.reset(); matrix.setRotate(degree); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { //小部件被添加时或者每次小部件更新时都会调用一次改方法, //小部件的更新实际由updatePeriodMillis来指定,每个周期小部件都会自动更新异常 super.onUpdate(context, appWidgetManager, appWidgetIds); final int len = appWidgetIds.length; Toast.makeText(context, "onUpdate:len=" + len, Toast.LENGTH_SHORT).show(); for (int i = 0; i < len; i++) { updateWidget(context, appWidgetManager, appWidgetIds[i]); } } private void updateWidget(Context context, AppWidgetManager appWidgetManager, int id) { appWidgetManager.updateAppWidget(id, obtainRemoteViews(context)); } //创建使用布局RemoteViews,并给lcq_img设置点击事件,注意:clickIntent必须设置ComponentName,否则8.0以上机型会接收不到广播 private RemoteViews obtainRemoteViews(Context context) { RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.lcq_widget); Intent clickIntent = new Intent(ACTION_ROTATE); //在8.0以上广播机制有所变化,sendbroadcast前要指定下receiver的类 clickIntent.setComponent(new ComponentName(context, LcqAppWidgetProvider.class)); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, clickIntent, 0); remoteViews.setOnClickPendingIntent(R.id.lcq_img, pendingIntent); return remoteViews; } }
这里主要实现了一个带有ImageView的小部件,点击小部件的ImageView对图片进行360度旋转特别注意clickIntent必须设置ComponentName,否则8.0以上机型会接收不到广播;AppWidgetProvider重要的几个重写方法,比如onDeleted、onEnabled、onDisabled、onReceive、onUpdate方法的作用已经在代码里面做了注释
- 在AndroidManifest.xml中声明小部件,因为其本质是一个广播,所以需要进行静态广播注册
<receiver android:name="com.lcq.lcqappwidgetprovider.LcqAppWidgetProvider" android:exported="true"> <meta-data android:name="android.appwidget.provider" android:resource="@xml/lcq_appwidget_provider_info" /><!--小部件的配信息--> <intent-filter> <action android:name="com.lcq.action.rotate.CLICK" /><!--用于识别小部件的点击行为--> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /><!--系统规范,必须存在,如果不加则在手机的小部件里面不会出现--> </intent-filter> </receiver>
android:resource="@xml/lcq_appwidget_provider_info"小部件的配置信息; <action android:name="com.lcq.action.rotate.CLICK" />:用于识别小部件的点击行为;
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />:系统规范,必须存在,如果不加则在手机的小部件里面不会出现 - 编译代码生成apk文件,将其安装到手机
- 长按手机的桌面(不同品牌手机可能操作方式不一样)会弹出桌面设置界面,选择添加工具
- 然后定位到我们的应用,并选中
- 选中后回到桌面设置界面,可以看到我们开发的小工具已经被添加进来了,这个可以重复的操作,效果就是会添加多个小部件
- 我们回到桌面,然后点击我们开发的桌面小部件,可以看到已经转起来了
- 具体的开发步骤和注意事项已经讲完了,你可以在这个基础之上做出更复杂更好看的效果和功能