Android之AppWidget桌面小部件开发
来源:互联网 发布:淘宝店公告栏在哪里写 编辑:程序博客网 时间:2024/04/28 14:35
做需求中遇到AppWidget相关知识,所以学习了官方文档和网上中文材料,并简单学习做了一个相关的Demo。
- Widget了解
- Widget主要组件
- 创建自定义Widget
Widget了解
App Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget
Widget组成
AppWidgetProviderInfo
描述一个App Widget元数据,比如App Widget的布局,更新频率,这个应该在XML里定义AppWidgetProvider
Android中提供的用于实现桌面小工具的类,其本质是一个广播,即BroadcastReceiver,用它来管理widget接收到广播后做出对应的响应处理widget_layout
用来设置widget的相关布局,但是Widget并不支持所有的布局和控件,这点需要注意。
创建一个Widget
建立一个Widget示例,要求Widget能被添加到主屏中,widget包含3个成分:文本、按钮和图片。文本要求:显示提示信息;按钮要求:点击按钮,弹出一个Toast提示框;图片要求:每个5秒随机更新一张图片
1.在修改Manifest中配置,在Manifest中注册AppWidgetProvider和负责发送更新图片广播的Service
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <!-- 声明widget对应的AppWidgetProvider --> <receiver android:name=".ExampleAppWidgetProvider" > <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="com.leeds.widget.UPDATE_ALL"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/example_appwidget_info" /> </receiver> <service android:name=".ExampleAppWidgetService" > <intent-filter> <action android:name="android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE" /> </intent-filter> </service> </application>
android.appwidget.action.APPWIDGET_UPDATE,必须要显示声明的action!因为所有的widget的广播都是通过它来发送的;要接收widget的添加、删除等广播,就必须包含它。
com.leeds.widget.UPDATE_ALL,是自定义的action,是为了接收服务所发送的更新图片的广播。
ExampleAppWidgetService 是用于更新widget中的图片的服务。
android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE 用于启动服务的action
2.编辑AppWidgetProviderInfo对应的资源文件
<?xml version="1.0" encoding="utf-8"?><appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="180dp" android:minHeight="180dp" android:previewImage="@mipmap/ic_note" android:initialLayout="@layout/activity_main" android:resizeMode="horizontal|vertical" android:widgetCategory="home_screen"></appwidget-provider>
文件中各个属性意义如下
minWidth 和minHeight
它们指定了App Widget布局需要的最小区域。缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格(cell)中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
注意:app widget的最小尺寸,不建议比 “4x4” 个单元格要大,基于适配性考虑minResizeWidth 和 minResizeHeight
它们属性指定了 widget 的最小绝对尺寸。也就是说,如果 widget 小于该尺寸,便会因为变得模糊、看不清或不可用。 使用这两个属性,可以允许用户重新调整 widget 的大小,使 widget 的大小可以小于 minWidth 和 minHeight。
Tip:
当 minResizeWidth 的值比 minWidth 大时,minResizeWidth 无效;当 resizeMode 的取值不包括 horizontal 时,minResizeWidth 无效。minResizeHeight对应同理。updatePeriodMillis
它定义了 widget 的更新频率。实际的更新时机不一定是精确的按照这个时间发生的。建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置。 实际上,当updatePeriodMillis的值小于30分钟时,系统会自动将更新频率设为30分钟!这是出于对电池的保护,因为如果当更新时机到达时设备正在休眠,那么就会唤醒设备以执行更新,频繁唤醒对电池寿命有影响。因此当需要较频繁的更新时,可以使用基于alarm的更新来代替widget自身的刷新机制,将 alarm 类型设置为 ELAPSED_REALTIME 或 RTC,将不会唤醒休眠的设备,同时请将 updatePeriodMillis 设为 0。或者使用service,AlarmManager,Timer等方式。
initialLayout
指向 widget 的布局资源文件configure
可选属性,定义了 widget 的配置 Activity。如果定义了该项,那么当 widget 创建时,会自动启动该 Activity。previewImage
指定预览图,该预览图在用户选择 widget 时出现,如果没有提供,则会显示应用的图标。该字段对应在 AndroidManifest.xml 中 receiver 的 android:previewImage 字段。由 Android 3.0 引入。autoAdvanceViewId
指定一个子view ID,表明该子 view 会自动更新。在 Android 3.0 中引入。resizeMode
指定了 widget 的调整尺寸的规则。可取的值有: “horizontal”, “vertical”, “none”。”horizontal”意味着widget可以水平拉伸,“vertical”意味着widget可以竖值拉伸,“none”意味着widget不能拉伸;默认值是”none”。Android 3.1 引入。widgetCategory
指定了 widget 能显示的地方:能否显示在 home Screen 或 lock screen 或 两者都可以。它的取值包括:”home_screen” 和 “keyguard”。Android 4.2 引入。
tip:
只有低于5.0的Android系统可以将widget添加到lock screen,5.0及5.0以上的系统规定widget只有添加到home screen有效。initialKeyguardLayout
指向 widget 位于 lockscreen 中的布局资源文件。Android 4.2 引入。
3.编辑widget的布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="HomeScreen Widget" android:textColor="#242424"/> <Button android:id="@+id/btn_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Show" /> </LinearLayout> <ImageView android:id="@+id/iv_show" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center"/></LinearLayout>
要注意的是widget并不支持所有的布局和控件,而是仅支持Android布局和控件的一个子集。具体支持的布局可以查看官方文档。
4.编辑AppWidgetProvider的实现类
当创建第一个widget到桌面时,执行onEnable,启动发送更新图片广播服务,当删除最后一个widget时,终止该服务;
每添加一个widget调用一次onUpdate,讲对应widget的id添加到widgetSet中;
onReceive()中,处理两个广播:
(1) 更新桌面的widget 以及 响应按钮点击广播。当收到ACTION_UPDATE_ALL广播时,调用updateAllAppWidgets()来更新所有的widget。
(2) 当收到的广播的categery为Intent.CATEGORY_ALTERNATIVE,并且scheme为BUTTON_SHOW时,对应是按钮点击事件。按钮的监听是在updateAllAppWidgets()中注册的。
public class ExampleAppWidgetProvider extends AppWidgetProvider { private static final String TAG = "ExampleWidgetProvider"; private boolean DEBUG = false; //启动service服务对应的action private final Intent EXAMPLE_SERVICE_INTENT = new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE"); //更新widget的广播对应的action private final String ACTION_UPDATE_ALL = "com.example.user.widgettest.UPDATE_ALL"; //保存widget的id的hashset,每新建一个widget都会为该widget分配一个id private static Set idsSet = new HashSet<>(); //按钮信息 private static final int BUTTON_SHOW = 1; //图片数组 private static final int[] ARR_IMAGES = { R.mipmap.sample_0, R.mipmap.sample_1, R.mipmap.sample_2, R.mipmap.sample_3, R.mipmap.sample_4, R.mipmap.sample_5, R.mipmap.sample_6, R.mipmap.sample_7,}; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 每次 widget 被创建时,对应的将widget的id添加到set中 for (int appWidgetId : appWidgetIds) { idsSet.add(Integer.valueOf(appWidgetId)); } } @Override public void onEnabled(Context context) { // 在第一个 widget 被创建时,开启服务 context.startService(EXAMPLE_SERVICE_INTENT); super.onEnabled(context); } @Override public void onDisabled(Context context) { // 在最后一个 widget 被删除时,终止服务 context.stopService(EXAMPLE_SERVICE_INTENT); super.onDisabled(context); } /** * widget被删除的时候调用 * @param context * @param appWidgetIds */ @Override public void onDeleted(Context context, int[] appWidgetIds) { // 当 widget 被删除时,对应的删除set中保存的widget的id for (int appWidgetId : appWidgetIds) { idsSet.remove(Integer.valueOf(appWidgetId)); } } @Override public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) { super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); } @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (ACTION_UPDATE_ALL.equals(action)) { // “更新”广播 updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet); } else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) { // “按钮点击”广播 Uri data = intent.getData(); int buttonId = Integer.parseInt(data.getSchemeSpecificPart()); if (buttonId == BUTTON_SHOW) { Toast.makeText(context, "Button Clicked", Toast.LENGTH_SHORT).show(); } } super.onReceive(context, intent); } private void updateAllAppWidgets(Context context, AppWidgetManager instance, Set idsSet) { // widget 的id int appID; // 迭代器,用于遍历所有保存的widget的id Iterator it = idsSet.iterator(); while (it.hasNext()) { appID = ((Integer) it.next()).intValue(); // 随机获取一张图片 int index = (new java.util.Random().nextInt(ARR_IMAGES.length)); if (DEBUG) Log.d(TAG, "onUpdate(): index=" + index); // 获取 example_appwidget.xml 对应的RemoteViews RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.activity_main); // 设置显示图片 remoteView.setImageViewResource(R.id.iv_show, ARR_IMAGES[index]); // 设置点击按钮对应的PendingIntent:即点击按钮时,发送广播。 remoteView.setOnClickPendingIntent(R.id.btn_show, getPendingIntent(context, BUTTON_SHOW)); // 更新 widget instance.updateAppWidget(appID, remoteView); } } /** * 设置点击按钮对应的PendingIntent * @param context * @param buttonShowId * @return */ private PendingIntent getPendingIntent(Context context, int buttonShowId) { Intent intent = new Intent(); intent.setClass(context, ExampleAppWidgetProvider.class); intent.addCategory(Intent.CATEGORY_ALTERNATIVE); intent.setData(Uri.parse("custom:" + buttonShowId)); PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0); return pi; } }
AppWidgetProvider中的广播处理函数如下:
onUpdate()
当 widget 更新时被执行。同样,当用户首次添加 widget 时,onUpdate() 也会被调用,这样 widget 就能进行必要的设置工作(如果需要的话) 。但是,如果定义了 widget 的 configure属性,那么当用户首次添加 widget 时,onUpdate()不会被调用;之后更新 widget 时,onUpdate才会被调用。
onAppWidgetOptionsChanged()
当widget 被初次添加或者当widget的大小被改变时,执行onAppWidgetOptionsChanged()。你可以在该函数中,根据 widget 的大小来显示/隐藏某些内容。可以通过 getAppWidgetOptions() 来返回 Bundle 对象以读取 widget 的大小信息,Bundle中包括以下信息:
OPTION_APPWIDGET_MIN_WIDTH – 包含 widget 当前宽度的下限,以dp为单位。
OPTION_APPWIDGET_MIN_HEIGHT – 包含 widget 当前高度的下限,以dp为单位。
OPTION_APPWIDGET_MAX_WIDTH – 包含 widget 当前宽度的上限,以dp为单位。
OPTION_APPWIDGET_MAX_HEIGHT – 包含 widget 当前高度的上限,以dp为单位。
onAppWidgetOptionsChanged() 是 Android 4.1 引入的。onDeleted(Context, int[])
当 widget 被删除时被触发。
onEnabled(Context)
当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。
onDisabled(Context)
当最后1个 widget 的实例被删除时触发。
onReceive(Context, Intent)
接收到任意广播时触发,并且会在上述的方法之前被调用。
总结:
AppWidgetProvider 继承于 BroadcastReceiver。实际上,App Widge中的onUpdate()、onEnabled()、onDisabled()等方法都是在 onReceive()中调用的,是onReceive()对特定事情的响应函数。
4.编辑发送更新图片广播的Service
public class ExampleAppWidgetService extends Service { private static final String TAG="ExampleAppWidgetService"; //更新widget的广播对应的action private final String ACTION_UPDATE_ALL ="com.example.user.widgettest.UPDATE_ALL"; //周期性更新widget的周期 private static final int UPDATE_TIME=5000; //周期性更新widget的线程 private UpdateThread mUpdateThread; private Context mContext; //更新周期的计数 private int count=0; @Override public void onCreate() { // 创建并开启线程UpdateThread mUpdateThread = new UpdateThread(); mUpdateThread.start(); mContext = this.getApplicationContext(); super.onCreate(); } @Override public void onDestroy() { // 中断线程,即结束线程。 if (mUpdateThread != null) { mUpdateThread.interrupt(); } super.onDestroy(); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } /** * 服务开始时,即调用startService * @param intent * @param flags * @param startId * @return */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand"); super.onStartCommand(intent, flags, startId); return START_STICKY; } private class UpdateThread extends Thread{ @Override public void run() { super.run(); try { count=0; while(true){ Log.d(TAG,"run...count:"+count); count++; Intent updateIntent =new Intent(ACTION_UPDATE_ALL); mContext.sendBroadcast(updateIntent); Thread.sleep(UPDATE_TIME); } } catch (InterruptedException e) { e.printStackTrace(); } } }}
服务UpdateThread 每隔5秒,发送1个广播ACTION_UPDATE_ALL。广播ACTION_UPDATE_ALL在ExampleAppWidgetProvider 被处理:用来更新widget中的图片。
完成后效果:
参考资料
[1] 官方文档
[2] Android 之窗口小部件详解–App Widget
- Android之AppWidget桌面小部件开发
- 【Android】AppWidget桌面小部件
- Android桌面小部件AppWidget
- Android Launcher开发(二)AppWidget(桌面小部件)解析
- Android Launcher开发(二)AppWidget(桌面小部件)解析
- Android Launcher开发(二)AppWidget(桌面小部件)解析
- Android基础学习之AppWidget(桌面小部件)
- appwidget桌面小部件
- appwidget桌面小部件
- android 中的AppWidget(桌面小部件)
- Android桌面小部件AppWidget(1)
- Android桌面小部件AppWidget(2)
- Android中的AppWidget(桌面小部件)
- Android之小部件AppWidget
- appWidget(桌面小部件)
- Android小部件AppWidget
- Android 之窗口小部件详解--AppWidget
- Android桌面小部件AppWidget:音乐播放器桌面控制部件Widget(3)
- HRV非线性分析PoincarePlot:SD1,SD2计算算法
- eclipse如何安装struts2的插件 (2013-09-11 16:43:56)
- MXNET Windows+Anaconda下安装教程及Bug解决
- PX4源码分析3_pixhawk硬件结构
- Android的广播Receiver动态注册和静态注册
- Android之AppWidget桌面小部件开发
- 两串旋转练习题
- junit4.1.2导入及入门使用
- JZ2440 TFT LCD
- 【菜鸟之路】杨辉三角形
- VMware tools安装 提点
- Bootstrap 缩略图和警告
- iOS APP框架搭建简析(附GitHub托管Demo)
- 分布式下必须要知道的CAP理论