AppWidget应用小部件详解(二)
在上一篇的 AppWidget应用小部件详解(一)中,介绍了如何自定义实现一个简单的App Widget应用小部件的步骤,而在本篇中将继续介绍自定义App Widget的创建,这次将介绍在应用小部件中显示一个列表容器视图,比如ListView、GridView、StackView、AdapterViewFlipper。接下来就实现一个基于该模式的 图片浏览的App Widget 。 其实创建应用小部件的步骤都一样,只是在呈现小部件内容的处理方式上面不同罢了,现在就开始实现该图片浏览的小部件 (基于android系统4.1)。 1、首先创建AppWidgetProviderInfo对象,即在res/xml目录下创建一个appwidget_provider_image.xml,该文件的内容对于所有的App Widget基本上大体相同,其代码如下:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="93dp"
android:minHeight="93dp"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/appwidget_provider_image" />
2、接着创建App Widget的布局文件,用于显示App Widget,该文件中有两个显示的view,StackView就是图片浏览的主要view,ImageView只是当StackView中没有内容时才会显示出来,其文件如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<StackView
android:id="@+id/stack_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:loopViews="true" />
<ImageView
android:id="@+id/empty_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/stack_img_empty"
android:contentDescription="@string/app_name"/>
</FrameLayout>
3、然后创建AppWidgetProvider的实现类StackViewAppWidgetProvider,用来接收App Widget发布的广播,根据广播进行相关的创建、更新App Widget等操作,与之前数字时钟的AppWidgetProvider实现类有所不同,StackView中显示的数据不能在当前的StackViewAppWidgetProvider类中去设置,必须通过设置RemoteAdapter来启动一个提供数据的StackRemoteViewsService服务;并且如果需要使StackView中的每一个子view都有自己独立的行为动作,就必须首先在承载该StackView的RemoteViews中设置一个PenddingIntentTemplate,对于触发的子view就可以通过这个PenddingIntentTemplate去执行其中的Intent,然后在StackRemoteViewsFactory类的getViewAt方法中为每一个子view设置一个FillInIntent,从而携带不同的数据,这样就可以在每一个子view上触发事件,通过相应的处理类进行处理,其核心代码如下:
public static final String TOAST_ACTION = "com.android.stackviewappwidget.TOAST_ACTION";
public static final String EXTRA_ITEM = "com.android.stackviewappwidget.EXTRA_ITEM";
/**
* 广播接收处理
* 当intent的 action为TOAST_ACTION时,就进行相应的界面更新处理?
* (点击stackview的哪一个子view就将其显示在最前面)
*/
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if(intent.getAction().equals(TOAST_ACTION))
{
// 创建AppWidgetManager对象
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
// 获得应用小部件的id
int appWidgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
// 获取当前点击的stackview子view的位置
int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
// 根据view的位置进行界面的更新,将该view显示在stackview的最前面
// 创建RemoteViews对象
RemoteViews views = createRemoteViews(context, appWidgetId);
// 显示当前点击的子view
views.setDisplayedChild(R.id.stack_image, viewIndex);
// 更新AppWidget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
for(int i=0;i<appWidgetIds.length;i++)
{
// 构造展示图片浏览的RemoteViews对象
RemoteViews views = createRemoteViews(context, appWidgetIds[i]);
// 更新当前的App Widget
appWidgetManager.updateAppWidget(appWidgetIds[i], views);
}
}
/**
* 构造展示图片浏览的RemoteViews对象
* @return
*/
private RemoteViews createRemoteViews(Context context, int appWidgetId)
{
// 创建RemoteViews对象,用于widget布局
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_image);
// 创建启动v
Intent intent = new Intent(context, StackRemoteViewsService.class);
// 将app widget id 添加到intent extras中
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
// 给views设置RemoteViews适配器
// 该适配器通过intent来连接RemoteViewsService,获得展示的数据
views.setRemoteAdapter(R.id.stack_image, intent);
// 如果StackView的数据为空,则显示empty_image视图
views.setEmptyView(R.id.stack_image, R.id.empty_image);
/**
* 创建PendingIntent模版:给子view添加独立的触发事件
* 一旦点击子view之后就会发送一个带TOAST_ACTION的广播
*/
// 创建Intent对象,用来启动StackViewAppWidgetProvider
Intent toastIntent = new Intent(context, StackViewAppWidgetProvider.class);
// 设置Intent的Action,启动相应组件的标识
toastIntent.setAction(TOAST_ACTION);
// 将app widget id 添加到intent extras中
toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
toastIntent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
// 根据toastIntent创建一个启动广播接收器的PendingIntent模版
PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// 将PendingIntent模版添加到RemoteViews中
views.setPendingIntentTemplate(R.id.stack_image, toastPendingIntent);
return views;
}
4、创建为RemoteViewsAdapter提供数据的StackRemoteViewsService服务,该服务中的核心就是RemoteViewsFactory的实现类StackRemoteViewsFactory,该类其实类似于一个自定义的BaseAdapter,为了让StackRemoteViewsFactory能够生成显示一个view就必须实现几个核心的方法getCount、getViewAt、onCreate等。其核心代码如下:
public class StackRemoteViewsService extends RemoteViewsService {
// 创建RemoteViewsFactory对象
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
}
/**
* StackView中RemoteViews构造器
* @author Administrator
*
*/
class StackRemoteViewsFactory implements RemoteViewsFactory
{
private Context mContext;
// stack图片
private int[] stackImages;
public StackRemoteViewsFactory(Context applicationContext, Intent intent) {
this.mContext = applicationContext;
}
@Override
public int getCount() {
return stackImages.length;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public RemoteViews getViewAt(int position) {
// 创建RemoteViews对象
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.appwidget_provider_item);
// 设置RemoteViews中显示的图片信息
views.setImageViewResource(R.id.image_item, stackImages[position]);
// 创建Intent对象,用来为每一个子view携带相关的数据
Intent fillInIntent = new Intent();
fillInIntent.putExtra(StackViewAppWidgetProvider.EXTRA_ITEM, position);
// 通过设置setOnClickFillInIntent方法,结合PengdingIntentTemplate就可以实现子view的触发事件
views.setOnClickFillInIntent(R.id.image_item, fillInIntent);
return views;
}
@Override
public int getViewTypeCount() {
return 0;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public void onCreate() {
// 初始化stack图片
stackImages = new int[]{R.drawable.stack_img_1, R.drawable.stack_img_2, R.drawable.stack_img_3,
R.drawable.stack_img_4, R.drawable.stack_img_5, R.drawable.stack_img_6,
R.drawable.stack_img_7};
}
@Override
public void onDataSetChanged() {
}
@Override
public void onDestroy() {
}
}
}
5、最后将StackViewAppWidgetProvider类以及StackRemoteViewsService注册到AndroidManifest.xml配置文件中,其文件如下:
<!-- 处理StackViewAppWidget的广播接收器 -->
<receiver android:name=".StackViewAppWidgetProvider"
android:label="@string/image_view"
android:icon="@drawable/image_view">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="com.android.stackviewappwidget.TOAST_ACTION" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_image"/>
</receiver>
<!-- 提供集合数据的服务:用于绑定remoteviews视图 -->
<service android:name=".StackRemoteViewsService"
android:permission="android.permission.BIND_REMOTEVIEWS"/>
6、通过以上几步,自定义图片浏览的App Widget就创建成功了,由于是运行在android4.1系统上面的,将应用小部件添加到手机桌面的方式和将应用快捷键添加到手机桌面的方式一样,其步骤以及运行结果如下所示: 点击手机屏幕下方的菜单按钮,进入所有显示应用界面:
通过向右滑动屏幕,一般到最后一个屏幕的界面就可以找到我们自定义的图片浏览的App Widget:
通过长按该自定义的图片浏览的App Widget的图标,就可以将其拖到手机桌面进行显示: