在Android 3.0 中引入了 Collection View Widget。用于在窗口小组件中添加了对集合View 的支持。
如下:
(1)StackView 一个卡片View,以层叠的方式显示其子View。
(2)ListView 和传统的ListView一样
(3)GridView 网格列表。具体用法和传统的一样。
第一步:创建Widget布局文件
(1)Wdiget的布局文件 路径:res/layout/my_widget_layout.xml
<?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:background="#FF555555" android:orientation="vertical" > <Button android:id="@+id/button_refresh" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="刷新" /> <ListView android:id="@+id/widget_list" android:layout_width="match_parent" android:layout_height="wrap_content" android:cacheColorHint="#00000000" android:scrollbars="none" /> <!-- 此处的ListView 可以换成StackView或者GridView --> </LinearLayout>
(2)在集合中显示的项 路径:res/layout/my_widget_layout_item.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/widget_list_item_layout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#00000000" android:orientation="vertical" > <TextView android:id="@+id/widget_list_item_tv" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_marginRight="10dip" android:layout_marginTop="10dip" android:lineSpacingExtra="4dip" android:textColor="#FFFFF0" android:textSize="20sp" /> </LinearLayout>
第二步:创建Widget的描述文件
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/my_widget_layout" android:minHeight="280dp" android:minWidth="280dp" android:previewImage="@drawable/ic_launcher" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="0" > <!-- sdk1.5之后updatePeriodMillis已失效,置为0,循环执行自行在代码中实现。 至于其他属性可以查一下。在其他随笔中我也给出了 --> </appwidget-provider>
第三步:创建Class文件
(1)创建一个MyAppWidgetProvider.class文件,继承自AppWidgetProvider(PS:这个类主要处理Widget中的各种操作。如更新,删除,是否可用等)
package com.example.mywidget; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.widget.RemoteViews; import android.widget.Toast; public class MyAppWidgetProvider extends AppWidgetProvider { private String clickAction = "itemClick"; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // 获取Widget的组件名 ComponentName thisWidget = new ComponentName(context, MyAppWidgetProvider.class); // 创建一个RemoteView RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.my_widget_layout); // 把这个Widget绑定到RemoteViewsService Intent intent = new Intent(context, MyRemoteViewsService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[0]); // 设置适配器 remoteViews.setRemoteAdapter(R.id.widget_list, intent); // 设置当显示的widget_list为空显示的View remoteViews.setEmptyView(R.id.widget_list, R.layout.none_data); // 点击列表触发事件 Intent clickIntent = new Intent(context, MyAppWidgetProvider.class); // 设置Action,方便在onReceive中区别点击事件 clickIntent.setAction(clickAction); clickIntent.setData(Uri.parse(clickIntent.toUri(Intent.URI_INTENT_SCHEME))); PendingIntent pendingIntentTemplate = PendingIntent.getBroadcast( context, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setPendingIntentTemplate(R.id.widget_list, pendingIntentTemplate); // 刷新按钮 final Intent refreshIntent = new Intent(context, MyAppWidgetProvider.class); refreshIntent.setAction("refresh"); final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast( context, 0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.button_refresh, refreshPendingIntent); // 更新Wdiget appWidgetManager.updateAppWidget(thisWidget, remoteViews); } /** * 接收Intent */ @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); String action = intent.getAction(); if (action.equals("refresh")) { // 刷新Widget final AppWidgetManager mgr = AppWidgetManager.getInstance(context); final ComponentName cn = new ComponentName(context, MyAppWidgetProvider.class); MyRemoteViewsFactory.mList.add("five"); // 这句话会调用RemoteViewSerivce中RemoteViewsFactory的onDataSetChanged()方法。 mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn), R.id.widget_list); } else if (action.equals(clickAction)) { // 单击Wdiget中ListView的某一项会显示一个Toast提示。 Toast.makeText(context, intent.getStringExtra("content"), Toast.LENGTH_SHORT).show(); } } }
(2)创建一个MyRemoteViewsFactory.class文件,该类实现了RemoteViewsFactory接口
package com.example.mywidget; import java.util.ArrayList; import java.util.List; import android.content.Context; import android.content.Intent; import android.widget.RemoteViews; import android.widget.RemoteViewsService.RemoteViewsFactory; public class MyRemoteViewsFactory implements RemoteViewsFactory { private final Context mContext; public static List<String> mList = new ArrayList<String>(); /* * 构造函数 */ public MyRemoteViewsFactory(Context context, Intent intent) { mContext = context; } /* * MyRemoteViewsFactory调用时执行,这个方法执行时间超过20秒回报错。 * 如果耗时长的任务应该在onDataSetChanged或者getViewAt中处理 */ @Override public void onCreate() { // 需要显示的数据 mList.add("one"); mList.add("two"); mList.add("three"); mList.add("four"); } /* * 当调用notifyAppWidgetViewDataChanged方法时,触发这个方法 * 例如:MyRemoteViewsFactory.notifyAppWidgetViewDataChanged(); */ @Override public void onDataSetChanged() { } /* * 这个方法不用多说了把,这里写清理资源,释放内存的操作 */ @Override public void onDestroy() { mList.clear(); } /* * 返回集合数量 */ @Override public int getCount() { return mList.size(); } /* * 创建并且填充,在指定索引位置显示的View,这个和BaseAdapter的getView类似 */ @Override public RemoteViews getViewAt(int position) { if (position < 0 || position >= mList.size()) return null; String content = mList.get(position); // 创建在当前索引位置要显示的View final RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.my_widget_layout_item); // 设置要显示的内容 rv.setTextViewText(R.id.widget_list_item_tv, content); // 填充Intent,填充在AppWdigetProvider中创建的PendingIntent Intent intent = new Intent(); // 传入点击行的数据 intent.putExtra("content", content); rv.setOnClickFillInIntent(R.id.widget_list_item_tv, intent); return rv; } /* * 显示一个"加载"View。返回null的时候将使用默认的View */ @Override public RemoteViews getLoadingView() { return null; } /* * 不同View定义的数量。默认为1(本人一直在使用默认值) */ @Override public int getViewTypeCount() { return 1; } /* * 返回当前索引的。 */ @Override public long getItemId(int position) { return position; } /* * 如果每个项提供的ID是稳定的,即她们不会在运行时改变,就返回true(没用过。。。) */ @Override public boolean hasStableIds() { return true; } }
(3)创建MyRemoteViewsService.class 文件,该类继承了RemoteViewsService
package com.example.mywidget; import android.content.Intent; import android.widget.RemoteViewsService; public class MyRemoteViewsService extends RemoteViewsService { @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { return new MyRemoteViewsFactory(this.getApplicationContext(), intent); } }
第四步:在AndroidManifest.xml 文件中注册Widget以及相关类文件
<!-- Widget必须添加到manifest文件中,和Broadcaset Receiver一样使用“receiver”标签 --> <receiver android:name=".MyAppWidgetProvider" > <!-- 此处设置Wdiget更新动作 --> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <!-- 此处设置Widget的描述资源res/xml/my_widget.xml --> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_widget" > </meta-data> </receiver> <!-- 注册Service,注意:一定要加权限:android.permission.BIND_REMOTEVIEWS --> <service android:name=".MyRemoteViewsService" android:exported="false" android:permission="android.permission.BIND_REMOTEVIEWS" > </service>
PS:如何在应用程序中控制Widget
(1)在应用程序中通知Wdiget刷新。
//通过广播方式,通知Widget刷新 //这个Action是在Manifext.xml 中注册的,主意这个Action不要和系统的Action重复咯 Intent intent = new Intent("android.appwidget.action.APPWIDGET_UPDATE"); sendBroadcast(intent);
在MyAppWidgetProvider中的onReceive中处理这个Intent。(ps:和刷新按钮处理方式一样。对比然后执行相关的方法)
(2)在Activity中更新Widget界面
Button btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 获取Widget管理器 AppWidgetManager awm = AppWidgetManager .getInstance(MainActivity.this); // Widget组件名字 ComponentName compe = new ComponentName(MainActivity.this, MyAppWidgetProvider.class); // 创建RemoteViews RemoteViews rv = new RemoteViews(MainActivity.this .getPackageName(), R.layout.my_widget_layout); // 修改RemoteViews中的刷新按钮 // 可以只用RemoteViews.setXXX对应的方法修改RemoteView中的组件。 rv.setTextViewText(R.id.button_refresh, "刷新Refresh"); // 设置字体颜色 rv.setTextColor(R.id.button_refresh, Color.RED); //更新Widget awm.updateAppWidget(compe, rv); } });
PS:如何在创建Widget时,启动Acitivity对其设置。
有时候我们可能面临,在把窗口组件拖动到主屏幕的时候。需要启动Activity对其进行相应的设置。如系统自带的Mail。实现步骤如下:
(1)在Widget描述文件中添加如下属性:(我的描述文件路径:res/xml/my_widget.xml)
android:configure="com.example.mywidget.MainActivity"
主意:一定要使用完全限定名(包名+Acitivity名)
(2)为了省事我就在MainActivity.class写了。
package com.example.mywidget; import android.os.Bundle; import android.app.Activity; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Intent; import android.graphics.Color; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.RemoteViews; public class MainActivity extends Activity { private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 启动配置Activity的Intent会包含一个EXTRA_APPWIDGET_ID的Extra,该Extra参数就是需要配置的Widget的ID Intent intent = this.getIntent(); Bundle bundle = intent.getExtras(); if (null != bundle) { // 获取Widget的ID appWidgetId = bundle.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); } // 如果用户不接受配置就退出Acitivity。那么把结果设为canceld setResult(RESULT_CANCELED); Button btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // 获取Widget管理器 AppWidgetManager awm = AppWidgetManager .getInstance(MainActivity.this); // Widget组件名字 ComponentName compe = new ComponentName(MainActivity.this, MyAppWidgetProvider.class); // 创建RemoteViews RemoteViews rv = new RemoteViews(MainActivity.this .getPackageName(), R.layout.my_widget_layout); // 修改RemoteViews中的刷新按钮 // 可以只用RemoteViews.setXXX对应的方法修改RemoteView中的组件。 rv.setTextViewText(R.id.button_refresh, "刷新Refresh"); // 设置字体颜色 rv.setTextColor(R.id.button_refresh, Color.RED); // 更新Widget awm.updateAppWidget(compe, rv); // 通知Widget Manager配置已经完成 Intent result = new Intent(); result.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); // 注意:此时会执行MyAppWidgetProvider中的onReceive方法,其他方法包括onUpdate不会执行。绑定数据最好在onReceive中执行 setResult(RESULT_OK, result); // 关闭设置页 finish(); } }); } }