了解AppWidget
学习资料:http://developer.android.com/guide/topics/appwidgets/index.html
基础
创建一个AppWidget,需要:
AppWidgetProviderInfo 对象 用于描述AppWidget的元数据,例如AppWidget的layout(布局),更新频率(update frequency),以及AppWidgetProvider类的,这些都应该定义在一个XML中.
AppWidgetProvider 类的实现 该类定义了AppWidget的一些基于广播事件的基本方法,用于提供AppWidget的编程接口,当App Widgets进行updated、enabled、disabled和deleted操作时可以通过这些基本方法来接收广播(Broadcasts)
View Layout 视图布局 视图布局用于定义AppWidget的初始布局,布局文件应建在工程文件夹的/res/layout目录下
此外,还可以定义一个App Widget configuration Activity,当用户创建Widget时可通过该Activity对App Widget 进行一些设置
在Android Manifest文件中声明一个App Widget
谷歌的开发文档中给的一个例子:
<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="ExampleAppWidgetProvider">:
<receiver>必须要声明android:name属性,该属性用来说明AppWidget使用的AppWidgetProvider.class
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>:
<intent-filter>元素必须包含一个带android:name属性的<action>,例子中的这个属性表明
AppWidgetProvider可以接受 ACTION_APPWIDGET_UPDATE 广播
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />:
<meat-data>用于描述AppWidgetProviderInfo的resource,该标签需要定义两个属性:
android:name="android.appwidget.provider"用于将该数据识别成对AppWidgetProviderInfo的描述
android:resource="@xml/example_appwidget_info" 用于声明AppWidgetProviderInfo的本地资源
</receiver>-->
添加AppWidgetProviderInfo的Metadata(元数据)
在res/xml目录下创建一个XML文件,该文件用于定义AppWidget的一些基本属性:
<!--<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp":最小宽度
android:minHeight="40dp":最小高度
android:updatePeriodMillis="86400000":更新频率
android:previewImage="@drawable/preview":定义AppWidget的预览图片
android:initialLayout="@layout/example_appwidget":定义AppWidget使用的初始布局
android:configure="com.example.android.ExampleAppWidgetConfigure" :定义AppWidget的设置文件
android:resizeMode="horizontal|vertical":设置可以让用户调整插件大小,horizontal|vertical表示宽和高都可以调整
android:widgetCategory="home_screen":设置AppWidget是否可以显示在home_screen上或者lock_screen上
>
</appwidget-provider>-->
创建AppWidget的布局
在res/layout目录下创建一个布局文件,用于设计AppWidget.由于AppWidget layout是基于RemoteViews类,只支持四个布局方式:
FrameLayout//框架布局
LinearLayout//线性布局
RelativeLayout//相对布局
GridLayout//网格布局
只支持以下的AppWidget classes:
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
AppWidget的箱模型:
给AppWidget添加Margins(边距)
1.设置app的targetSdkVersion版本,至少要14;可能是因为版本低的不支持.
2.创建一个引用了dimension resource来设置margins的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/widget_margin"
>
<ImageButton
android:id="@+id/ImageButtonClick"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" >
</ImageButton>
</LinearLayout>
引用dimension resource: android:padding=”@dimen/widget_margin”
3.在两个地方创建dimensions resources,一个在res/values
目录下,另一个在res/values-14
目录下
res/values/dimens.xml
用于自定义AppWidget的margins(边距):
<dimen name="widget_margin">8dp</dimen>
res/values-14/dimens.xml
用于设置值为0 的margins的AppWidget:
<dimen name="widget_margin">0dp</dimen>
使用AppWidgetProvider类
AppWidgetProvider
类继承自BroadcastReceiver
类,使用该类便于处理AppWidget broadcasts,AppWidgetProvider只接收和AppWidget有关的广播:updated,deleted,enabled,和disabled等.当这些广播事件发生时,AppWidgetProvider会触发相应的回调方法.
onUpdate()
onUpdate()
会根据设置的updatePeriodMillis
属性来间歇性更新AppWidget.当用户添加AppWidget时也会调用该方法,所以onUpdate()应该用来完成一些AppWidget的基本设置,例如定义Views的event handlers(事件处理器)、当需要时开启一个暂时性的服务.但是如果已经声明了一个configuration Activity,当用户添加AppWidget时候系统不会立即调用onUpdate(),而是通过调用创建的configuration Activity来执行第一次更新,而onUpdate()则在AppWidget的后续更新时才会被调用.
onAppWidgetOptionsChanged()
onAppWidgetOptionsChanged()
方法在两个情况下会被调用,一个是AppWidget第一次被添加的时候,另一个是当AppWidget大小被调整后.可以通过该方法显示或者隐藏AppWidget尺寸范围内的内容.可以调用getAppWidgetOptions()
方法来获取AppWidget的size rangs(大小范围),getAppWidgetOptions()
方法会返回一个Bundle(包),这个Bundle包含以下内容:
OPTION_APPWIDGET_MIN_WIDTH :包含AppWidget当前的最小宽度,单位为dp
OPTION_APPWIDGET_MIN_HEIGHT :包含AppWidget当前的最小高度,单位为dp
OPTION_APPWIDGET_MAX_WIDTH :包含AppWidget当前的最大宽度,单位为dp
OPTION_APPWIDGET_MAX_HEIGHT :包含AppWidget当前的最大高度,单位为dp
ps:这个方法在Android4.1版本以上才有
onDeleted(Context context, int[] appWidgetIds)
当AppWidget被删除时会被调用.
onEnabled(Context context)
用户在桌面添加第一个AppWidget调用该方法.第一个是指从无到有的过程,也就是说不管之前是否添加过,然后又删了.只要用户添加AppWidget的时候桌面上没有这个AppWidget,就会调用该方法.
onDisabled(Context context)
最后一个AppWidget被删除时调用该方法.可以重写这个方法以处理一些事情,比如清除使用痕迹,删除临时database.
onReceive(Context context, Intent intent)
上述几个方法调用之后都会调用这个方法,这个方法可以不用自己实现.AppWidgetProvider已经封装好.
之前写的一个例子:
package com.example.mylauncher;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.RemoteViews.RemoteView;
public class myAppWidgetProvider extends AppWidgetProvider
{
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions)
{
// TODO Auto-generated method stub
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
}
@Override
public void onRestored(Context context, int[] oldWidgetIds, int[] newWidgetIds)
{
// TODO Auto-generated method stub
super.onRestored(context, oldWidgetIds, newWidgetIds);
}
// receiver默认的消息接收重载
@Override
public void onReceive(Context context, Intent intent)
{
// TODO Auto-generated method stub
super.onReceive(context, intent);
Log.i("myLog", "onReceive");
}
// 到了时间间隔的时候接收到的消息,appwidgetservice发过来的消息
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
// TODO Auto-generated method stub
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.i("myLog", "onUpdate");
// 更新远程的界面(launcher里的我的"所有的"appwidget)
for (int i = 0; i < appWidgetIds.length; i++)
{
Log.i("myLog", i + "");// i+""这样子写会先把i转换成String然后再和后面的""拼接
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_mywidget);// 远程视图
// views.setTextViewText(R.id.ImageButtonClick,
// "myClick");//这行代码会造成widget不能正常更新而显示"Problem Loading Widget"
views.setImageViewResource(R.id.ImageButtonClick, R.drawable.preview);// 设置Widget中ImageButton的图片
Log.i("myLog", "更新了图片");
// 加入事件:当点击widget时产生的 事件,这里设置的是跳转到mainActivity
views.setOnClickPendingIntent(R.id.ImageButtonClick, PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0));
appWidgetManager.updateAppWidget(appWidgetIds[i], views);// 更新视图
}
}
// 删除一个appwidget得到消息
@Override
public void onDeleted(Context context, int[] appWidgetIds)
{
// TODO Auto-generated method stub
super.onDeleted(context, appWidgetIds);
Log.i("myLog", "onDeleted");
Log.i("myLog","context:"+ context.toString());
}
// 第一次add widget 的时候接收的消息
@Override
public void onEnabled(Context context)
{
// TODO Auto-generated method stub
super.onEnabled(context);
Log.i("myLog", "onEnabled");
}
// 最后一个app widget被删除的时候接收到的消息
@Override
public void onDisabled(Context context)
{
// TODO Auto-generated method stub
super.onDisabled(context);
Log.i("myLog", "onDisabled");
}
}
>ps:所有的AppWidget实例的更新频率只能由一个updatePeriodMills的控制,即被用户添加到桌面的AppWidget实例只能同时更新.原因是AppWidgetProvider继承自BroadcastReceiver,不能保证app的进程在上述的回调函数返回结果之后保持运行(可以看一下BroadcastReceiver的生命周期).解决此问题的途径是,考虑在onUpdate()中开启一个Service,在这个Service中实现AppWidget自己的update.而且不用担心AppWidgetProvider会因为ANR错误(Application Not Responding)而关闭.
在AppWidget中运行Service的例子:http://code.google.com/p/wiktionary-android/source/browse/trunk/Wiktionary/src/com/example/android/wiktionary/WordWidget.java
接收AppWidget广播的Intents
AppWidgetProvider
只是一个便利的类,如果想要直接接收AppWidget广播,可以自己写一个BroadcastReceiver
或者重写onReceive(Context,Intent)
方法.其中需要注意的Intent Action有:
ACTION_APPWIDGET_UPDATE
ACTION_APPWIDGET_DELETED
ACTION_APPWIDGET_ENABLED
ACTION_APPWIDGET_DISABLED
ACTION_APPWIDGET_OPTIONS_CHANGED
创建AppWidget Configuration Activity
在Android Manifest文件中声明
<!-- 注册一个AppWidgetConfiguration Activity -->
<activity android:name=".myAppWidgetConfig" >
<intent-filter >
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter>
</activity>
在AppWidgetProviderInfo xml文件中声明android:configure属性:
注意两点:
1.AppWidget host每次调用configuration Activity时,configuration Activity要通过Intent extras返回一个包含AppWidget ID(EXTRA_APPWIDGET_ID
)的结果;
2.AppWidget被创建的时候,如果configuration Activity打开了的话onUpdate()
是不会被调用的,AppWidget第一次的更新将由configuration Activity去完成,而第二次以后才会调用onUpdate()
来更新AppWidget.
从configuration Activity中更新AppWidget
直接从AppWidgetManager请求更新:
1.从发起configuration Activity的Intent获取AppWidget ID
public class myAppWidgetConfig extends Activity
{
private int mAppWidgetId=0;
......
@Override
protected void onCreate(Bundle savedInstanceState)
{
......
//从启动AppWidgetConfiguration的intent获取AppWidget ID
Intent intent=getIntent();
Bundle extras=intent.getExtras();
if (extras!=null)
{
mAppWidgetId=extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
Log.i("myLog", "获取到ID:"+mAppWidgetId);
}
......
}
}
2.执行 AppWidget configuration
3.执行完AppWidget configuration之后,调用getInstance(Context)
方法获取一个AppWidgetManager实例
AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(getBaseContext());//获取一个AppWidgetManager实例
4.调用updateAppWidget(int,Remoteviews)
用RemoteViews布局来更新AppWidget
RemoteViews views=new RemoteViews(getBaseContext().getPackageName(), R.layout.layout_mywidget);//通过RemoteViews视图更新AppWidget
views.setOnClickPendingIntent(R.id.btnDrumToTestActivity, PendingIntent.getActivity(getBaseContext(), 0, new Intent(getBaseContext(), Test2Activity.class), 0));
appWidgetManager.updateAppWidget(mAppWidgetId, views);
5.创建return Intent用于返回结果,关闭configuration Activity
Intent resultValue=new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
configuration Activity的一个例子:
http://wptrafficanalyzer.in/blog/android-app-widget-with-configuration-activity/
设置Preview Image
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
...
android:previewImage="@drawable/preview">
</appwidget-provider>
20150726 By Xiong