先来看下效果图. 这是仿三星FM收音机的一个widget.
1. 首先在widget创建一个布局文件: layout/widget.xml.
2. 在xml中创建xml/radioinfo.xml:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget" android:minHeight="50.0dip" android:minWidth="300.0dip" android:previewImage="@drawable/fm_radio_widget_preview" android:resizeMode="vertical" />属性解释: android:initialLayout="@layout/widget" : 指定widget的布局文件
android:previewImage="@drawable/fm_radio_widget_preview" 指定widget在手机小部件中的缩略图.
3. 创建一个继承AppWidgetProvider类FMRadioWidgetProvider.java, 并实现onUpdate , onReceive ,这两个函数.
3.1 AppWidgetProvider解释: 这个类其实是一个广播接收器, 当widget被拖放到桌面的时候会首先调用onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds)函数. 所以 , 一般在这个函数中做初始化操作.
3.2 为按钮添加事件:
Intent intent = new Intent(FMRadioService.ACTION_FM_NEXT); intent.setClass(context, FMRadioService.class); pIntent = PendingIntent.getService(context, 0, intent, 0); remoteViews.setOnClickPendingIntent(R.id.ic_arrow_right, pIntent);
这种方式绑定的事件只能适用于单个View. 对于像ListView, GridView这样的集合的item要绑定事件的话不适用. 这点后面讲到.
3.3 为组件设置相关属性, 例如给TextView设置文本:
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); remoteViews.setTextViewText(R.id.txt_frequency, String.valueOf(freq));
3.4 为GridView/ListView绑定数据集: widget中的FM电台列表是一个GridView. 那么如何为GridView设置数据集呢.
Intent intent = new Intent(context , FMRadioWidgetService.class); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.setData(FMRadioStation.Station.CONTENT_URI); remoteViews.setRemoteAdapter(appWidgetId, R.id.widget_gridView, intent);
public class FMRadioWidgetService extends RemoteViewsService { private final String LOGTAG = "FMRadioWidgetService" ; private final boolean ISLOG = false ; @Override public RemoteViewsFactory onGetViewFactory(Intent intent) { if(ISLOG)Log.d(LOGTAG , " FMRadioWidgetService::[onGetViewFactory ] intent = "+intent); return new FMRadioRemoteViewsFactory(getApplicationContext() , intent); } }
此处getViewAt(int position)的实现类似我们传统Adapter的bindView()函数. 然后在onDataSetChanged() 一般会从数据库读取数据.
@Override public RemoteViews getViewAt(int position) { RemoteViews rv = new RemoteViews(mContext.getPackageName() , R.layout.widget_item); ... HashMap<String, Object> map = mlistStations.get(position); rv.setTextViewText(R.id.widget_gv_txt_itme, freqStr); ... Intent fillInIntent = new Intent(); Bundle extras = new Bundle(); extras.putInt(FMRadioAppWidgetProvider.EXTRA_SORT, position); 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 rv; } @Override public void onDataSetChanged() { queryFMradioStation(); }
Intent fillInIntent = new Intent();
Bundle extras = new Bundle();
extras.putInt(FMRadioAppWidgetProvider.EXTRA_SORT, position);
fillInIntent.putExtras(extras);
// Make it possible to distinguish the individual on-click
// action of a given item
rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
而该处的intent要与FMRadioWidgetProvider.java的onUpdate函数中的:
// Set the action for the intent.
// When the user touches a particular view, it will have the effect of
// broadcasting TOAST_ACTION.
itemClcikIntent.setAction(FMRadioAppWidgetProvider.ACTION_CLICK);
itemClcikIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
PendingIntent itemPendingIntent = PendingIntent.getBroadcast(context, 0, itemClcikIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setPendingIntentTemplate(R.id.widget_gridView, itemPendingIntent);
这两处最终合成一个Intent. 当点击每一个item的时候, 会发送一个ACTION_CLICK的广播, 并且在FMRadioAppWidgetProvide接收广播后, 在onReceive中进行处理.
整个widget大概基本完成.