Android Widget 开发详解(二)——支持listView滑动的widget

来源:互联网 发布:python 教务系统 编辑:程序博客网 时间:2024/05/18 10:52

转载请标明出处:http://blog.csdn.net/sk719887916/article/details/47027263

不少开发项目中都会有widget功能,别小瞧了它,他也是Android的七大组件之一,对widget陌生的朋友可以阅读下我的上篇文章< Android Widget工作原理详解(一)>关于内部的介绍,还没掌握的同学不要担心,开发AppWidget套路很简单,今天我们就实现一个可以加入listView滑动的widget,熟悉下一个普通widget的开发步骤。

一 创建AppWidgetProvider
此类是widget的控制核心,主要控制添加,删除,更新等。他是Broadcast的子类,可以拥有广播的一切特性。
创建 MyAppListWidgetProvider类继承AppWidgetProvider,实现其一下方法,onUpdate(),onReceive(), onEnabled(Context context) , onDeleted(), onDisabled()后面三方法可选而不可选。

1 onUpdate()
此方法一般处理widget的创建布局和更新UI操作,当widget添加到桌面会触发onUpdate()方法,接下我们可以在此里通过获取remoteViews来给widget加载一个布局,远程视图前面也说过,它是widget的资源管理工具,我们可以用来给widget转换一个它支持布局,仅支持特定的view,下面我为它加载一个listView,在widget上给某个控件设置点击事件采用PendingIntent,通过new一个延时意图,然后remoteViews.setOnClickPendingIntent()来注册点击事件。更新布局可以获得用WidgetManager..updateAppWidget(thisWidget, remoteViews)来加载或更新widget布局,也可以通过onReceive()
收到一个自定义的广播来调用此方法更新布局也可以。

@Override  public void onUpdate(Context context, AppWidgetManager appWidgetManager,          int[] appWidgetIds) {      // 获取Widget的组件名      ComponentName thisWidget = new ComponentName(context,              MyAppListWidgetProvider.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, MyAppListWidgetProvider.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,              MyAppListWidgetProvider.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);  }  

2 onReceive()

此方功类似广播的onReceive()用发,用开接收和处理广播,如果我们在manifest.xml注册了MyAppListWidgetProvider为一个appwidget,那么不必须为此广播加上widget标示,添加一action:,下面的 标签用来定义widget的属性,指定一个widget描述信息,具体释义请阅读 上篇widget原理详解文章,

<!-- Widget必须添加到manifest文件中,和Broadcaset Receiver一样使用“receiver”标签 -->         <receiver android:name=".MyAppListWidgetProvider" >             <!-- 此处设置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/widget_info"                 >             </meta-data>         </receiver>  

onReceive里处理代码逻辑,比如我这里用来接收widget的用来更新我们在onUpdate()给刷新按钮定义的点击事件,处理刷新界面需求,

/**      * 接收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,                      MyAppListWidgetProvider.class);              MyRemoteViewsFactory.mList.add("音乐"+i);              // 这句话会调用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();          }          i=i+1;      }  

3 onEnabled(Context context)

当wdiget可用正在拖进桌面时触发,这里我们一般可以进行一些变量初始的工作

@Override     public void onEnabled(Context context) {      // TODO Auto-generated method stub      super.onEnabled(context);         Toast.makeText(context, "用户将widget添加桌面了",                 Toast.LENGTH_SHORT).show();     }  

4 onDeleted(),
widget被删除了,这里我们通常用于释放一些对象和视图资源,便于防止内存泄露。

@Override   public void onDeleted(Context context, int[] appWidgetIds) {      // TODO Auto-generated method stub。       Toast.makeText(context, "用户将widget从桌面移除了",               Toast.LENGTH_SHORT).show();      super.onDeleted(context, appWidgetIds);   }  

5 onDisabled()
widget在被拖动的时候触发,这是widget是无法点击的,当停止拖动操作时widget可用。

二 创建RemoteViewsFactory
远程视图工厂,用来返回Remoteviews,其通过adpter进行工作的,这里我们调用 MyRemoteViewsFactory.mList.add()方法;给widget上的Listview加入数据。

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("");              for (int i = 0; i < 5; i++) {                    mList.add("item"+ i);              }          }          /*          * 当调用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;          }  

三 RemoteViewsService
RemoteViewsService子类提供了RemoteViewsFactory用于填充远程集合视图。
具体地说,其子类RemoteViewsService是一个远程的服务适配器 可以请求RemoteViews,管理RemoteViews的服务。我们继承RemoteViewsService来获得一个视图工厂,

@TargetApi(Build.VERSION_CODES.HONEYCOMB)  public class MyRemoteViewsService extends RemoteViewsService {      @Override      public RemoteViewsFactory onGetViewFactory(Intent intent) {             return new MyRemoteViewsFactory(this.getApplicationContext(), intent);      }  }  

四 增加widet基础属性配置
1 添加widget描述文件

我们为widget新增一个描述xml,在res/下新建一个xml文件目录,然后新建widget_info.xml文件,具体如下
给widget指定了最小的宽高和浏览的基础视图,包括其具体的布局文件。具体介绍请看上篇文章 —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="120dp"      android:minWidth="280dp"      android:previewImage="@drawable/ic_launcher"      android:resizeMode="horizontal|vertical"      android:updatePeriodMillis="0" >      <!--          sdk1.5之后updatePeriodMillis已失效,置为0,循环执行自行在代码中实现。          至于其他属性可以查一下。在其他随笔中我也给出了      -->  </appwidget-provider>  

2 新建widget资源文件xml
为widget新建一个实际要加载,也就是我直观的看到的视图布局。此布局通过widgetInfo的android:initialLayout=”@layout/my_widget_layout”
属性来指定。而widget描述信息我们在manifest.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="200dp"      android:background="@android:color/white"      android:orientation="vertical" >      <Button          android:id="@+id/button_refresh"          android:layout_width="wrap_content"          android:layout_height="wrap_content"          android:layout_gravity="center_horizontal"           android:background="@drawable/lite_widget_item_choosed_background_icon"              android:textColor="@android:color/white"          android:layout_marginTop="2dp"          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>  

通过以上的步骤我们简单的实现了一个widget,用于初学者学习和交流,比较复杂的widget逻辑我们还会加入网络访问功能,和一些和sevice进行数据交互,如果想要widget实现自动加入到桌面,或者widget支持自定义控件的话,第一可以将我的app变成系统app,第二,采用重写Remoteviews来支持我们自定义的view,具体实现逻辑后面再介绍,谢谢阅读。