APP小挂件指的是一个小型的应用View控件,他可以嵌入到其他应用程序中(比如说桌面),并接受定期的更新。你可以通过Widget Provider来自己发布一个。一个可以持有其他App小挂件的应用组件叫做AppWidget host

下图表示一个音乐应用的挂件;

此文章将会描述怎么使用AppWidget provider去发布一个应用挂件。创建挂件牵涉到的类是:

android.appwidget.AppWidgetHost

一、基本的描述

要创建一个App挂件,你需要做到下面的工作。

  • AppWidgetProviderInfo对象

描述App挂件的元数据,比如说:挂件的布局,更新频率,和AppWidgetProvider类。你需在XML文件中定义

  • 继承AppWidgetProvider

定义一个基于广播事件的允许动态改变挂件界面的方法,通过他,当你的应用挂件更新、变得可用、变得不可用、被删除的事件你都可以收到。

  • View Layout

XML文件中定义一个挂件的初始布局文件。

此外,你可以实现一个挂件配置 Activity,他是一个可选的Activity,当用户要添加你的挂件时,他会启动用于给用户设置和更改挂件的设置。

下面将介绍怎么去设置一个这样的组件

Manifest中申明一个挂件

首先需要在Manifest中声明AppWidgetProvider类。比如说:

<receiver android:name="ExampleAppWidgetProvider" ><intent-filter><action android:name="android.appwidget.action.APPWIDGET_UPDATE"/></intent-filter><meta-data android:name="android.appwidget.provider"android:resource="@xml/example_appwidget_info" />
</receiver>

<receiver>元素需要androidname属性,他指的是挂件使用的AppWidgetProvider

<intent-filter>必须要包含一个<action>元素,并且包含androidname属性这个属性表示AppWidgetProvider接受ACTION_APPWIDGET_UPDATE广播。这是你唯一需要明确指定的广播接受,在必要的时候AppWidgetManager将会自动发送广播给所有挂件。

<meta-data>元素定义了AppWidgetProviderInfo资源,他需要有下面几个属性:

  • Android:name----定义元数据的名字,此例中使用android.appwidget.provider来定义这个数据,作为AppWidgetProviderInfo的描述信息
  • android:resource---定义AppWidgetProviderInfo资源的地址

添加AppWidgetProviderInfo元数据

AppWidgetProviderInfo定义了一个挂件的基本品质,比如说:最小布局尺寸、他初始化的布局资源、更新挂件的频率、和configurationActivity在创建的时候加载在XML文件中定义AppWidgetProviderInfo对象只需要使用<appwidget-provider>元素,保存在res/xml文件夹中。

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"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">
</appwidget-provider>

下面是<appwidget-provider>的属性概览:

  • minWidthminHeight的值表示挂件需要的最小空间。默认情况下,挂件的位置是基于主屏幕中的具有宽高的方格子(可以看做将屏幕划分为很多个矩阵格子)。如果挂件的宽和高与方格子不匹配,挂件的尺寸将计算到与方格子相近的尺寸。

【注意:为了使你的挂件能方便的跨设备,你最好将你的最小尺寸设置为低于4 X 4

  • minResizeWidthminResizeHeight属性指定了挂件的绝对最小尺寸,这个值应该定义为,当你的挂件出现字迹不清与不可用时的最小尺寸。使用这些属性允许用户去重新设置挂件的尺寸,可能会小于你使用minWidthminHigh的值。这个特性从 android 3.1开始
  • updatePeriodMillis属性定义了挂件多久被AppWidgetProvider调用onUpdate()刷新一次,事实上不能保证准时刷新(会有一定的出入),我们建议刷新频率不要太高,以便保持电量。当然你也可以允许用户自己去设置刷新的频率,有的人可能会设置15分钟刷新,当然也有人可能设置为一天只刷新4次。

【如果设备正在休眠,但是到了刷新你挂件的时间,设备将会被激活,然后去执行更新操作。如果你一个多小时的刷新一次可能不会对电池造成什么影响,但是,如果你刷新频率比较高,或者可能不需要挂件在休眠的时候刷新,那么你可以基于alarm刷新,他将不会激活设备。实现方式:使用你的AppWidgetProvider需要接受的Intent设置一个alarm使用AlarmManager,将alarm的类型设置为ELAPSED_REALTIME或者RTC,这样只有在设备唤醒的情况下才通知你的挂件刷新,最后你需要将你的updatePeriodMillis设置为 0

  • initialLayout属性指向一个定义挂件布局的XML文件。
  • configure定义了一个Activity,在用户添加挂件的时候他将会启动,以便用户去设置挂件的配置。这是可选操作。(具体怎么创建后面将会介绍到)
  • previewImage属性,用作预览的图片。当用户还未添加挂件的时候通过此图片让用户大致知道你的挂件。此属性与在Manifest中的<receiver>中的androidpreviewImage是一样的。(具体怎么设置后面将会介绍)
  • autoAdvanceViewId属性定义了可以被挂件宿主自增加的子Viewid。在android 3.0中被引进。
  • resizeMode属性指定了挂件被重定义大小的规则。你可以使用此挂件让你的挂件在主屏幕中可以被调节大小---水平,垂直,同时。用户长按挂件可以去显示调节把柄,然后拖拽把柄可以改变大小(还是以方格子为单元)。resizeMode的值包括:“horizontal”“vertical”“none”。如果你要设置为水平、垂直都可以改变,那么这样设置“horizontal | vertical”。在android 3.1中引进
  • minResizeHeight属性定义了那些可以被调节大小的挂件的最小的高度(使用dp做单位)。如果挂件比minHeight大或者挂件调节大小并不可用时,此字段将没有作用。在android 4.0中引进
  • minResizeWidth属性定义了最小的宽度(使用dp作单位),与minResizeHeight似的作用
  • widgetCategory属性表示你的挂件是否可以展示在主屏幕(home_screen)中,锁屏界面(keyguard)中,或者同时。只有在android 5.0以下的才可以使用锁屏界面的挂件(keyguard),在android5.0或以上只能使用 home_screen

参考类android.appwidget.AppWidgetProviderInfo了解更多

创建挂件的布局文件

你必须在XML文件中定义个初始化的布局文件,保存在你工程的res/layout/目录下。你可以使用下面的View对象来设计你的布局。但是,在你设计你的布局之前,你最好先阅读并理解挂件的设计指导方针AppWidget Design Guidelines:(见右侧)

创建挂件非常简单,如果你熟悉Layout的话。然而,你必须知道的是,挂件的布局使基于RemoteViewsandroid.widget.RemoteViews)的,不是所有的布局和view控件他都支持。

他们支持的layout如下:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

他们支持的控件如下:(他们的衍生品不支持)

  • AnalogClock【时钟控件】
  • Button
  • Chronometer【计时器】
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

(他们都在android.widget包下。)

RemoteView也支持使用ViewStubandroid.view.ViewStub)进行运行时懒加载。

为挂件添加margin

我们的控件一般不需要延生到边缘,也不要和其他挂件挤在一起了。因此我们需要在边界上加上一个的空白。那就是“Margin”或者“padding”

在android4.0以后,系统将在挂件之间自动加上padding。我们不需要自己手动的添加。如果低于4.0的,我们自己加上就行了。

例子如下:

<FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:padding="@dimen/widget_margin"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"android:background="@drawable/my_widget_background">…</LinearLayout></FrameLayout>

资源文件如下:

res/values/dimens.xml文件中这样定义:

<dimen name="widget_margin">8dp</dimen>

res/values-v14/dimens.xml文件中这样定义(由于系统默认给我们加了,我们再添加)

<dimen name="widget_margin">0dp</dimen>

另外一个可选操作则是:为有margin的布局做一个.9.png图片,为没有margin的布局做一个.9.png图片。

使用AppWidgetProvider

AppWidgetProvider继承了BroadCastReceiver类,方便其接受来自挂件广播的事件。AppWidgetProvider只接受与挂件相关的广播事件,比如说:挂件更新了、被删除了、挂件变得可用、挂件不可用等。当这些事件发生了,下面的对应方法将会被调用:

  •  onUpdate (Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)

每当过了updatePeriodMillis设置的时间后,此方法被调用来更新挂件。当挂件被添加时,此方法也会被调用。因此,他在挂件被创建的时候会被执行,例如:如果需要,为View定义一个事件handler并启动一个临时Service。但是,如果你创建了一个configuration Activity,创建挂件的时候,此方法将不会被调用。当configuration Activity执行完毕后,此方法将被调用(进行第一次更新)

  • onAppWidgetOptionsChanged (Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)

此方法在被第一次放置的时候,或者挂件大小被改变后被调用。你可以使用此回调函数去基于挂件的尺码范围去展示或者隐藏内容,你可以通过调用getAppWidgetOptions(intappWidgetId)方法去获取到尺码范围,他的返回值是一个Bundle类型的对象,包含了下面的内容:

  • AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH

包含了当前挂件的最小宽度尺寸(使用dp为单位)。

  • AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT

包含了当前挂件的最小高度尺寸(使用dp为单位)。

  • AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH

包含了当前挂件的最大宽度尺寸(使用dp为单位)。

  • AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT

包含了当前挂件的最大高度尺寸(使用dp为单位)。

【需要注意的是:此API是在16引出的(android4.1以上)。】

  •  onDeleted (Context context, int[] appWidgetIds)

此方法将在挂件被删除的时候调用

  • onEnabled (Context context)

在挂件对象实例第一次创建的时候调用。比如说:用户添加了两个你的实例,此方法也只被调用一次。有的事情只需要执行一次的:例如:创建数据库。可以再次进行。

  • onDisabled (Context context)

此方法将在你所有的挂件实例中的最后对象被删除后调用。用来为onEnable()做一些清除工作。

  • onReceive (Context context, Intent intent)

此方法是为broadcast所调用,并且优先于上面几个回调方法。一般你不需要实现此方法。因为APPWidgetProvider默认实现了这个方法。

最重要的APPWidgetprovider的回调方法是:onUpdate(),因为他在每次添加到宿主屏幕的时候都会调用(除非你使用了configuration Activity)。如果你的挂件需要接受一些用户的交互事件,那你需要在此会掉方法中注册事件的handler,如果你的挂件不用创建临时的文件或者数据库,或者执行其他的清理工作,那么onUpdate()方法也许就是你唯一需要实现的方法了。比如说:如果你想一个拥有button的挂件,当他被点击的时候你就启动一个Activity,那么你可以这么做。【当然,如果你不想启动Activity,你想出发某个时间,你可以使用PendingIntent来发送广播,广播接收器收到后,通过Intent的Action来执行不同的代码】

public class ExampleAppWidgetProvider extends AppWidgetProvider {public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {final int N = appWidgetIds.length;// Perform thisloop procedure for each App Widget that belongs to this providerfor (int i=0; i<N; i++) {int appWidgetId = appWidgetIds[i];// Create anIntent to launch ExampleActivityIntent intent = new Intent(context, ExampleActivity.class);PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);// Get the layoutfor the App Widget and attach an on-click listener// to the buttonRemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);views.setOnClickPendingIntent(R.id.button, pendingIntent);// Tell theAppWidgetManager to perform an update on the current app widgetappWidgetManager.updateAppWidget(appWidgetId, views);}}
}

上面的APPWidgetProvider就只是定义了onUpdate()方法,此方法用来定义一个启动ActivityPendingIntent,然后使用setOnclickPendingIntentintPendingIntent)绑定到挂件的Button上。

注意:上面的代码是在一个循环中,他们迭代 appWidgetIds数组的每个实例,每个实例都是一个挂件的ID,通过这种方式,如果用户创建了多个挂件实例(例如在主屏幕创建相同的两个挂件),他们全部都将更新,因为,他们使用的updatePeriodMillis使用的是同一个实例。比如说:如果刷新频率被设置为两个小时,同时,桌面上有两个相同的挂件,第二个挂件在第一个挂件出现的一个小时后添加的。他们最终的结果就是,一旦到了第一个挂件刷新时(也就是第一个挂件添加两个小时后),第二个挂件也一起刷新,但是到了第二个挂件刷新时,将不起任何作用(直接忽视了第二个挂件的周期,以第一个为准)

注意:

由于APPWidgetProvider是扩展自BroadcastReceiver,因此,不能保证在回调方法返回后你的进程将继续保持运行(BroadcastReceiver的生命周期),如果你需要做一些时间比较长的任务,你最好在onUpdate()中开启一个Service,然后在Service中更新界面。例子见右边--

接收Widget广播Intents

使用APPWidgetProvider仅仅是个便利的做法,如果你喜欢,你也可以将挂件直接继承BroadcastReceiver,或者复写APPWidgetProvideronRecerveContextIntent)方法。有几个Intent你需要注意的:

  • ACTION_APPWIDGET_UPDATE
  • ACTION_APPWIDGET_DELETED
  • ACTION_APPWIDGET_ENABLED
  • ACTION_APPWIDGET_DISABLED
  • ACTION_APPWIDGET_OPTIONS_CHANGED

创建挂件Configuration Activity

如果你想让用户在新添加你的挂件前先做一些设置。那么你可以创建一个Configuration Activity,此Activity将会在挂件添加前启动,以便于用户为挂件做设置,比如说:挂件颜色、挂件大小、挂件更新频率等。

Configuration Activity应该像一般的Activity一样在manifest中申明,只是需要加上

<actionandroid:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>以便接受Intent的过滤器而已。比如说:

<activity android:name=".ExampleAppWidgetConfigure">

    <intent-filter><action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/></intent-filter>
</activity>

同时,我们需要在APPWidgetProviderInfoXML文件中的android:configure中添加上此Activity。比如:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"...android:configure="com.example.android.ExampleAppWidgetConfigure"... >
</appwidget-provider>

记得 android:configure 的值需要类的全称!

上面的内容就是你启动ConfigurationActivity需要做的所有内容。剩下的需要你做的事情就在这个实际的Activity中,当你在实现Activity的时候,下面有两点重要的事情你需要记住:

  • 调用Configuration Activity后,Configuration Activity应该返回结果,结果中应该包含绑定的挂件的ID,将ID保存到Intent中的EXTRA_APPWIDGET_ID中。
  • 如果你创建了Configuration Activity,挂件的onUpdate()方法将不会被调用(当Configuration Activity被启动的时候,系统将不会发送ACTION_APPWIDGET_UPDATE广播),请求APPWidgetManager更新挂件就将是Configuration Activity的职责。当然,以后onUpdate()方法会被正常调用。

Configuration Activity更新挂件

当挂件使用了Configuration Activity,那么当Configuration Activity处理完毕后,第一次更新挂件将是Configuration Activity的职责。当然,你可以直接从APPWidgetManager请求更新。

下面有一些关于更新挂件和关闭ConfigurationActivity的程序的总结

1、首先通过Intent得到挂件的ID

Intent intent = getIntent();
Bundle extras =intent.getExtras();
if (extras !=null) {mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);
}

2、执行你的Configuration Activity交互(一般是用户设置等)

3、当设置完毕,通过getInstanceContext)获取到APPWidgetManager对象

AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

4、通过拥有布局的RemoteViews对象调用updateAPPWidgetintRemoteViews)来更新挂件。

RemoteViews views = newRemoteViews(context.getPackageName(),R.layout.example_appwidget);
appWidgetManager.updateAppWidget(mAppWidgetId, views);

5、最后返回Intent、关闭Activity

Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();

小知识:

当你的ConfigurationActivity被创建时,如果你在最后返回的值是:RESULT_CANCELED(或者用户并没有与你的Configuration Activity有太多交互,而是直接结束了此Activity,也会返回这样的值)。那么挂件会接受到这样的事件,最终,他将不会被添加到宿主中。

设置预览图

Android 3.0引进了previewImage字段。他可以定义一个挂件的预览图,方便用户直接筛选,如果此字段没定义,那么系统将使用挂件的图片作为预览。

具体定义如下:

<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"...android:previewImage="@drawable/preview">
</appwidget-provider>

【为了帮助你为你的挂件创建挂件预览图,安卓模拟器包含了一个应用,叫做“Widget Preview”,启动这个应用,选择你的挂件,设置好后,“Widget Preview”将会给你生成预览图,这时候你保存下来,放到你的工程中做为资源图就好了】

带集合物件的挂件

Android 3.0引入了带集合物件的挂件,这些种类的挂件使用RemoteViewsService来将后台远程的数据展示出来,比如:来自content Provider的数据。使用RemoteViewsService提供数据,使用下面的View控件展示内容,我们称这样的View为: “collection views

  • ListView

在一个垂直方向滚动显示条目的列表。

  • GridView

ListView相似,只是在水平方向展示的条目多于1条(ListView1条)。

  • StackView

类似于将很多卡牌堆叠在一起的View(有点像名片盒),

  • AdapterViewFlipper

一个由ViewAnimator做后台支撑的adapter,他可以在两个或多个View之间动画。但是同一时间只能有一个child可以被显示。

上面有说道:这些collectionView将显示来自其他地方的数据。这就意味着,他们要使用一个Adapter去绑定他们的数据到UI上,一个适配器绑定了一些条目,而这些条目是将一堆数据分别存放到分别的View对象中。

由于这些collectionview是基于adapter的,所有安卓框架必须要包含额外的建筑样式去支持这样的挂件。在挂件的上下文中,adapterRemoteViewsFactory所取代,它仅仅对Adapter做了很小的包装,当其中一个条目被请求,那么RemoteViewsFactory将会创建并返回此条目(作为一个RemoteViews对象返回),如果你要包含collection View到你的挂件中,你必须要实现两个类:android.widget.RemoteViewsService

android.widget.RemoteViewsService.RemoteViewsFactory

RemoteViewsService是一个允许适配器远程请求RemoteViews对象的服务

RemoteViewsFactory是一个在collection View和其对应的数据的一个接口,

public class StackWidgetService extends RemoteViewsService {@Overridepublic RemoteViewsFactory onGetViewFactory(Intent intent) {return new StackRemoteViewsFactory(this.getApplicationContext(), intent);}
}class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {//... include adapter-likemethods here. See the StackView Widget sample.}

二、示例应用

此例子由10堆叠起来的View组成,展示的名称由0!到9!,此例子有下面一些行为:

  • 可以像查看堆叠的相册一样,向下向上翻阅----使用的StackView
  • 如果用户没与之交互,他会自己滚动。因为在xml文件中使用了android:atutoAdvanceViewId = "@id/stack_view"
  • 如果用户点击了其中的一个View,那么他将会吐丝:你点击了 **”。其中**是根据你点击的索引不同而不同。
实现集合类型的小挂件
下面有几个相同的实现步骤,用于实现APP Widgets。下面的实例是针对集合类的挂件(ViewStacks,ListView,GridView这样的视图)
1、在Manifest中创建APP Widgets申明。
除了创建APP Widgets必须要在Manifest申明的内容之外(前文中有介绍),如果我们想实现集合类型的挂件,我们还需要绑定一个RemoteViewsService服务,所以,我们也必须在Manifest中将其Service申明出来,并且需要加上BIND_REMOTEVIEWS权限。申明权限主要是为了阻止其他应用程序访问你的数据。比如:我们可以申明这样一个Service(用于填充集合类挂件的数据的服务)。
<service android:name="MyWidgetService"
...
android:permission="android.permission.BIND_REMOTEVIEWS" />
android:name="MyWidgetService" 指向你创建的RemoteViewService的子类。
2、为集合类挂件创建一个Layout。
既然我们要使用集合类挂件,那么,我们的布局文件中就要使用一种集合类的View,可以使用的有:ListView、GridView、StackView或者AdapterViewFlipper。下面的widget_layout.xml使用StackView作为实例:
<?xml version="1.0" encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><StackView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/stack_view"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:loopViews="true" /><TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/empty_view"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:background="@drawable/widget_item_background"android:textColor="#ffffff"android:textStyle="bold"android:text="@string/empty_view_text"android:textSize="20sp" />
</FrameLayout>

【当我们使用集合类View时,我们最好在集合类View的同级申明一个当该View为空时显示的View。上面实例中的id为empty_view的TextView就是用于当StackView为空时显示的一个View】
值得注意的是:使用ListView等集合类View我们知道,除了创建ListView的布局文件之外,我们还要创建其集合View的itemView。本例子中,我们为StackView创建了两套Itemview,分别是:dark_widget_item.xml和light_widget_item.xml。

3、为集合类挂件创建APPWidgetProvider类。
正常情况下,我们写的一般的AppWidgetProvider子类大部分都会在onUpdate()方法中(主要复写对象)。开发集合类的挂件复写onUpdate()主要不同在于,当创建一个集合类挂件时,我们必须要调用用setRemoteAdapter(),用来告诉集合View去哪里取数据。RemoteViewsService可以返回一个RemoteViewsFactory的实现,这样Widgets就可以获取到数据了,当你调用setRemoteAdapter()方法时,你必须传递一个Intent(指向你实现的RemoteViewsService)、一个widgetID(指向需要更新的widget)。
例如:下面展示了我们的StackView Widget实现onUpdate()的一个例子。
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {// update each ofthe app widgets with the remote adapterfor (int i = 0; i < appWidgetIds.length; ++i) {//Set up the intent that starts the StackViewService, which will//provide the views for this collection.Intent intent = new Intent(context, StackWidgetService.class);//Add the app widget ID to the intent extras.intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));//Instantiate the RemoteViews object for the app widget layout.RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);//Set up the RemoteViews object to use a RemoteViews adapter.//This adapter connects//to a RemoteViewsService  through the specified intent.//This is how you populate the data.rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);//The empty view is displayed when the collection has no items.//It should be in the same layout used to instantiate the RemoteViews//object above.rv.setEmptyView(R.id.stack_view, R.id.empty_view);////Do additional processing specific to this app widget...//appWidgetManager.updateAppWidget(appWidgetIds[i], rv);  }super.onUpdate(context, appWidgetManager, appWidgetIds);
}

【RemoteViewsServiceclass类】
持久化数据
你不能把数据存储依赖于某个单独的Service实例,你应该将其持久化。因此,你不要将你的任何数据存储在RemoteViewsService中(静态的也不行)。如果你想将你的widget的数据持久化,最好的方式是使用ContentProvider,他可以不受进程的生命周期影响。
正如上面的描述,你的RemoteViewsService实现类提供RemoteViewsFactory来填充远程的集合view 。
特别地:你需要执行下面几个步骤:
1、创建RemoteViewsService的子类。通过RemoteViewsService,远程adapter可以请求RemoteView。
2、在你的RemoteViewsService中,包含一个RemoteViewsFactory接口的实现,RemoteViewsFactory是一个远程集合类View和其adapter之间的接口,并持有其集合类View的数据,这个接口主要用于通过给定的data生成RemoteView的每个Item。这个接口仅仅是对adapter进行了简单的包装。
RemoteViewService最重要的组成部分就是RemoteViewsFactory,下面我们将对其进行介绍。
【RemoteViewsFactory接口】
我们实现RemoteViewsFactory接口就是为了给集合类View的每个Item提供数据。为了实现这样的目的,我们通过他来讲每个item的XML布局文件和相应的数据源结合起来。具体数据源可以是来自数据库的任意的数据。在此例子中:StackView的数据源是一个WidgetItems的数组,RemoteViewsFactory扮演者一个adapter的角色,用于将数据和远程的集合类View绑定起来。
下面有两个非常重要的方法我们需要实现:
onCreate()和getViewAt()
系统在创建你Factory的时候将会调用其onCreate()方法。在这个方法中,你可以进行一些数据库连接或者cursor连接之类的操作。比如说:在我们的例子中StackView Widget使用onCreate()方法去初始化一个WidgetItem的集合。当你的widget处于活动状态是,系统将访问这个集合去根据item的不同position取不同的值来初始化item。
下面有个StackView的RemoteViewsFactory实例代码。
class StackRemoteViewsFactory implements
RemoteViewsService.RemoteViewsFactory {private staticfinal intmCount =10;private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();private Context mContext;private int mAppWidgetId;public StackRemoteViewsFactory(Context context, Intent intent) {mContext =context;mAppWidgetId =intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);}public void onCreate() {//In onCreate() you setup any connections / cursors to your data source. Heavylifting,//for example downloading or creating content etc, should be deferred toonDataSetChanged()//or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.for(int i = 0; i < mCount; i++) {mWidgetItems.add(new WidgetItem(i + "!"));}...}
...

RemoteViewsFactory的getViewAt()方法返回一个与data集合中相应position的数据相关的RemoteView对象。下面是StackView的一段摘录代码。
public RemoteViews  getViewAt(int position){// Construct aremote views item based on the app widget item XML file,// and set thetext based on the position.RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);...// Return theremote views object.return rv;
}

【为每个独立的item添加行为(点击效果)】
上面提到的内容告诉我们怎样创建一个集合类View。但是,我们如何为每个item添加不同的行为呢?下面继续介绍:
正如之前介绍的,一般情况下我们为在RemoteView中为一个View添加点击事件时,我们可以使用setOnClickPendingIntent()来为之设置点击事件。但是,如果我们要为集合类View的每个Item都设置点击事件时,这样的做法是不允许的。取而代之的是,先为集合类View使用setPendingIntentTemplate()创建一个PendingIntent模板,然后其每个不同item使用setOnClickFillInIntent()去填充出不同的Intent数据。
这小节将通过StackView为例子来介绍怎样为每个Item添加不同的行为。在这个例子中,如果用户点击了最顶上的view,appWidget将会显示一个Toast消息“touched view n,”其中n为view的position号。思路如下:
  • StackWidgetProvider(APPWidgetProvider的子类)创建一个PendingIntent,并拥有一个自定义action,叫做:TOAST_ACTION。
  • 当用户点击了一个view,我们将会广播这个Intent。
  • 这个广播将会传递给StackWidgetProvider的onReceive()方法,然后小挂件将会显示点击该View的Toast信息。每个item的数据是由RemoteViewsService的RemoteViewsFactory提供。
(提示:在这个例子中,StackView Widgets使用的是广播,但是在很多app中可能会启动相应的Activity。但是实现代码和思路都很类似)
【创建PendingIntent模板】
StackWidgetProvider(APPWidgetProvider的子类)设置PendingIntent,集合中每个独立的item不能设置他们自己的PendingIntent。正确的做法是:为整个集合类View创建一个PendingIntent模板,然后每个item去填充intent。当然,因为,我们的StackView的每个Item点击事件是发送广播,所以,为了方便,我们的StackWidgetsProvider也设置为可以接受这类广播消息(Action是TOAST_ACTION的广播消息),从而onReceive()会受到这样的消息。实例代码片段如下:
public class StackWidgetProvider extends AppWidgetProvider {public staticfinal String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";public staticfinal String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";...// Called whenthe BroadcastReceiver receives an Intent broadcast.// Checks to seewhether the intent's action is TOAST_ACTION. If it is, the app widget// displays aToast message for the current item.@Overridepublic void onReceive(Context context, Intent intent) {AppWidgetManager mgr = AppWidgetManager.getInstance(context);if(intent.getAction().equals(TOAST_ACTION)) {int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();}super.onReceive(context, intent);}@Overridepublic void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {//update each of the app widgets with the remote adapterfor(int i = 0; i < appWidgetIds.length; ++i) {// Sets up the intent that points to the StackViewServicethat will// provide the views for this collection.Intent intent = new Intent(context, StackWidgetService.class);intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);// When intents are compared, the extras are ignored, so weneed to embed the extras// into the data so that the extras will not be ignored.intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);// The empty view is displayed when the collection has noitems. It should be a sibling// of the collection view.rv.setEmptyView(R.id.stack_view, R.id.empty_view);// This section makes it possible for items to haveindividualized behavior.// It does this by setting up a pending intent template.Individuals items of a collection// cannot set up their own pending intents. Instead, thecollection as a whole sets// up a pending intent template, and the individual itemsset a fillInIntent// to create unique behavior on an item-by-item basis.Intent toastIntent = new Intent(context, StackWidgetProvider.class);// Set the action for the intent.// When the user touches a particular view, it will havethe effect of// broadcasting TOAST_ACTION.toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,PendingIntent.FLAG_UPDATE_CURRENT);rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);appWidgetManager.updateAppWidget(appWidgetIds[i], rv);}super.onUpdate(context, appWidgetManager, appWidgetIds);}
}
【创建填充模板PendingIntent的Intent实例】
你的RemoteViewsFactory必须为每个Item创建一个Intent去填充PendingIntent中的内容,以示区分item。下面是个实例代码片段:
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {private staticfinal intmCount =10;private List<WidgetItem> mWidgetItems = new ArrayList<WidgetItem>();private Context mContext;private int mAppWidgetId;public StackRemoteViewsFactory(Context context, Intent intent) {mContext =context;mAppWidgetId =intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,AppWidgetManager.INVALID_APPWIDGET_ID);}// Initialize thedata set.publicvoidonCreate(){// In onCreate() you set up any connections / cursors toyour data source. Heavy lifting,// for example downloading or creating content etc, shouldbe deferred to onDataSetChanged()// or getViewAt(). Taking more than 20 seconds in this callwill result in an ANR.for (int i = 0; i < mCount; i++) {mWidgetItems.add(new WidgetItem(i + "!"));}...}...//Given the position (index) of a WidgetItem in the array, use the item's textvalue in//combination with the app widget item XML file to construct a RemoteViewsobject.publicRemoteViews getViewAt(int position) {// position will always range from 0 to getCount() - 1.// Construct a RemoteViews item based on the app widgetitem XML file, and set the// text based on the position.RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);rv.setTextViewText(R.id.widget_item, mWidgetItems.get(position).text);// Next, set a fill-intent, which will be used to fill inthe pending intent template// that is set on the collection view inStackWidgetProvider.Bundle extras = new Bundle();extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);Intent fillInIntent = new Intent();fillInIntent.putExtras(extras);// Make it possible to distinguish the individual on-click// action of a given itemrv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);...// Return the RemoteViews object.return rv;}...}
【保持数据的更新】
下面的插图显示了集合类Widgets当有数据更新的情况。他展示了Widgets是如何和RemoteViewsFactory交互的,并且,你如何触发更新操作。

集合类挂件的其中一个特性就是可以为用户提供最新的内容展示。
比如说:如果android3.0有个Gmail的小挂件,他可以给用户提供收件箱的快速浏览。为了将此变为可能,你需要触发你的RemoteViewsFactory和集合类View去获取并展示最新的数据。你可以通过APPWidgetManager调用notifyAPPWidgetViewDataChanged()去实现。这个方法最终回去调用你的RemoteViewsFactory的onDataSetChanged()方法,从而给了你机会去获取新数据。
另外,你可以在onDataSetChanged()方法中执行大量的密集的同步操作(也就是耗时操作),最终也可以保证你的方法在MetaData和ViewData之前。不止如此,你也可以在getViewAt()方法中执行密集的耗时操作,如果用户滑动到此依旧为加载完毕时,该item会显示loadingView(由RemoteViewsFactory的getLoadingView指定),直到该getViewAt()正确的返回了View。

【android开发】桌面小挂件( APP Widgets )相关推荐

  1. android窗口小挂件,Android小挂件(APP Widgets)设计指导

    应用小挂件(也叫做窗口小挂件)在android1.5的时候被第一次引出,后来再android3.0和android3.1中得到了极大的发展,他们可以展示一些应用的常用信息或者一些相关的信息到桌面上,标 ...

  2. 【Android】开发桌面小插件(一)

    转载自:http://www.eoeandroid.com/thread-71208-1-1.html 应用程序窗口小部件App Widgets 应用程序窗口小部件(Widget)是微小的应用程序视图 ...

  3. Android开发日志打卡APP(二)

    Android开发日志打卡APP(二) 文章目录 Android开发日志打卡APP(二) 前言 开发过程 一.背景和标题 二.日志图标 三.日志弹框 前言 在之前的文章中,准备工作已经完成,现在我们将 ...

  4. Android开发详解之App升级程序一点通

    Android开发详解之App升级程序一点通 结束语 UpdateManager.java import java.io.File; import java.io.FileOutputStream; ...

  5. Android开发日志打卡APP(一)

    Android开发日志打卡APP(一) 文章目录 Android开发日志打卡APP(一) 简介 界面展示 内容总结 1.控件 2.布局 3.技术 开发过程 准备工作 启动页面 底部导航栏 简介 ​ 初 ...

  6. android开发 实现动态获得app的cpu占有率并导出文件的两种方法。

    android开发 实现动态获得app的cpu占有率并导出文件的两种方法. 最近在做学校实验室的项目的时候,师兄要求我对app的性能进行评估,主要是从电量.cpu占有率.python模型的响应时间三者 ...

  7. (转)解决android开发人员,手机app图标显示不正确问题

    (转)解决android开发人员,手机app图标显示不正确问题 参考文章: (1)(转)解决android开发人员,手机app图标显示不正确问题 (2)https://www.cnblogs.com/ ...

  8. Python Tkinter 实现桌面小挂件

    许多朋友喜欢使用桌面便签功能,实际上就是一个桌面小挂件,类似上图所示的软件(图片来自网络) 类似的悬浮小挂件效果,实际上在Python最简单的GUI库Tkinter中就能实现.然而在Tkinter的官 ...

  9. android AppWidgetProvider开发桌面小工具

    什么是桌面小工具  就是你写好代码后 双指缩放桌面屏幕(三星手机出现小组件,华为出现窗口小工具)点击之后会出现你继承AppWidgetProvider所编写的界面 效果: 代码: /** * Impl ...

最新文章

  1. Matlab与数据结构 -- 如何获取完整文件名
  2. Ubuntu共享WiFi(AP)给Android方法
  3. 计算机文件无法显示后缀,但一般情况下电脑默认是看不到文件格式显示(也就是文件扩展名)...
  4. OpenGL绘制二个不同颜色的三角形的实例
  5. ubuntu常见问题解决方法
  6. 每个施加在HTML元素上的Angular Directive,运行时都会生成一个新的实例
  7. ICCV 2019 | 基于全局类别表征的小样本学习
  8. 几种测量app启动时间的方式
  9. 自定义插入子串、删除子串、替换子串函数
  10. 「07」回归的诱惑:深入浅出逻辑回归
  11. scikit keras_使用Scikit-Learn,Scikit-Opt和Keras进行超参数优化
  12. 写出线程同步相关的方法,以银行账号存储款为例
  13. 自制Flash电子相册
  14. 如何分辨usb压枪芯片是无后座压枪还是键鼠模拟压枪
  15. 小马哥讲Spring核心编程思想 - 第二章 重新认识IoC
  16. PPT打印预览无背景
  17. matlab在编辑器中写完了怎么运行,在编辑器中运行测试
  18. 写给想做互联网产品经理的师弟师妹们一些话
  19. 计算机太极之光,且看今朝,刚柔并济,叱咤风云
  20. neighbors.kneighbors_graph的原理和应用

热门文章

  1. 【图论算法】深度优先搜索的应用
  2. 教你用一句代码简单实现雪景动画,不用任何元件
  3. 数据结构 - 线性表(顺序表)C语言代码实现-处理整型数据(附详细解释)。 _清风明月
  4. SAP FICO AFAB 科目要求一个成本会计分配(消息号 KI235)
  5. Excel(XLSX/XLS)文件内容导入WEB表单,纯前端无需服务器支持,开源Chrome扩展 v0.0.8,附下载,附github链接(2021/5/8更)
  6. iOS/swift之常用正则
  7. 101个python小代码 (6)设计一个重量转换器,输入以‘g’为单位的数字后返回换算成‘kg’的结果。
  8. 成都邮储信息技术岗实习面试准备
  9. css左侧投影_css3投影讲解、投影
  10. mysql导入数据load data infile用法