热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android之窗口小部件(Widget)详解

Android之窗口小部件详解--AppWidget版本号说明作者日期1.0添加AppWidge介绍和示例SkyWang201306271AppWidget简介AppWidget是

Android 之窗口小部件详解--App Widget

 

 

 版本号说明作者日期 
1.0 添加App Widge介绍和示例 Sky Wang2013/06/27 
    

 
1 App Widget简介

App Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget。

本文参考Android官方文本,先介绍App Widget的主要组件,然后再以示例来详细说明。

 

 


2 App Widget主要的相关类介绍

2.1 AppWidgetProvider

AppWidgetProvider 继承自 BroadcastReceiver,它能接收 widget 相关的广播,例如 widget 的更新、删除、开启和禁用等。

AppWidgetProvider中的广播处理函数如下:

onUpdate()
  当 widget 更新时被执行。
  同样,当用户首次添加 widget 时,onUpdate() 也会被调用,这样 widget 就能进行必要的设置工作(如果需要的话) 。但是,如果定义了 widget 的 configure属性(即android:config,后面会介绍),那么当用户首次添加 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()对特定事情的响应函数。参考android源码frameworks/base/core/java/android/appwidget/AppWidgetProvider.java中onReceive()的定义:

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);}
}

View Code

 

2.2 AppWidgetProviderInfo

AppWidgetProviderInfo描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider 类。这个应该在XML里定义。下面以XML示例来对AppWidgetProviderInfo中常用的类型进行说明。

示例XML:

android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen|keyguard"
android:initialKeyguardLayout="@layout/example_keyguard">


示例说明:
minWidth 和minHeight
  它们指定了App Widget布局需要的最小区域。
  缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
  注意:app widget的最小尺寸,不建议比 “4x4” 个单元格要大。关于app widget的尺寸,后面在详细说明。

minResizeWidth 和 minResizeHeight
  它们属性指定了 widget 的最小绝对尺寸。也就是说,如果 widget 小于该尺寸,便会因为变得模糊、看不清或不可用。 使用这两个属性,可以允许用户重新调整 widget 的大小,使 widget 的大小可以小于 minWidth 和 minHeight。
  注意:(01) 当 minResizeWidth 的值比 minWidth 大时,minResizeWidth 无效;当 resizeMode 的取值不包括 horizontal 时,minResizeWidth 无效。
           (02) 当 minResizeHeight 的值比 minHeight 大时,minResizeHeight 无效;当 resizeMode 的取值不包括 vertical 时,minResizeHeight 无效。

updatePeriodMillis
  它定义了 widget 的更新频率。实际的更新时机不一定是精确的按照这个时间发生的。建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置。 实际上,当updatePeriodMillis的值小于30分钟时,系统会自动将更新频率设为30分钟!关于这部分,后面会详细介绍。
  注意: 当更新时机到达时,如果设备正在休眠,那么设备将会被唤醒以执行更新。如果更新频率不超过1小时一次,那么对电池寿命应该不会造成多大的影响。 如果你需要比较频繁的更新,或者你不希望在设备休眠的时候执行更新,那么可以使用基于 alarm 的更新来替代 widget 自身的刷新机制。将 alarm 类型设置为 ELAPSED_REALTIME 或 RTC,将不会唤醒休眠的设备,同时请将 updatePeriodMillis 设为 0。

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 引入。

initialKeyguardLayout
  指向 widget 位于 lockscreen 中的布局资源文件。Android 4.2 引入。

 

 

 


3 App Widget布局说明

3.1 添加 widget 到lock screen中

  默认情况下(即不设置android:widgetCategory属性),Android是将widget添加到 home screen 中。
  但在Android 4.2中,若用户希望 widget 可以被添加到lock screen中,可以通过设置 widget 的 android:widgetCategory 属性包含keyguard来完成。

  当你把 widget 添加到lock screen中时,你可能对它进行定制化操作,以区别于添加到home screen中的情况。 你能够通过 getAppWidgetOptions() 来进行判断 widget 是被添加到lock screen中,还是home screen中。通过 getApplicationOptions() 获取 Bundle对象,然后读取 Bundle 的OPTION_APPWIDGET_HOST_CATEGORY值:若值为 WIDGET_CATEGORY_HOME_SCREEN, 则表示该 widget 被添加到home screen中; 若值为 WIDGET_CATEGORY_KEYGUARD,则表示该 widget 被添加到lock screen中。

  另外,你应该为添加到lock screen中的 widget 单独使用一个layout,可以通过 android:initialKeyguardLayout 来指定。而 widget 添加到home screen中的layout则可以通过 android:initialLayout 来指定。


3.2 布局

一 Widget窗口组件

  如上图所示,典型的App Widget有三个主要组件:一个边界框(A bounding box),一个框架(a Frame),和控件的图形控件(Widget Controls)和其他元素。App Widget并不支持全部的视图窗口,它只是支持视图窗口的一个子集,后面会详细说明支持哪些视图窗口。
  要设计美观的App Widget,建议在“边界框和框架之间(Widget Margins)”以及“框架和控件(Widget Padding)”之间填留有空隙。在Android4.0以上的版本,系统为自动的保留这些空隙。

 

二 Widget窗口大小

  在AppWidgetProviderInfo中已经介绍了,minWidth 和minHeight 用来指定了App Widget布局需要的最小区域。缺省的App Widgets所在窗口的桌面位置基于有确定高度和宽度的单元网格中。如果App Widget的最小长度或宽度和这些网格单元的尺寸不匹配,那么这个App Widget将上舍入(上舍入即取比该值大的最接近的整数——译者注)到最接近的单元尺寸。
  例如,很多手机提供4x4网格,平板电脑能提供8x7网格。当widget被添加到时,在满足minWidth和minHeight约束的前提下,它将被占领的最小数目的细胞。

粗略计算minWidth和minHeight,可以参考下面表格:

 

 

单元格个数
(行 / 列)
对应的设置大小 (dp)
(minWidth / minHeight)
140dp
2110dp
3180dp
4250dp
n70 × n − 30



详细计算minWidth和minHeight,要计算各个区域的大小。以下图为例:

计算结果:
minWidth = 144dp + (2 × 8dp) + (2 × 56dp) = 272dp
minHeight = 48dp + (2 × 4dp) = 56dp


3.3 App Widget支持的布局和控件

Widget并不支持所有的布局和控件,而仅仅只是支持Android布局和控件的一个子集。
(01) App Widget支持的布局:
FrameLayout
LinearLayout
RelativeLayout
GridLayout
(02) App Widget支持的控件:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper

 

 

 


4 App Widget应用实例

  应用实例需求:建立一个Widget示例,要求Widget能被添加到主屏中,widget包含3个成分:文本、按钮和图片。文本要求:显示提示信息;按钮要求:点击按钮,弹出一个Toast提示框;图片要求:每个5秒随机更新一张图片。

第1步 新建Android工程
新建空白的Android工程,即不需要在建立Activity子类。

第2步 编辑manifest
修改AndroidManifest.xml,代码如下:

xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android&#61;"http://schemas.android.com/apk/res/android"package&#61;"com.skywang.widget"android:versionCode&#61;"1"android:versionName&#61;"1.0" ><uses-sdkandroid:minSdkVersion&#61;"14"android:targetSdkVersion&#61;"17" /><applicationandroid:allowBackup&#61;"true"android:icon&#61;"&#64;drawable/ic_launcher"android:label&#61;"&#64;string/app_name"android:theme&#61;"&#64;style/AppTheme" ><receiver android:name&#61;".ExampleAppWidgetProvider" ><intent-filter><action android:name&#61;"android.appwidget.action.APPWIDGET_UPDATE" /><action android:name&#61;"com.skywang.widget.UPDATE_ALL"/>intent-filter><meta-data android:name&#61;"android.appwidget.provider"android:resource&#61;"&#64;xml/example_appwidget_info" />receiver><service android:name&#61;".ExampleAppWidgetService" ><intent-filter><action android:name&#61;"android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE" />intent-filter>service>application>manifest>

说明&#xff1a;
(01) ExampleAppWidgetProvider是继承于的AppWidgetProvider类&#xff0c;用来响应widget的添加、删除、更新等操作。
(02) android.appwidget.action.APPWIDGET_UPDATE&#xff0c;必须要显示声明的action&#xff01;因为所有的widget的广播都是通过它来发送的&#xff1b;要接收widget的添加、删除等广播&#xff0c;就必须包含它。
(03) action android:name&#61;"com.skywang.widget.UPDATE_ALL&#xff0c;这是我自己添加了&#xff0c;是为了接收服务所发送的更新图片的广播。
(04) 指定了 AppWidgetProviderInfo 对应的资源文件
    android:name -- 指定metadata名&#xff0c;通过android.appwidget.provider来辨别data。
    android:resource -- 指定 AppWidgetProviderInfo 对应的资源路径。即&#xff0c;xml/example_appwidget_info.xml。
(05) ExampleAppWidgetService 是用于更新widget中的图片的服务。
(06) android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE 用于启动服务的action。


第3步 编辑AppWidgetProviderInfo对应的资源文件
在当前工程下新建xml目录(若xml目录不存在的话)&#xff1b;并在xml目录下新建example_appwidget_info.xml文件。xml文件内容如下&#xff1a;

<appwidget-provider xmlns:android&#61;"http://schemas.android.com/apk/res/android"android:minWidth&#61;"180dp"android:minHeight&#61;"180dp"android:previewImage&#61;"&#64;drawable/preview"android:initialLayout&#61;"&#64;layout/example_appwidget"android:resizeMode&#61;"horizontal|vertical"android:widgetCategory&#61;"home_screen|keyguard">appwidget-provider>

说明&#xff1a;
(01) android:previewImage&#xff0c;用于指定预览图片。即搜索到widget时&#xff0c;查看到的图片。若没有设置的话&#xff0c;系统为指定一张默认图片。
(02) android:updatePeriodMillis 更新widget的时间间隔(ms)。在实际测试中&#xff0c;发现设置android:updatePeriodMillis的值为5秒时&#xff0c;不管用&#xff01;跟踪android源代码&#xff0c;发现&#xff1a;
当android:updatePeriodMillis的值小于30分钟时&#xff0c;会被设置为30分钟。也就意味着&#xff0c;即使将它的值设为5秒&#xff0c;实际上也是30分钟之后才更新。因此&#xff0c;我们若向动态的更新widget的某组件&#xff0c;最好通过service、AlarmManager、Timer等方式&#xff1b;本文采用的是service。

android源码中widget服务文件&#xff1a;frameworks/base/services/java/com/android/server/AppWidgetService.java
与 android:updatePeriodMillis相关代码如下&#xff1a;

...
private static final int MIN_UPDATE_PERIOD &#61; 30 * 60 * 1000; // 30 minutes
...void registerForBroadcastsLocked(Provider p, int[] appWidgetIds) {...// 获取updatePeriodMillis的值long period &#61; p.info.updatePeriodMillis;// 当updatePeriodMillis小于30分钟时&#xff0c;设为它为30分钟if (period < MIN_UPDATE_PERIOD) {period &#61; MIN_UPDATE_PERIOD;} mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,SystemClock.elapsedRealtime() &#43; period, period, p.broadcast);...
}

 


第4步 编辑example_appwidget.xml等资源文件
新建layout/example_appwidget.xml&#xff0c;代码如下&#xff1a;

xml version&#61;"1.0" encoding&#61;"utf-8"?>
<LinearLayout xmlns:android&#61;"http://schemas.android.com/apk/res/android"android:layout_width&#61;"match_parent"android:layout_height&#61;"match_parent"android:orientation&#61;"vertical" ><LinearLayout android:layout_width&#61;"wrap_content" android:layout_height&#61;"wrap_content"android:layout_gravity&#61;"center" android:orientation&#61;"horizontal" ><TextView android:layout_width&#61;"wrap_content" android:layout_height&#61;"wrap_content" android:text&#61;"HomeScreen Widget" /> <Buttonandroid:id&#61;"&#64;&#43;id/btn_show"android:layout_width&#61;"wrap_content" android:layout_height&#61;"wrap_content" android:text&#61;"Show" />LinearLayout> <ImageViewandroid:id&#61;"&#64;&#43;id/iv_show"android:layout_width&#61;"wrap_content" android:layout_height&#61;"wrap_content" android:layout_gravity&#61;"center"/> LinearLayout>

说明:

(01) example_appwidget布局中&#xff0c;包含了“1个文本&#xff0c;1个按钮和1个图片控件”。

点击下载&#xff1a;本工程中需要用到的图片
将所有的图片放到drawable目录中。这里的drawable广义的&#xff0c;指工程实际用到的图片所在目录&#xff1b;例如&#xff0c;我自己的是放在drawabld-hdmi中。


第5步 编辑ExampleAppWidgetProvider.java
在当前工程下&#xff0c;新建ExampleAppWidgetProvider.java&#xff0c;代码如下&#xff1a;

package com.skywang.widget;import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.net.Uri;
import android.widget.RemoteViews;
import android.widget.Toast;
import android.util.Log;import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;/** &#64;author : skywang * description : 提供App Widget*/public class ExampleAppWidgetProvider extends AppWidgetProvider {private static final String TAG &#61; "ExampleAppWidgetProvider";private boolean DEBUG &#61; false; // 启动ExampleAppWidgetService服务对应的actionprivate final Intent EXAMPLE_SERVICE_INTENT &#61; new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE");// 更新 widget 的广播对应的actionprivate final String ACTION_UPDATE_ALL &#61; "com.skywang.widget.UPDATE_ALL";// 保存 widget 的id的HashSet&#xff0c;每新建一个 widget 都会为该 widget 分配一个 id。private static Set idsSet &#61; new HashSet();// 按钮信息private static final int BUTTON_SHOW &#61; 1;// 图片数组private static final int[] ARR_IMAGES &#61; { R.drawable.sample_0, R.drawable.sample_1, R.drawable.sample_2, R.drawable.sample_3, R.drawable.sample_4, R.drawable.sample_5,R.drawable.sample_6,R.drawable.sample_7,};// onUpdate() 在更新 widget 时&#xff0c;被执行&#xff0c;
&#64;Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {Log.d(TAG, "onUpdate(): appWidgetIds.length&#61;"&#43;appWidgetIds.length);// 每次 widget 被创建时&#xff0c;对应的将widget的id添加到set中for (int appWidgetId : appWidgetIds) {idsSet.add(Integer.valueOf(appWidgetId));}prtSet();}// 当 widget 被初次添加 或者 当 widget 的大小被改变时&#xff0c;被调用
&#64;Override public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {Log.d(TAG, "onAppWidgetOptionsChanged");super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); } // widget被删除时调用
&#64;Override public void onDeleted(Context context, int[] appWidgetIds) { Log.d(TAG, "onDeleted(): appWidgetIds.length&#61;"&#43;appWidgetIds.length);// 当 widget 被删除时&#xff0c;对应的删除set中保存的widget的idfor (int appWidgetId : appWidgetIds) {idsSet.remove(Integer.valueOf(appWidgetId));}prtSet();super.onDeleted(context, appWidgetIds); }// 第一个widget被创建时调用
&#64;Override public void onEnabled(Context context) { Log.d(TAG, "onEnabled");// 在第一个 widget 被创建时&#xff0c;开启服务
context.startService(EXAMPLE_SERVICE_INTENT);super.onEnabled(context); } // 最后一个widget被删除时调用
&#64;Override public void onDisabled(Context context) { Log.d(TAG, "onDisabled");// 在最后一个 widget 被删除时&#xff0c;终止服务
context.stopService(EXAMPLE_SERVICE_INTENT);super.onDisabled(context); }// 接收广播的回调函数
&#64;Override public void onReceive(Context context, Intent intent) { final String action &#61; intent.getAction();Log.d(TAG, "OnReceive:Action: " &#43; action);if (ACTION_UPDATE_ALL.equals(action)) {// “更新”广播
updateAllAppWidgets(context, AppWidgetManager.getInstance(context), idsSet);} else if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {// “按钮点击”广播Uri data &#61; intent.getData();int buttonId &#61; Integer.parseInt(data.getSchemeSpecificPart());if (buttonId &#61;&#61; BUTTON_SHOW) {Log.d(TAG, "Button wifi clicked");Toast.makeText(context, "Button Clicked", Toast.LENGTH_SHORT).show();}}super.onReceive(context, intent); } // 更新所有的 widget private void updateAllAppWidgets(Context context, AppWidgetManager appWidgetManager, Set set) {Log.d(TAG, "updateAllAppWidgets(): size&#61;"&#43;set.size());// widget 的idint appID;// 迭代器&#xff0c;用于遍历所有保存的widget的idIterator it &#61; set.iterator();while (it.hasNext()) {appID &#61; ((Integer)it.next()).intValue(); // 随机获取一张图片int index &#61; (new java.util.Random().nextInt(ARR_IMAGES.length));if (DEBUG) Log.d(TAG, "onUpdate(): index&#61;"&#43;index); // 获取 example_appwidget.xml 对应的RemoteViews RemoteViews remoteView &#61; new RemoteViews(context.getPackageName(), R.layout.example_appwidget);// 设置显示图片
remoteView.setImageViewResource(R.id.iv_show, ARR_IMAGES[index]);// 设置点击按钮对应的PendingIntent&#xff1a;即点击按钮时&#xff0c;发送广播。
remoteView.setOnClickPendingIntent(R.id.btn_show, getPendingIntent(context,BUTTON_SHOW));// 更新 widget
appWidgetManager.updateAppWidget(appID, remoteView); } }private PendingIntent getPendingIntent(Context context, int buttonId) {Intent intent &#61; new Intent();intent.setClass(context, ExampleAppWidgetProvider.class);intent.addCategory(Intent.CATEGORY_ALTERNATIVE);intent.setData(Uri.parse("custom:" &#43; buttonId));PendingIntent pi &#61; PendingIntent.getBroadcast(context, 0, intent, 0 );return pi;}// 调试用&#xff1a;遍历setprivate void prtSet() {if (DEBUG) {int index &#61; 0;int size &#61; idsSet.size();Iterator it &#61; idsSet.iterator();Log.d(TAG, "total:"&#43;size);while (it.hasNext()) {Log.d(TAG, index &#43; " -- " &#43; ((Integer)it.next()).intValue());}}}
}

说明&#xff1a;
(01) 当我们创建第一个widget到桌面时&#xff0c;会执行onEnabled()。在onEnabled()中通过 context.startService(EXAMPLE_SERVICE_INTENT) 启动服务ExampleAppWidgetService。服务的作用就是每隔5秒发送一个ACTION_UPDATE_ALL广播给我们&#xff0c;用于更新widget中的图片。
       仅仅当我们创建第一个widget时才会启动服务&#xff0c;因为onEnabled()只会在第一个widget被创建时才执行。
(02) 当我们删除最后一个widget到桌面时&#xff0c;会执行onDisabled()。在onDisabled()中通过 context.stopService(EXAMPLE_SERVICE_INTENT) 终止服务ExampleAppWidgetService。
       仅仅当我们删除最后一个widget时才会终止服务&#xff0c;因为onDisabled()只会在最后一个widget被删除时才执行。
(03) 本工程中&#xff0c;每添加一个widget都会执行onUpdate()。例外情况&#xff1a;在定义android:configure的前提下&#xff0c;第一次添加widget时不会执行onUpdate()&#xff0c;而是执行android:configure中定义的activity。
(04) onReceive()中&#xff0c;处理两个广播&#xff1a;更新桌面的widget 以及 响应按钮点击广播。
       当收到ACTION_UPDATE_ALL广播时&#xff0c;调用updateAllAppWidgets()来更新所有的widget。
       当收到的广播的categery为Intent.CATEGORY_ALTERNATIVE&#xff0c;并且scheme为BUTTON_SHOW时&#xff0c;对应是按钮点击事件。按钮的监听是在updateAllAppWidgets()中注册的。

 

第6步 编辑ExampleAppWidgetService.java
在当前工程下&#xff0c;新建ExampleAppWidgetService.java&#xff0c;代码如下&#xff1a;

package com.skywang.widget;import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;/** &#64;author : skywang * description : 周期性更新AppWidget的服务*/public class ExampleAppWidgetService extends Service {private static final String TAG&#61;"ExampleAppWidgetService"; // 更新 widget 的广播对应的actionprivate final String ACTION_UPDATE_ALL &#61; "com.skywang.widget.UPDATE_ALL";// 周期性更新 widget 的周期private static final int UPDATE_TIME &#61; 5000;// 周期性更新 widget 的线程private UpdateThread mUpdateThread;private Context mContext;// 更新周期的计数private int count&#61;0; &#64;Overridepublic void onCreate() {// 创建并开启线程UpdateThreadmUpdateThread &#61; new UpdateThread();mUpdateThread.start();mContext &#61; this.getApplicationContext();super.onCreate();}&#64;Overridepublic void onDestroy(){// 中断线程&#xff0c;即结束线程。if (mUpdateThread !&#61; null) {mUpdateThread.interrupt();}super.onDestroy();}&#64;Overridepublic IBinder onBind(Intent intent) {return null;}/** 服务开始时&#xff0c;即调用startService()时&#xff0c;onStartCommand()被执行。*/&#64;Overridepublic 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 {&#64;Overridepublic void run() {super.run();try {count &#61; 0;while (true) {Log.d(TAG, "run ... count:"&#43;count);count&#43;&#43;;Intent updateIntent&#61;new Intent(ACTION_UPDATE_ALL);mContext.sendBroadcast(updateIntent);Thread.sleep(UPDATE_TIME);} } catch (InterruptedException e) {// 将 InterruptedException 定义在while循环之外&#xff0c;意味着抛出 InterruptedException 异常时&#xff0c;终止线程。
e.printStackTrace();}}}
}

说明&#xff1a;

(01) onCreate() 在创建服务时被执行。它的作用是创建并启动线程UpdateThread()。
(02) onDestroy() 在销毁服务时被执行。它的作用是注销线程UpdateThread()。
(03) 服务UpdateThread 每隔5秒&#xff0c;发送1个广播ACTION_UPDATE_ALL。广播ACTION_UPDATE_ALL在ExampleAppWidgetProvider被处理&#xff1a;用来更新widget中的图片。

 

点击下载&#xff1a;源代码

点击查看&#xff1a;skywang博客索引

widget在添加到桌面前的效果图&#xff1a;

widget在添加到桌面后的效果图&#xff1a;

 



转:https://www.cnblogs.com/xieyuan/p/3787517.html



推荐阅读
  • Android 图像色彩处理技术详解
    本文详细探讨了 Android 平台上的图像色彩处理技术,重点介绍了如何通过模仿美图秀秀的交互方式,利用 SeekBar 实现对图片颜色的精细调整。文章展示了具体的布局设计和代码实现,帮助开发者更好地理解和应用图像处理技术。 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 【问题】在Android开发中,当为EditText添加TextWatcher并实现onTextChanged方法时,会遇到一个问题:即使只对EditText进行一次修改(例如使用删除键删除一个字符),该方法也会被频繁触发。这不仅影响性能,还可能导致逻辑错误。本文将探讨这一问题的原因,并提供有效的解决方案,包括使用Handler或计时器来限制方法的调用频率,以及通过自定义TextWatcher来优化事件处理,从而提高应用的稳定性和用户体验。 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 在Android开发中,BroadcastReceiver(广播接收器)是一个重要的组件,广泛应用于多种场景。本文将深入解析BroadcastReceiver的工作原理、应用场景及其具体实现方法,帮助开发者更好地理解和使用这一组件。通过实例分析,文章详细探讨了静态广播的注册方式、生命周期管理以及常见问题的解决策略,为开发者提供全面的技术指导。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 在Android 4.4系统中,通过使用 `Intent` 对象并设置动作 `ACTION_GET_CONTENT` 或 `ACTION_OPEN_DOCUMENT`,可以从相册中选择图片并获取其路径。具体实现时,需要为 `Intent` 添加相应的类别,并处理返回的 Uri 以提取图片的文件路径。此方法适用于需要从用户相册中选择图片的应用场景,能够确保兼容性和用户体验。 ... [详细]
  • 掌握Android UI设计:利用ZoomControls实现图片缩放功能
    本文介绍了如何在Android应用中通过使用ZoomControls组件来实现图片的缩放功能。ZoomControls提供了一种简单且直观的方式,让用户可以通过点击放大和缩小按钮来调整图片的显示大小。文章详细讲解了ZoomControls的基本用法、布局设置以及与ImageView的结合使用方法,适合初学者快速掌握Android UI设计中的这一重要功能。 ... [详细]
  • 技术分享:深入解析GestureDetector手势识别机制
    技术分享:深入解析GestureDetector手势识别机制 ... [详细]
  • 开发笔记:深入解析Android自定义控件——Button的72种变形技巧
    开发笔记:深入解析Android自定义控件——Button的72种变形技巧 ... [详细]
  • 本文探讨了在Android应用中实现动态滚动文本显示控件的优化方法。通过详细分析焦点管理机制,特别是通过设置返回值为`true`来确保焦点不会被其他控件抢占,从而提升滚动文本的流畅性和用户体验。具体实现中,对`MarqueeText.java`进行了代码层面的优化,增强了控件的稳定性和兼容性。 ... [详细]
  • Android目录遍历工具 | AppCrawler自动化测试进阶(第二部分):个性化配置详解
    终于迎来了“足不出户也能为社会贡献力量”的时刻,但有追求的测试工程师绝不会让自己的生活变得乏味。与其在家消磨时光,不如利用这段时间深入研究和提升自己的技术能力,特别是对AppCrawler自动化测试工具的个性化配置进行详细探索。这不仅能够提高测试效率,还能为项目带来更多的价值。 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
  • PyQt5 QTextEdit:深入解析Python中多功能GUI库的应用与实现
    本文详细探讨了 PyQt5 中 QTextEdit 组件在 Python 多功能 GUI 库中的应用与实现。PyQt5 是 Qt 框架的 Python 绑定,提供了超过 620 个类和 6000 个函数及方法,广泛应用于跨平台应用程序开发。QTextEdit 作为其中的重要组件,支持丰富的文本编辑功能,如富文本格式、文本高亮和自定义样式等。PyQt5 的流行性不仅在于其强大的功能,还在于其易用性和灵活性,使其成为开发复杂用户界面的理想选择。 ... [详细]
author-avatar
李国龙度_476
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有