应用Widget是可以在其他应用程序(如主屏幕)嵌入并接受定期更新的微型应用程序的意见。这些观点在用户界面被称为小工具,你可以发布一个与应用的Widget提供商。即能持有其他应用程序的窗口小部件的应用程序组件被称为应用程序的Widget主机。下面的截图显示音乐应用的Widget。
本文介绍了如何使用应用程序的Widget提供者发布应用的Widget。为了创建自己的AppWidgetHost托管应用小部件的讨论,请参阅应用的Widget主机。
设计的Widget
有关如何设计你的应用程序窗口小部件的信息,请阅读窗口小部件的设计指南。
基础
要创建一个应用程序窗口小部件,您需要满足以下条件:
AppWidgetProviderInfo对象
描述元数据为应用的Widget,如在App Widget的布局,更新频率,以及AppWidgetProvider类。这应当以XML来定义。
AppWidgetProvider类的实现
定义的基本方法,让您以编程与应用程序widget界面,基于广播的事件。通过它,当应用程序的Widget被更新,启用,禁用和删除您将收到广播。
视图布局
定义了应用程序插件的初始布局,在XML中定义。
此外,还可以实现一个应用的Widget配置活动。这是一个可选的活动,当用户将您的Widget应用程序,并允许他或她在修改创作时应用小工具设置,启动。
以下各节描述了如何设置这些组件。
在清单中声明一个应用程序窗口小部件
首先,声明在应用程序的AndroidManifest.xml文件中的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>在<接收器>元素需要在android:name属性,它指定了应用程序的Widget使用的AppWidgetProvider。
<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提供商>的摘要属性:
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="@dimen/widget_margin"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:background="@drawable/my_widget_background"> … </LinearLayout> </FrameLayout>创建两个维度的资源,一个在RES/价值/提供预Android 4.0的定制空间,和一个在res/值-V14/提供Android 4.0的小部件没有微胖:
<dimen name="widget_margin">8dp</dimen>res/values-v14/dimens.xml :
<dimen name="widget_margin">0dp</dimen>另一种选择是简单地在默认情况下建造额外的利润到您的九宫背景的资产,并提供不同的九补丁没有利润率API级别14或更高版本。
public class ExampleAppWidgetProvider extends AppWidgetProvider { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { final int N = appWidgetIds.length; // Perform this loop procedure for each App Widget that belongs to this provider for (int i=0; i<N; i++) { int appWidgetId = appWidgetIds[i]; // Create an Intent to launch ExampleActivity Intent intent = new Intent(context, ExampleActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); // Get the layout for the App Widget and attach an on-click listener // to the button RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout); views.setOnClickPendingIntent(R.id.button, pendingIntent); // Tell the AppWidgetManager to perform an update on the current app widget appWidgetManager.updateAppWidget(appWidgetId, views); } } }这AppWidgetProvider定义了用于定义启动一个活动的PendingIntent,并与setOnClickPendingIntent(INT,的PendingIntent)连接到App Widget的按钮的目的只有的onUpdate()方法。请注意,它包含一个循环,通过appWidgetIds每个条目,这是确定此提供程序创建的每个应用程序的Widget ID数组进行迭代。以这种方式,如果用户创建的应用程序的widget的多个实例,则它们都同时更新。但是,只有一个updatePeriodMillis时间表将用于应用程序的widget的所有实例进行管理。例如,如果更新时间表被定义为每两个小时,和App小工具的第二个实例是第一个之后添加一小时后,他们都将在由第一和第二更新所定义的周期来更新期间将被忽略(他们都会进行每两小时不每小时更新一次)。
<activity android:name=".ExampleAppWidgetConfigure"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> </intent-filter> </activity>(见添加AppWidgetProviderInfo元以上)配置属性:此外,活动必须在AppWidgetProviderInfo XML文件中声明,与Android。例如,配置活动可以声明如下:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:configure="com.example.android.ExampleAppWidgetConfigure" ... > </appwidget-provider>请注意,该活动是声明的完全限定的命名空间,因为它会从你的包范围内引用。
Intent intent = getIntent(); Bundle extras = intent.getExtras(); if (extras != null) { mAppWidgetId = extras.getInt( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); }执行你的App小部件配置。
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);Update the App Widget with a
RemoteViews
layout by calling
updateAppWidget(int, RemoteViews)
:
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.example_appwidget); appWidgetManager.updateAppWidget(mAppWidgetId, views);
Intent resultValue = new Intent(); resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId); setResult(RESULT_OK, resultValue); finish();提示:当您的配置活动第一次打开,设置活动结果RESULT_CANCELED。这样一来,如果用户备份出活动的到达结束之前,应用的Widget通知主机的配置被取消和App的Widget将不能添加。
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" ... android:previewImage="@drawable/preview"> </appwidget-provider>为了帮助您的应用程序窗口小部件(指定预览图像场)创建一个预览图像,Android模拟器包括称为应用程序“窗口小部件预览”。要创建一个预览图像,启动这个应用程序,选择您的应用程序应用程序窗口小部件,并将它设置你喜欢你们的预览图像出现,然后将其保存,并将其放置在应用程序的绘图资源。
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. }示例应用程序
该样本包括10次,其中显示值栈的“0”!到“9”!示例应用程序部件有这些主要行为:
用户可以垂直甩在应用程序插件的顶视图来显示一个或下一个视图。这是一个内置StackView行为。
无需任何用户交互,应用小工具会自动通过其在序列意见的进步,像幻灯片。这是由于设置的android:在res / XML / stackwidgetinfo.xml文件autoAdvanceViewId =“@ ID / stack_view”。此设置适用于该视图的ID,在这种情况下是堆栈视图的视图ID。
如果用户触摸的顶视图,该应用插件播放吐司消息“已触摸视图N,”,其中n是被触摸的视图的索引(位置)。对于这是如何实现的更多讨论,请参见添加行为的个别项目。
实施与应用的集合小工具
为了实现与集合的应用程序插件,你按照你会用它来实现任何应用程序插件的相同的基本步骤。以下各节介绍您需要执行来实现与集合的应用程序插件的额外步骤。
清单与集合应用小工具
除了声明中的清单应用程序窗口小部件列出的要求,使其能够用于收藏的应用程序部件绑定到你的RemoteViewsService,您必须在使用许可BIND_REMOTEVIEWS您的清单文件中声明该服务。这可以防止其他应用程序访问任意你的应用程序widget的数据。例如,创建一个使用RemoteViewsService来填充集合视图一个App窗口小部件时,清单条目可能是这样的:
<service android:name="MyWidgetService" ... android:permission="android.permission.BIND_REMOTEVIEWS" />该生产线机器人:名字=“MyWidgetService”指的是你RemoteViewsService的子类。
<?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>需要注意的是空的意见必须是其空视图表示空状态集合视图的兄弟姐妹。
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); }RemoteViewsService类
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 + "!")); } ... } ...所述RemoteViews工厂方法getView()返回在数据集中的指定位置相对应的数据的RemoteViews对象。下面是从StackView的Widget样品的RemoteViewsFactory实现的摘录:
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; }添加行为,个别项目
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); } }设置填充式意图
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; } ... }保持收集数据新鲜
使用集合应用小部件的一个特点是,为用户提供了最新的内容。例如,考虑到Android 3.0的Gmail应用小工具,它为用户提供他们的收件箱的快照。要做到这一点,你需要能够触发RemoteViewsFactory和收集,以获取并显示新的数据。您的通话AppWidgetManager实现这一notifyAppWidgetViewDataChanged()。此调用生成一个回调到您RemoteViewsFactory的onDataSetChanged()方法,它让你有机会获取任何新数据。请注意,您可以内onDataSetChanged()回调同步执行处理密集型操作。你保证,之前的元数据或显示数据是从RemoteViewsFactory获取此调用将完成。此外,可以将getViewAt()方法中进行处理密集型操作。如果该调用需要很长的时间,加载视图(由RemoteViewsFactory的getLoadingView()方法指定),将显示在集合视图,直到它返回的相应位置。