Android App Widget

原文
App Widget是一种可以被放在其他应用中(如Launcher)并接收周期性更新的应用视图。这些视图在UI上就表现为Widget,并且你可以同App Widget Provider一起发布。
对于能够包含其他App Widget的应用程序组件,称为App Widget Host。
基本信息

要创建一个App Widget,你需要完成以下步骤:
lAppWidgetProviderInfo对象:它描述了App Widget的基本元素,比如说布局、更新频率、AppWidgetProvider类等。这些都是在xml文件中定义的。
l AppWidgetProvider类的实现:它定义了一些基本的方法以支持通过广播事件与App Widget交互。通过它,当App Widget被更新、启用、禁用以及删除时,你将收到相应的广播信息。
l View Layout:通过xml文件定义App Widget的初始视图。
另外,你还可以实现一个App Widget的配置Activity。当然,这不是强制的。
在AndroidManifest中声明一个App Widget

首先,声明AppWidgetProvider。

Xml代码

<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>  

标签用于指明App Widget使用的AppWidgetProvider
标签必须包括一个含有android:name属性的标签。该属性用于指明AppWidgetProvider接收APPWIDGET_UPDATE广播。这是你唯一需要显示声明的广播。当有需要时,AppWidgetManager自动发送AppWidgetProder所需的各种广播。
标签标识了AppWidgetProviderInfo资源,它需要以下属性:
l android:name:使用android.appwidget.provider来标识AppWidgetProviderInfo。
l android:resource:标识AppWidgetProviderInfo的资源位置。
添加AppWidgetProviderInfo元数据

AppWidgetProviderInfo定义了一个App Widget的必要属性,例如最小布局范围、初始布局、更新频率、以及在创建时显示的配置Activity(可选)。
AppWidgetProviderInfo使用标签来定义,并保存在res/xml文件夹中。
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>

l minWidth与minHeight属性表示了App Widget所需的最小布局区域。
默认的主屏中,App Widget要想确认其位置,需要通过基于网格的具有固定宽度和高度的单元。如果App Widget的最小宽度和高度无法匹配给定的单元,它将会自动扩展到最接近的单元大小。
由于主屏的布局方向是可变的,你应该考虑最坏的情况(每单元的宽和高都是74dp)。然而,为了防止在扩展时产生整数计算错误,你还需要减去2。因此,你可以用以下公式来计算最小宽度和高度(单位dp):(单元数量×74)-2。
同时,为了保证你的App Widget能够在各种设备上正常使用,它们的宽度和高度必须不超过4×4个单元。
lupdatePeriodMillis属性定义了App Widget框架调用AppWidgetProvider的onUpdate方法的频率。对于实际的更新,我们不建议采用该值进行实时处理。最好是越不频繁越好——为了保证电量,一小时不超过一次为好。当然,你也可以允许用户对更新频率进行设置。
注意,如果更新触发时设备正处于休眠状态,设备将唤醒以执行该操作。如果你的更新频率不超过一小时一次,这不会对电池的寿命产生多大的影响。但如果你需要更频繁地更新却又不想要在设备休眠时执行,那你可以使用定时器来执行更新。要达到这种目的,可以在AlarmManager中设置一个AppWidgetProvider能接收的Intent。将类型设为ELAPSED_REALTIME或RTC。由于AlarmManager只有当设备处于唤醒状态时才会被调用,我们只要设updatePeriodMillis为0即可。
linitialLayout属性标识了初始布局文件。
lconfigure属性定义了当用户添加App Widget时调用的Activity。(这是可选的)
lpreviewImage定义了App Widget的缩略图,当用户从widget列表中选择时,显示的就是这张图。如果没设置,用户将看见的是你的应用的默认图标。
lautoAdvanceViewId属性是在Android3.0引入的,用于标识需要被host(launcher)自动更新的widget的子视图。
l resizeMode属性标识了widget重新布局的规则。你可以使用该属性来让widget能够在水平、竖直、或两个方向上均可变化。可用的值包括horizontal、vertical、none。如果是想在两个方向上均能拉伸,可设置为horizontal|vertical,当然,需要Android3.1以上版本。
创建App Widget的布局

要创建你的App Widget的初始布局,你可以使用以下View对象。
创建布局不是很麻烦,重点是,你必须记住,这个布局是基于RemoteViews的,不是所有的布局类型与View都支持。
一个RemoteViews对象可以支持以下布局类:
FrameLayout
LinearLayout
RelativeLayout
以及一下widget类
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
注:这些类的子类是不支持的。
为App Widget添加边距
作为一个widget,它不应该扩展到屏幕的边缘,同时在视觉上,不应该与其它widget相混淆。因此,你需要为你的widget在四个方向上添加边距。
在Android4.0中,所有的widget都自动添加了内边距,并且提供了更好的对齐方案。要利用这种优势,建议设置应用的targetSdkVersion到14或更高。
为了支持以前的设备,你也可以为早些的平台写一个包含边距的布局,而对于Android4.0以上的,则不设置:
1. 设置targetSdkVersion到14或更高
2. 创建一个布局:
Xml代码

<service android:name="MyWidgetService"  
...  
android:permission="android.permission.BIND_REMOTEVIEWS" /> 

Collection的布局
布局中最主要的部分就是collection视图:ListView,GridView,StackView或AdapterViewFlipper。以下是一个例子:
Xml代码

<?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 view是当没数据时显示的视图。
除了App Widget的整体视图,你还需要为collection中的每一项定义布局。比如说,StackView Widget的例子中,只有一种。而WeatherListWidget的例子却有两种布局文件。
带有collection的AppWidgetProvider
与普通App Widget的唯一区别就是,在带有collection的AppWidgetProvider.onUpdate中,你需要调用setRemoteAdapter方法。通过调用这个方法,collection就知道如何获取它的数据。然后你在RemoteViewsService中返回一个RemoteViewsFactory,这样widget就能获取到相应的数据了。另外,当你调用setRemoteAdapter时,你需要传入一个Intent。这个Intent指向了RemoteViewsService的实现,并指明了App Widget的ID。
Java代码

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类

就像上面所说的,你的RemoteViewsService指向了用于创建远程collection视图的RemoteViewsFactory。
你需要执行以下操作:
1、 继承RemoteViewsService
2、 在你的RemoteViewsService子类里,实现一个RemoteViewsFactory的内部类。
注意:你不能依赖一个Service的实力来保存数据。除非是静态数据,否则任何内容都不应该保存在此处。如果你要保存你的App Widget的数据,你可以使用ContentProvider。
RemoteViewsFactory接口
你的实现了RemoteViewsFactory的类为App Widget提供了collection中的各项所需的数据。
其中,最重要的两个需要实现的方法是onCreate和getViewAt。
当系统第一次创建factory对象时,会调用onCreate。在这里面,你可以配置数据源。通过创建内容集合或Cursor等等。例如,在StackView Widget中,onCreate里创建了一个WidgetItem的数组。当你的App Widget处于激活状态时,系统会使用它们的index来获取数据并显示。
Java代码

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 + "!"));  
        }  
        ...  
    }  
...  

getViewAt用于返回特定位置上的RemoteViews对象。
Java代码

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

为单独的item设置相应的行为

前文说过,通常来说,你是使用setOnClickPendingIntent方法来设置一个控件的点击事件。但对于collection中的子项,该方法是无效的。你可以先用setPendingIntentTemplate方法为collection整体的点击设置一个处理的PendingIntent,然后通过RemoteViewsFactory使用setOnClickFillInIntent为collection视图中的每一项传入一个与该项相关的Intent。该Intent会被合入处理时接收到Intent中。
在onUpdate中设置pending intent template

Java代码

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

在RemoteViewsFactory中设置fill-in intent
Java代码

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

保证Collection的数据是最新的

下图给出了当更新发生时,一个带有collection视图的App Widget的工作流。
这里写图片描述

带有collection视图的App Widget的一个作用就是为用户提供实时的数据。例如,Android 3.0上的Gmail app widget,它为洪湖提供了他们的收件箱的快照。要实现这个功能,你需要能够触发你的RemoteViewsFactory及collection视图去获取以及显示新的数据。你可以通过调用AppWidgetManager的notifyAppWidgetViewDataChanged方法来达到这个目的。
通过调用这个方法,RemoteViewsFactory的onDataSetChanged方法将被调用,你可以在其中进行数据的获取。
onDataSetChanged以及后续的getViewAt方法中,你都可以进行一些处理密集型的操作。也就是说,你不用怕这个操作会占用太长的时间,从而导致UI线程无响应。
如果getViewAt方法耗时太长,加载视图(可由RemoteViewsFactory的getLoadingView获取)将显示在collection视图所在的区域。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android AppWidget 可以通过 AlarmManager 进行实时更新。AlarmManager 是一个系统级别的服务,用于在指定时间触发指定的操作,包括启动服务、广播等。 以下是使用 AlarmManager 进行 AppWidget 实时更新的步骤: 1. 在 AppWidgetProvider 类中创建一个 PendingIntent 对象: ```java Intent intent = new Intent(context, AppWidgetProvider.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); ``` 2. 使用 AlarmManager 设置定时任务: ```java AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), intervalMillis, pendingIntent); ``` 其中,`intervalMillis` 是更新间隔时间,以毫秒为单位。 3. 在 AppWidgetProvider 类中重写 onUpdate() 方法,并在其中处理更新操作。注意,需要在 onEnabled() 方法中注册 AlarmManager,同时在 onDisabled() 方法中取消注册。 ```java @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 处理更新操作 } @Override public void onEnabled(Context context) { super.onEnabled(context); // 注册 AlarmManager } @Override public void onDisabled(Context context) { super.onDisabled(context); // 取消注册 AlarmManager } ``` 这样就可以通过 AlarmManager 实现 AppWidget 的实时更新了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值