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

转载请标明出处: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()
收到一个自定义的广播来调用此方法更新布局也可以。


[java]  view plain  copy
  1. @Override  

[java]  view plain  copy
  1. public void onUpdate(Context context, AppWidgetManager appWidgetManager,  
  2.         int[] appWidgetIds) {  
  3.     // 获取Widget的组件名  
  4.     ComponentName thisWidget = new ComponentName(context,  
  5.             MyAppListWidgetProvider.class);  
  6.   
  7.     // 创建一个RemoteView  
  8.     RemoteViews remoteViews = new RemoteViews(context.getPackageName(),  
  9.             R.layout.my_widget_layout);  
  10.   
  11.     // 把这个Widget绑定到RemoteViewsService  
  12.     Intent intent = new Intent(context, MyRemoteViewsService.class);  
  13.     intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[0]);  
  14.   
  15.     // 设置适配器  
  16.     remoteViews.setRemoteAdapter(R.id.widget_list, intent);  
  17.   
  18.     // 设置当显示的widget_list为空显示的View  
  19.     remoteViews.setEmptyView(R.id.widget_list, R.layout.none_data);  
  20.   
  21.     // 点击列表触发事件  
  22.     Intent clickIntent = new Intent(context, MyAppListWidgetProvider.class);  
  23.     // 设置Action,方便在onReceive中区别点击事件  
  24.     clickIntent.setAction(clickAction);  
  25.     clickIntent.setData(Uri.parse(clickIntent.toUri(Intent.URI_INTENT_SCHEME)));  
  26.   
  27.     PendingIntent pendingIntentTemplate = PendingIntent.getBroadcast(  
  28.             context, 0, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
  29.   
  30.     remoteViews.setPendingIntentTemplate(R.id.widget_list,  
  31.             pendingIntentTemplate);  
  32.   
  33.     // 刷新按钮  
  34.     final Intent refreshIntent = new Intent(context,  
  35.             MyAppListWidgetProvider.class);  
  36.     refreshIntent.setAction("refresh");  
  37.     final PendingIntent refreshPendingIntent = PendingIntent.getBroadcast(  
  38.             context, 0, refreshIntent, PendingIntent.FLAG_UPDATE_CURRENT);  
  39.     remoteViews.setOnClickPendingIntent(R.id.button_refresh,  
  40.             refreshPendingIntent);  
  41.   
  42.     // 更新Wdiget  
  43.     appWidgetManager.updateAppWidget(thisWidget, remoteViews);  
  44.   
  45. }  
      
     2  onReceive()

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

[html]  view plain  copy
  1. <!-- Widget必须添加到manifest文件中,和Broadcaset Receiver一样使用“receiver”标签 -->  
  2.        <receiver android:name=".MyAppListWidgetProvider" >  
  3.            <!-- 此处设置Wdiget更新动作 -->  
  4.            <intent-filter>  
  5.                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />  
  6.            </intent-filter>  
  7.            <!-- 此处设置Widget的描述资源res/xml/my_widget.xml -->  
  8.            <meta-data  
  9.                android:name="android.appwidget.provider"  
  10.                android:resource="@xml/widget_info"   
  11.               >  
  12.            </meta-data>  
  13.        </receiver>  

  onReceive里处理代码逻辑,比如我这里用来接收widget的用来更新我们在onUpdate()给刷新按钮定义的点击事件,处理刷新界面需求,
 
[java]  view plain  copy
  1. /** 
  2.      * 接收Intent 
  3.      */  
  4.     @Override  
  5.     public void onReceive(Context context, Intent intent) {  
  6.         super.onReceive(context, intent);  
  7.        
  8.         String action = intent.getAction();  
  9.   
  10.         if (action.equals("refresh")) {  
  11.             // 刷新Widget  
  12.             final AppWidgetManager mgr = AppWidgetManager.getInstance(context);  
  13.             final ComponentName cn = new ComponentName(context,  
  14.                     MyAppListWidgetProvider.class);  
  15.   
  16.             MyRemoteViewsFactory.mList.add("音乐"+i);  
  17.         
  18.             // 这句话会调用RemoteViewSerivce中RemoteViewsFactory的onDataSetChanged()方法。  
  19.             mgr.notifyAppWidgetViewDataChanged(mgr.getAppWidgetIds(cn),  
  20.                     R.id.widget_list);  
  21.              
  22.         } else if (action.equals(clickAction)) {  
  23.             // 单击Wdiget中ListView的某一项会显示一个Toast提示。  
  24.             Toast.makeText(context, intent.getStringExtra("content"),  
  25.                     Toast.LENGTH_SHORT).show();  
  26.         }  
  27.         i=i+1;  
  28.     }  

    3 onEnabled(Context context) 

    当wdiget可用正在拖进桌面时触发,这里我们一般可以进行一些变量初始的工作
   
[java]  view plain  copy
  1. @Override  
  2.    public void onEnabled(Context context) {  
  3.     // TODO Auto-generated method stub  
  4.     super.onEnabled(context);  
  5.       
  6.       
  7.        Toast.makeText(context, "用户将widget添加桌面了",  
  8.                Toast.LENGTH_SHORT).show();  
  9.    }  

    

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

 
[java]  view plain  copy
  1. @Override  
  2.  public void onDeleted(Context context, int[] appWidgetIds) {  
  3.     // TODO Auto-generated method stub。  
  4.      Toast.makeText(context, "用户将widget从桌面移除了",  
  5.              Toast.LENGTH_SHORT).show();  
  6.     super.onDeleted(context, appWidgetIds);  
  7.  }  


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

二 创建RemoteViewsFactory

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

[java]  view plain  copy
  1. public class MyRemoteViewsFactory implements RemoteViewsFactory {  
  2.   
  3.      private final Context mContext;  
  4.         public static List<String> mList = new ArrayList<String>();  
  5.   
  6.         /* 
  7.          * 构造函数 
  8.          */  
  9.         public MyRemoteViewsFactory(Context context, Intent intent) {  
  10.   
  11.             mContext = context;  
  12.         }  
  13.   
  14.         /* 
  15.          * MyRemoteViewsFactory调用时执行,这个方法执行时间超过20秒回报错。 
  16.          * 如果耗时长的任务应该在onDataSetChanged或者getViewAt中处理 
  17.          */  
  18.         @Override  
  19.         public void onCreate() {  
  20.             // 需要显示的数据  
  21.             mList.add("");  
  22.             for (int i = 0; i < 5; i++) {  
  23.                   mList.add("item"+ i);  
  24.             }  
  25.             
  26.              
  27.         }  
  28.   
  29.         /* 
  30.          * 当调用notifyAppWidgetViewDataChanged方法时,触发这个方法 
  31.          * 例如:MyRemoteViewsFactory.notifyAppWidgetViewDataChanged(); 
  32.          */  
  33.         @Override  
  34.         public void onDataSetChanged() {  
  35.           
  36.         }  
  37.   
  38.         /* 
  39.          * 这个方法不用多说了把,这里写清理资源,释放内存的操作 
  40.          */  
  41.         @Override  
  42.         public void onDestroy() {  
  43.             mList.clear();  
  44.         }  
  45.   
  46.         /* 
  47.          * 返回集合数量 
  48.          */  
  49.         @Override  
  50.         public int getCount() {  
  51.             return mList.size();  
  52.         }  
  53.   
  54.         /* 
  55.          * 创建并且填充,在指定索引位置显示的View,这个和BaseAdapter的getView类似 
  56.          */  
  57.         @Override  
  58.         public RemoteViews getViewAt(int position) {  
  59.             if (position < 0 || position >= mList.size())  
  60.                 return null;  
  61.             String content = mList.get(position);  
  62.             // 创建在当前索引位置要显示的View  
  63.             final RemoteViews rv = new RemoteViews(mContext.getPackageName(),  
  64.                     R.layout.my_widget_layout_item);  
  65.   
  66.             // 设置要显示的内容  
  67.             rv.setTextViewText(R.id.widget_list_item_tv, content);  
  68.   
  69.             // 填充Intent,填充在AppWdigetProvider中创建的PendingIntent  
  70.             Intent intent = new Intent();  
  71.             // 传入点击行的数据  
  72.             intent.putExtra("content", content);  
  73.             rv.setOnClickFillInIntent(R.id.widget_list_item_tv, intent);  
  74.   
  75.             return rv;  
  76.         }  
  77.   
  78.         /* 
  79.          * 显示一个"加载"View。返回null的时候将使用默认的View 
  80.          */  
  81.         @Override  
  82.         public RemoteViews getLoadingView() {  
  83.             return null;  
  84.         }  
  85.   
  86.         /* 
  87.          * 不同View定义的数量。默认为1(本人一直在使用默认值) 
  88.          */  
  89.         @Override  
  90.         public int getViewTypeCount() {  
  91.             return 1;  
  92.         }  
  93.   
  94.         /* 
  95.          * 返回当前索引的。 
  96.          */  
  97.         @Override  
  98.         public long getItemId(int position) {  
  99.             return position;  
  100.         }  
  101.   
  102.         /* 
  103.          * 如果每个项提供的ID是稳定的,即她们不会在运行时改变,就返回true(没用过。。。) 
  104.          */  
  105.         @Override  
  106.         public boolean hasStableIds() {  
  107.             return true;  
  108.         }  

三  RemoteViewsService

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

[java]  view plain  copy
  1. @TargetApi(Build.VERSION_CODES.HONEYCOMB)  
  2. public class MyRemoteViewsService extends RemoteViewsService {  
  3.   
  4.     @Override  
  5.     public RemoteViewsFactory onGetViewFactory(Intent intent) {  
  6.           
  7.            return new MyRemoteViewsFactory(this.getApplicationContext(), intent);  
  8.     }  
  9.   
  10. }  


四 增加widet基础属性配置

  1  添加widget描述文件
     
    我们为widget新增一个描述xml,在res/下新建一个xml文件目录,然后新建widget_info.xml文件,具体如下
   给widget指定了最小的宽高和浏览的基础视图,包括其具体的布局文件。具体介绍请看上篇文章 --- widget原理详解

  
[java]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:initialLayout="@layout/my_widget_layout"  
  4.     android:minHeight="120dp"  
  5.     android:minWidth="280dp"  
  6.     android:previewImage="@drawable/ic_launcher"  
  7.     android:resizeMode="horizontal|vertical"  
  8.     android:updatePeriodMillis="0" >  
  9.   
  10.     <!--  
  11.         sdk1.5之后updatePeriodMillis已失效,置为0,循环执行自行在代码中实现。  
  12.         至于其他属性可以查一下。在其他随笔中我也给出了  
  13.     -->  
  14.   
  15. </appwidget-provider>  

 
  2  新建widget资源文件xml
    
    为widget新建一个实际要加载,也就是我直观的看到的视图布局。此布局通过widgetInfo的android:initialLayout="@layout/my_widget_layout"
属性来指定。而widget描述信息我们在 manifest.xml中 用<meta-data>标签用来指定。

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="200dp"  
  5.     android:background="@android:color/white"  
  6.     android:orientation="vertical" >  
  7.   
  8.     <Button  
  9.         android:id="@+id/button_refresh"  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content"  
  12.         android:layout_gravity="center_horizontal"  
  13.          android:background="@drawable/lite_widget_item_choosed_background_icon"      
  14.         android:textColor="@android:color/white"  
  15.         android:layout_marginTop="2dp"  
  16.         android:text="刷新" />  
  17.       
  18.         
  19.     <ListView  
  20.         android:id="@+id/widget_list"  
  21.         android:layout_width="match_parent"  
  22.         android:layout_height="wrap_content"  
  23.         android:cacheColorHint="#00000000"  
  24.         android:scrollbars="none" />  
  25.     <!-- 此处的ListView 可以换成StackView或者GridView -->  
  26.   
  27. </LinearLayout>  


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值