前言
在Android3.0中,增加了大量的APP Widgets功能,在本文中将详细介绍它们。
一,设置预览图片在Android 3.0版本中,增加了
previewImage
属性,它用于指明 App Widget的预览图片,它将在用户选中该App Widget的图标,打算添加该App Widget时,进行显示,以便用户了解该App Widget的界面。如果没提供预览图标的话,显示的将是你的App Widget的启动图标。该属性和AndroidManifest.xml中的<receiver>元素的android:previewImage的属性一致。你可以在XML定义该属性,如示例1
示例1:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ...
android:previewImage="@drawable/preview"> </appwidget-provider>
Android emulator提供了一个叫做"Widget Preview"的APK应用程序,以便创建你的App Widgets的previewImage。
为了创建你的App Widgets的previewImage, 首先启动"Widget Preview"的这个应用程序, 然后选择需要制作previewImage的app widget,接着再按照提示在你的App Widget的Configuration Activity对其App Widget进行配置, 最后保存得到
previewImage图片。
注意
:你可以在Android emulator的Application中,直接看到"Widget Preview"的这个
APK
应用程序,它的源码位于
android-sdk-windows\samples\android-12\WidgetPreview目录下
二,App Widgets中collection views简介
App Widget通过RemoteViewsService提供的RemoteViewsFactory来进行数据集的显示。用于数据集显示的View,我们将称其为“集合视图"或“collection views”,它可以为以下类型之一:
ListView
该类view将以垂直可滚动列表的形式进行数据集中所有数据项的显示。最具代表性的是Gmail app widget
GridView
该view将以水平和垂直都可以滚动的grid(网格状)形式进行数据集中所有数据项的显示。最具代表性的是Bookmarks app widget
StackView
A stacked card view (kind of like a rolodex), 你可以对最前面一张card进行上翻(flick)/下翻(flick)来看到前一张/后一张 card,。最具代表性的是 YouTube and Books app widgets.
AdapterViewFlipper
An adapter-backed simple ViewAnimator that animates between two or more views. Only one child is shown at a time.
如上所述,这些collection views将显示由远程数据所组成的数据集合.这就意味着我们需要一个Adapter来把数据绑定到视图。
这个Adapter要负责把数据集的单个数据项和单个View对象进行绑定.
因为collection views是由adapters在后台提供的, 所以Android framework必须使用另外一种框架来支持在app widgets中使用它们.
在app widget应用中, 我们使用RemoteViewsFactory来代替Adapter , RemoteViewsFactory提供了和Adapter类似的接口.当请求数据集的一个数据项的时候,RemoteViewsFactory将以一个RemoteViews的形式返回数据集的一个数据项.为了在app widget中能使用collection view, 你必须实现一个RemoteViewsService和RemoteViewsFactory
RemoteViewsService是一个service,远程的adapter可以通过它请求并获得RemoteViews对象.
RemoteViewsFactory
为collection view (such as
ListView
,
GridView
, and so on)和collection view的数据提供了适配接口。
这里是实现RemoteViewsService和其接口的一个代码样板:
示例2
public class StackWidgetService extends RemoteViewsService {
@Override public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } }
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { //... include adapter-like methods here. See the StackView Widget sample. }
例子程序
以下是StackView Widget示例程序的一个界面截图
This sample consists of a stack of 10 views, which display the values "0!" through "9!" The sample app widget has these primary behaviors:
首先、The user can vertically fling the top view in the app widget to display the next or previous view. This is a built-in StackView behavior.
其次
、
Without any user interaction, the app widget automatically advances through its views in sequence, like a slide show. This is due to the setting
android:autoAdvanceViewId="@id/stack_view"
in the
res/xml/stackwidgetinfo.xml
file. This setting applies to the view ID, which in this case is the view ID of the stack view.
最后、If the user touches the top view, the app widget displays the Toast message "Touched view n," where n is the index (position) of the touched view. For more discussion of how this is implemented, see Adding behavior to individual items。
关于StackView Widget示例程序的相信内容请阅读《
StackView Widget示例程序》
三、Implementing app widgets with collections
To implement an App Widget with collections, you follow the same basic steps you would use to implement any app widget. The following sections describe the additional steps you need to perform to implement an App Widget with collections.
3.1、Manifest for app widgets with collections
In addition to the requirements listed in Declaring an App Widget in the Manifest, to make it possible for App Widgets with collections to bind to your RemoteViewsService, you must declare the service in your manifest file with the permission BIND_REMOTEVIEWS. This prevents other applications from freely accessing your app widget's data. For example, when creating an App Widget that uses RemoteViewsService to populate a collection view, the manifest entry may look like this:
<service android:name="MyWidgetService" ...android:permission="android.permission.BIND_REMOTEVIEWS" />
这里的android:name="MyWidgetService" 是就你的RemoteViewsService.
3.2、Layout for app widgets with collections
你的app widget的布局文件应该包括下面之一的 collection views: ListView, GridView, StackView, or AdapterViewFlipper.
下面就是StackView Widget示例程序的布局文件widget_layout.xml
:
示例3
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns: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>
注意
:empty views是在collection view为空(数据集为空)的时候,显示的view.关于次的更多内容,你将在后文看到。
除了这个布局文件,你改要编写另外的布局文件,以用于数据项的View布局。比如,在
StackView Widget示例程序中它只有一个用于数据项显示的View布局的文件, widget_item.xml。而在WeatherList Widget示例程序中,它只有两个用于数据项显示的View布局的文件: dark_widget_item.xml和light_widget_item.xml
3.3、AppWidgetProvider class for app widgets with collections
app widgets with collections像一般的app widget一样,你主要代码应该在AppWidgetProvider的onUpdate()方法中。
主要区别在于,在 app widgets with collections中,你必须对RemoteViews调用setRemoteAdapter方法。这个函数能告诉collection view能在哪里取得它的数据.。RemoteViewsService必须能返回一个RemoteViewsFactory对象。
当你调用setRemoteAdapter()方法的时候, 你必须传递一个用于启动你的RemoteViewsService的intent和一个App Widget ID用于说明对哪个app widget进行更新.
For example, here's how the StackView Widget sample implements the onUpdate() callback method to set the RemoteViewsService as the remote adapter for the app widget collection:
示例4
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // update each of the app widgets with the remote adapter for (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); }
3.4、RemoteViewsService class
如上面描述那样, 你的RemoteViewsService应该编写一个RemoteViewsFactory,以提供数据项的remote collection view.
具体来说,您需要执行这些步骤:
第一、新建一个RemoteViewsService的子类. RemoteViewsService是个service,通过它remote adapter可以请求并获得RemoteViews.
第二,在你RemoteViewsService的子类中, 添加一个实现了RemoteViewsFactory接口的方法.
RemoteViewsFactory
为collection view (such as ListView, GridView, and so on)和collection view的数据提供了适配接口。
在RemoteViewsFactory中,你要负责为数据集的每个数据项提供一个RemoteViews对象. RemoteViewsFactory相等于Adapter的一个精简版本。
编写RemoteViewsService的主要工作集中在RemoteViewsFactory,关于次将在下文进行介绍
3.5、RemoteViewsFactory interface
你的RemoteViewsFactory需要为app widget提供数据集的每个数据项其对应的RemoteViews对象
为此, 你需要用app widget数据项的View布局文件和数据集的数据项来合成RemoteViews对象。数据源可以为任何形式,可以是一个简单的数组,也来自于数据库。在StackView Widget示例程序中, 数据源是个WidgetItems数组. RemoteViewsFactory的主要功能就是把数据变成 RemoteViews.
在RemoteViewsFactory中,你主要需要实现
onCreate()
和getViewAt()
这两个方法 。
当RemoteViewsFactory首次被创建时,onCreate()方法将被调用.
在该函数中,你应该做些初始工作,比如建立针对你的数据源的连接或光标。比如, 在StackView Widget示例程序中的onCreate()方法中,
就初始了一个WidgetItem对象数组. 当你app widget活动的时候, 系统可以通过数组索引号来访问这些WidgetItem对象。
这些WidgetItem对象中所包含的文本内容也就将被显示。
以下是StackView Widget示例程序的RemoteViewsFactory中关于onCreate()方法的部分代码:
示例5
class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int mCount = 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. Heavy lifting, // for example downloading or creating content etc, should be deferred to onDataSetChanged() // 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()方法将返回与数据集的指定位置的数据项相对应的RemoteViews对象.下面是
StackView Widget示例程序中,
关于
RemoteViewsFactory
的部分代码。
示例6
3.6、设置数据项View的行为
前文已经告诉你如何绑定您的数据到你的app widget collection。但是如何为数据项View添加动态的行为呢?本部分将讲述关于次的详细内容。
在普通的App Widgets中,我们可以通过
setOnClickPendingIntent()
来设置View控件Click时,发送的Intent.通过次方法,我们就可以实现当用户点击一个按钮时,就启动一个Activity.但是这种方法对
app widget collection的数据集的数据项View并不适用(
你可以像Gmail app widget中一样通过setOnClickPendingIntent()来设置global button的click行为来启动一个应用程序, 但是对数据集的数据项View不能使用这种方法
)。替代的是首先使用
setOnClickFillInIntent()方法为
数据集的数据项View统一设置其Pending的Intent模板,然后在
RemoteViewsFactory中为数据集的数据项View的Pending的Intent模板的设置填充Intent。这样当数据项View在Click时,发送的Intent将是其
Pending的Intent模板和其
Pending的Intent模板填充Intent合成的Intent。
在这里我们将使用
StackView Widget示例程序来说明如何为数据项View添加Click行为。在
StackView Widget示例程序
中,当你单击一个数据项View,它将显示一个
Toast消息
"Touched view n,"
n是它在touched view中的 index (position)。其主要执行流程如下:
第一步:首先,在StackWidgetProvider(an AppWidgetProvider subclass)中
使用
setOnClickFillInIntent()方法为
数据集的数据项View统一设置其Pending的Intent模板
,该intent有一个自定义的名叫
TOAST_ACTION
的action.
在RemoteViewsFactory中为数据集的数据项View的Pending的Intent模板的设置填充Intent,
填充Intent通过
Extras
把
数据项View的index (position
)携带在其中
。
第二步、用户点击数据项View,
其Pending的Intent模板和其Pending的Intent模板填充Intent被合成一个
Intent,该Intent被激活并被发送。
第三步
、数据项View发送的Intent广播被
StackWidgetProvider
接收,被传递到它的onReceive()中。
在
onReceive()
方法中,我们从Intent的Extra中提取其
数据项view的index (position)
,然后发送Toast消息。
注意:StackView Widget示例程序中使用的是把Intent发送到广播(
StackWidgetProvider
),但是一般的app widget只是简单的启动一
个Activity
3.6.1、设置数据项View的pending intent模板
在StackWidgetProvider (AppWidgetProvider subclass),数据项View的pending intent模板被统一进行设置.
不能对单个的数据项View的进行pending intent模板.你必须对数据集的数据项View的Pending的Intent模板进行统一设置,
对于数据集的单个数据项View只能进行填充Intent设置,以便让每个数据项View都有个性化的Click行为。
在StackWidgetProvider中,它接受了用户click数据项View所发送的Intent,并在
onReceive()
对其进行了处理。
如果发现intent的action是TOAST_ACTION(即为用户click数据项View所发送的Intent), app widget 将显示一个关于当前数据项View的Toast消息.
示例7
public class StackWidgetProvider extends AppWidgetProvider { public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION"; public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM"; ... // Called when the BroadcastReceiver receives an Intent broadcast. // Checks to see whether the intent's action is TOAST_ACTION. If it is, the app widget // displays a Toast message for the current item. @Override public 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); }
@Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // update each of the app widgets with the remote adapter for (int i = 0; i < appWidgetIds.length; ++i) {
// Sets up the intent that points to the StackViewService that 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 we need 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 no items. 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 have individualized behavior. // It does this by setting up a pending intent template. Individuals items of a collection // cannot set up their own pending intents. Instead, the collection as a whole sets // up a pending intent template, and the individual items set 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 have the 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); } }
3.6.2、设置数据项View的填充intent
你必须在RemoteViewsFactory中对数据集的每个数据项View设置填充intent,以用于区分是哪个数据项View.
当用户点击数据项View,
其Pending的Intent模板和其填充Intent被合成一个最终的
Intent,并进行广播发送。
示例8
public class StackWidgetService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new StackRemoteViewsFactory(this.getApplicationContext(), intent); } } class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory { private static final int mCount = 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 the data set. public void onCreate() { // In onCreate() you set up any connections / cursors to your data source. Heavy lifting, // for example downloading or creating content etc, should be deferred to onDataSetChanged() // 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 + "!")); } ... } ...
// Given the position (index) of a WidgetItem in the array, use the item's text value in // combination with the app widget item XML file to construct a RemoteViews object. public RemoteViews getViewAt(int position) { // position will always range from 0 to getCount() - 1.
// Construct a RemoteViews item based on the app widget item 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 in the pending intent template // that is set on the collection view in StackWidgetProvider. 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 item rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
...
// Return the RemoteViews object. return rv; } ... }
public RemoteViews getViewAt(int position) {
// Construct a remote views item based on the app widget item 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); ... // Return the remote views object. return rv; }
来源:oschina
链接:https://my.oschina.net/u/552347/blog/59674