今天要做一个App Widget,在网上搜了一下,发现一篇翻译的DevGuide,但是没有翻译完整,所以自己来翻译一下,顺便也加深理解,水平有限翻译得可能不好望见谅。以后有时间会陆续翻译其他内容,不过还是建议大家尽量读英文文档。
App Widget
基础知识
1.AppWidgetProviderInfo对象,描述了AppWidget的元数据,比如它的Layout,更新频率和AppWidgetProvider类,这些需要定义在XML文件中定义。
2.AppWidgetProvider类的实现,定义了基于广播事件的基本方法,允许你通过编程与AppWidget交互,当此App Widget被更新,启用,禁用或删除时你将会收到广播。
3.View Layout,用XML定义了App Widget的初始化Layout。
4.另外,你可以实现一个可选的AppWidgetConfigurationAc
/* 接下来的内容是创建一个简单的App Widget的步骤/
在Manifest中声明一个App 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>
<receiver>元素的android:name是必须的,指定了AppWidget所用的AppWidgetProvider。
<intent-filter>必须包含一个拥有android:name属性的<action>元素,它指定了这个AppWidgetProvider接收ACTION_APPWIDGET_UPDATE广播。这是你唯一必须显示声明的广播,其余AppWidget广播将由AppWidgetManager在必要时自动向AppWidgetProvider发送。
<meta-data>元素指定了AppWidgetProviderInfo资源,需要以下属性:
添加AppWidgetProviderInfo元数据
AppWidgetProviderInfo定义了AppWidget的基本特性,比如最小的布局尺寸,初始化布局资源,更新频率和在创建的时候启动的一个configurationActivity(可选)。在XML文件中用一个单独的<appwidget-provider>元素定义AppWidgetProviderInfo对象,并保存到项目的res/xml/目录。比如:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="294dp"
android:minHeight="72dp"
android:updatePeriodMillis="86400000"
android:previewImage="@drawable/preview"
android:initialLayout="@layout/example_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigure"
android:resizeMode="horizontal|vertical">
</appwidget-provider>
minWidth和minHeight指定了AppWidget的layout要求的最小空间。缺省的AppWidgets所在窗口的桌面位置基于有确切高度和宽度的单元网格。如果AppWidget的最小长宽和这些网格单元的尺寸不匹配,那么这个App Widget将收缩到最接近的单元尺寸。
因为桌面布局方向(由此,单元的尺寸)可以变化,按照拇指规则,你应该假设最坏情况单元尺寸是74像素高和宽。不过,你必须从最后的尺寸中减去2以把像素计算过程中产生的任何的整数舍入误差考虑在内。要找到像素密度无关的最小宽度和高度,使用这个公式:
(number of cells * 74) - 2
遵循这个公式,你应该使用72dp为每一个单元高度,294dp为四个单元宽度。
注意,为了使你的app能跨设备,你的App Widget最小尺寸应该永远不要大于4 x 4单元。
updatePerdiodMillis 属性定义了AppWidget框架调用onUpdate()方法来从AppWidgetProvider请求一次更新的频度。实际更新时间并不那么精确,而且我们建议更新频率越低越好-也许每小时不超过一次以节省电源。你也许还会允许用户在配置中调整这个频率-一些人可能想每15分钟一次股票报价,或者一天只要四次。
注意,如果更新的时候机器正好在休眠,那么他会被唤醒去更新。如果你更新频率不高于每小时一次,那对电池寿命可能不会造成严重问题。但是如果你需要更频繁的更新或者/并且不需要在休眠时进行更新,你可以用不会唤醒机器的闹钟alarm来代替。为此,用AlarmManager,通过一个你的AppWidgetProvider能接收的Intent设置一个alarm,把闹钟类型设为ELAPSED_REALTIME或者RTC,这样就只会在机器醒着时提供alram,然后把updatePeriodMillis设为0.
initialLayout属性指向定义App Widget布局的资源。
configure参数定义了添加App Widget时启动的Activity,以便用户设置AppWidget的属性,这个参数是可选的。
previewImage指定了AppWidget配置完后的长相的预览,用户选择它后就可看到。如果不支持,用户将会看到你的应用程序的登录图标。这个域对应manifest中<receiver>的android:previewImage参数。
resizeMode指定了widget调整大小时依据的规则。你可以用它让homescreen的wiget可以从水平,垂直或者两个轴来调整大小。(Android3.1)
创建App Widget Layout
你必须在XML中为你的AppWidget定义初始化Layout并保存在/res/layout/目录。你可以使用下面列出的View objects来设计你的AppWidget,但之前请阅读AppWidget Design Guidelines。
App Widget Layout基于RemoteViews,后者并不支持每种layout或者view widget。
一个RemoteViews对象(由此,一个App Widget)支持下列layout:
FrameLayout
LinearLayout
RelativeLayout
和以下widget:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
不支持它们的子类。
使用AppWidgetProvider类
你必须通过在清单文件中使用<receiver>元素,来声明你的AppWidgetProvider类实现为一个广播接收器。
AppWidgetProvider 类扩展BroadcastReceiver 为一个简便类来处理AppWidget广播。AppWidgetProvider只接收和这个App Widget相关的事件广播,比如这个AppWidget被更新,删除,启用,以及禁用。当这些广播事件发生时,AppWidgetProvider将接收到下面的方法调用:
onUpdate(),以在AppWidgetProviderInfo中updatePeriodMillis为间隔被调用,当用户添加时也会被调用,因此它应该执行基础的设置,比如为视图定义事件处理器并启动一个临时的服务Service,如果需要的话。但是,如果你已经声明了一个ConfigurationActivity,这个方法 在用户添加App Widget时将不会被调用,而只在后续更新时被调用。ConfigurationActivity应该在配置完成时负责执行第一次更新。
onDeleted(Context, int[]),当从App Widget宿主删除App Widget时被调用。
onEnabled(Context),当App Widget实例第一次被创建时被调用。比如,如果用户添加了你的AppWidget的两个实例,它只会在第一个创建时被调用。如果你需要打开一个新的数据库或者执行其他的对于所有AppWidget实例来说只需要发生一次的操作,这里是一个实现的好地方。
onDisabled(Context),当你的App Widget的最后一个实例被从AppWidget宿主删除时它将被调用。在这里你应该把任何在onEnabled()中进行的工作清理干净,比如删除一个临时数据库。
onReceive(Context,Intent),接收到每个广播都会调用此函数,并且在调用以上回调函数之前。你通常不需要实现这个方法,因为默认的AppWidgetProvider实现会恰当地过滤所有AppWidget广播并调用回调函数。
注意: 在Android 1.5中,有一个已知问题,onDeleted()方法在该调用时不被调用。为了规避这个问题,你可以像Grouppost中描述的那样实现onReceive() 来接收这个onDeleted()回调。
最重要的AppWidgetProvider回调是onUpdate(),因为在任意一个AppWidget被添加到宿主时它会被调用(除非你使用了ConfigurationActivity)。如果你的App Widget接受任何用户交互事件,那么你需要在这个回调中注册相应的事件处理器。如果你的AppWidget不会创建临时文件或者数据库,或者执行其他需要清理的工作,那么onUpdate()也许是你唯一需要定义的回调函数。比如,如果你想要一个包含一个Button的AppWidget,点击之后将登录一个Activity,你可以使用下面的AppWidgetProvider实现:
public class ExampleAppWidgetProvider extends AppWidgetProvider{
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
final int N = appWidgetIds.length();
//为每个属于此provider的App Widget执行此循环程序
for(int i=0; i<N; i++){
int appWidgetId = appWidgetIds[i];
//创建一个登录ExampleActivity的Intent
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context,0,intent,0);
//取得App Widget的layout并为Button绑定一个点击监听器
RemoteViews views = new RemoteViews(context.getPackageName(),R.layout.appwidget_provider_layout);
views.setOnClickPendingEvent(R.id.button, pendingIntent);
//通知AppProviderManager在现在的appwidget上执行一次更新
appWidgetManager.updateAppWidget(appWidgetId,views);
}
}
}
这个AppWidgetProvider仅定义了onUpdate()方法,其目的是为定义一个用于登录一个Activity的PendingIntent,并且用setOnClickPendingEvent(int,PendingIntent)将其绑定到此AppWidget的Button。注意它包含了一个遍历appWidgetIds所有项的循环,appWidgetIds是一个定义了所有被这个provider创建的AppWidget的ID的数组。这样,如果用户创建了这个App Widget的不止一个的实例,那么他们会同时被更新。不过,对于所有的AppWidget实例,只有一个updatePeriodMillis 时间表被管理。比如,如果这个更新时间表被定义为每隔两个小时,而且AppWidget的第二个实例是在第一个后面一小时添加的,那么它们将按照第一个所定义的周期来更新而第二个被忽略(它们将都是每2个小时进行更新,而不是每小时)。
注意,因为AppWidgetProvider是BroadcastReceiver的扩展,不能保证你的进程在回调函数返回后仍然继续运行。如果你的AppWidget的设置过程会持续几秒钟(也许在执行web请求),并且你要求你的进程继续,那么可以考虑在onUpdate()方法里start一个Service,在里面你可以对AppWidget进行你自己的更新而不必担心由应用程序无响应错误导致AppWidgetProvider关闭。
接收App Widget广播Intent
AppWidgetProvider 只是一个简便类。如果你想直接接收App Widget广播,你可以实现自己的BroadcastReceiver 或者重写 onReceive(Context, Intent)回调函数。你需要注意的4个Intent如下:
创建一个App Widget ConfigurationActivity
如果你希望用户在添加一个新的AppWidget时调整设置,你可以创建一个AppWidgetConfigurationAc
Configuration Activity应该和普通Activity一样在Manifest中声明。但是,它会被AppWidget宿主用ACTION_APPWIDGET_CONFIGURE启动,所以这个Activity需要接收这个Intent。比如:
<activity android:name=".ExampleAppWidgetConfigure">
<intent-filter >
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
同时,这个Activity必须以android:configure参数在AppWidgetProviderInfoXML文件中声明,比如:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
...
android:configure="com.example.android.ExampleAppWidgetConfigure"
...>
</appwidget-provider>
注意这个活动是用全名声明的,因为它将从你的程序包外被引用。
这就是你需要为开始ConfigurationActivity做的所有准备。现在你需要一个真实的Acativity。但是,当你实现这个Activity的时候还有两个重要的事情需要记住;
从Configuration Activity更新App Widget
在App Widget使用Configuration Activity的情况下,当配置完成后由ConfigurationActivity负责更新App Widget。你可以通过直接从AppWidgetManager请求一次更新来实现。
下面是恰当地更新App Widget和关闭Configuration Activity的程序的总结:
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if(extras != null){
mAppWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
2.执行你的AppWidget配置
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
RemoteViews views = new RemoteViews(context.getPackageName(), mAppWidgetId);
appWidgetManager.updateAppWidget(mAppWidgetId, views);
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
提示:第一次打开你的ConfigurationActivity时,把返回值设为RESULT_CANCELED。这样,如果用户在Activity运行完前退出了,AppWidget宿主会被告知配置被中断,从而不会加入App Widget。
设置预览图片
Android3.0引入了previewImage域,指定了App Widget长相的预览。预览将会从widgetpicker展示给用户,如果不支持,则会使用App Widget的图标。
下面是你如何在XML中进行设置:
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
...
android:previewImage="@drawable/preview"
</appwidget-provider>
为了帮助替你的AppWidget创建预览图片(指定previewImage域),android模拟器包含了一个叫“WidgetPreview”的应用程序。启动这个应用程序以创建一个预览图片,选择你的应用程序的AppWidget,设置你希望你的预览图片出现的方式,然后保存并放到你的应用程序的drawable资源里。
Using App Widgets withCollections暂不涉及