您好,登录后才能下订单哦!
转自我的新浪博文
一、概念
首先要区分widget和AppWidget这两个概念。
1、Widget
widget可以直译为小部件,它在Android中代表视图的概念,如TextView、Button、EditText等widget视图控件,及LinearLayout等视图布局。

2、AppWidget
AppWidget是放置在手机屏幕的桌面小组件应用,如时钟、日历、天气等组件,与一般应用程序有所不同。一般应用虽也可以以图标的形式(快捷方式)放在桌面,但必须点击运行和查看;而AppWidget一般不须点击即直观呈现其主要内容。当然,AppWidget也可以被设置为点击打开其它屏幕或应用等。
而且,AppWidget可以被定时更新,如日历每天更新,时钟每分钟更新等。当然,也可以在AppWidget的视图界面中加入类似刷新的小按钮,以进行实时更新,如天气预报。
一般在提到Widget部件或Widget程序时,指的是AppWidget;如果说到widget控件,则可能是指视图控件,如Button等。
3、操作
通过在桌面(HomeScreen)中长按,在弹出的对话框中选择AppWidget部件来进行创建;或者在应用程序列表的AppWidget程序列表中选择并长按来创建。同一个AppWidget部件可以在桌面同时创建多个。每新建一个,实际上是生成了一个新的AppWidget实例。
要删除桌面的Widget部件,只需长按并拖动到垃圾箱即可。
二、一个简单的AppWidget应用
1、简单AppWidget组成
一个简单的AppWidget应用只需包括以下部分:
AppWidgetProviderInfo对象:
这个对象为AppWidget提供元数据,包括布局、更新频率等信息,这个对象定义在xml文件中,不需要自己编写,由系统根据XML文件生成。
AppWidgetProvider类:

如图所示,AppWidgetProvider类,继承自BroadcastReceiver,可以接收并处理广播事件。这个类定义了AppWidget的基本生命周期函数:
onReceive(Context, Intent) 接收广播事件。
onUpdate(Context , AppWidgetManager, int[] appWidgetIds) 到达指定的更新时间或用户向桌面添加widget时调用;实际是接受并处理“android.appwidget.action.APPWIDGET_UPDATE”广播事件。appWidgetIds保存着已创建的各(桌面)AppWidget实例编号。在onUpdate方法中可以依次更新所有实例的界面内容。
onEnabled(Context) 当AppWidget实例第一次被创建时调用
onDeleted(Context, int[] appWidgetIds) 当一个AppWidget实例被删除时调用
onDisabled(Context) 当最后一个AppWidget实例被删除时调用
2、一个简单应用开发
该应用很简单,只是在桌面显示一行文字。
(1)应用的界面布局文件res/layout/appwidget_provider_layout.xml:

(2)应用的元数据定义文件res/xml/appwidget_provider.xml:

(3)Widget实例提供程序SimpleWidgetProvider.java文件:
package com.example.simpleappwidget;
    import android.appwidget.AppWidgetManager;
    import android.appwidget.AppWidgetProvider;
    import android.content.Context;
    import android.util.Log;
    import android.widget.RemoteViews;
import com.example.simpleappwidget.R;
public class SimpleWidgetProvider extends AppWidgetProvider {
       private String TAG = "widgetexample";
       周期更新时调用
      public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
      {
         final int N = appWidgetIds.length;
         Log.i(TAG,String.valueOf(N));
         for (int i = 0; i < N; i++)
         {
            int appWidgetId = appWidgetIds[i];
            String message = "目前有"+N+"个AppWidget实例";
            构建RemoteViews对象来对桌面部件进行更新
            RemoteViews views = new RemoteViews(context.getPackageName(),
                       R.layout.appwidget_provider_layout);
            更新文本内容,指定布局的组件
            views.setTextViewText(R.id.appwidget_text, message);
            将RemoteViews的更新传入AppWidget进行更新
            appWidgetManager.updateAppWidget(appWidgetId, views);
         }
      }
    }
该应用比较简单,只是为了说明程序的结构。Provider类中仅提供了更新事件处理方法。运行界面如下图:

可以创建多个实例:

但并没有如估计的显示“目前有2个实例”。
关闭模拟器并重新启动打开,才显示有两个实例:

示例程序下载
问题解决:前面更新多个实例的问题,后面通过将以实例ID为参数逐一修改Widget组件实例的以下方法:
           appWidgetManager.updateAppWidget(appWidgetId, views);
    改为调用组件管理器的修改所有小组件实例的方法:
           ComponentName myComponentName = new ComponentName(context, SimpleWidgetProvider.class);
           appWidgetManager.updateAppWidget(myComponentName,views);
3、为AppWidget程序添加按钮事件处理
天气预报等桌面组件有类似功能,即点击一个小图标(按钮)刷新数据显示。这里只是简单模拟一下类似功能。
点击按钮刷新数据,可以有多种方式实现。如点击按钮打开一个Activity、点击发送广播消息、点击启动一个服务等。下面首先看一下点击打开Activity的关键代码实现:
(1)按钮事件处理可以在SimpleWidgetProvider类的onUpdate方法中实现:
......
    for (int i = 0; i < N; i++)
    {
       int appWidgetId = appWidgetIds[i];
       String message = "目前有"+N+"个AppWidget实例";
       RemoteViews views = new RemoteViews(context.getPackageName(),
                R.layout.appwidget_provider_layout);
       views.setTextViewText(R.id.appwidget_text, message);
       为按钮绑定点击事件处理器
       Intent intent = new Intent(context, MyActivity.class);
       intent.putExtra("appWidgetId", appWidgetId);
       Log.i(TAG,"ID:"+(intent.getExtras()).getInt("appWidgetId"));
       PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
             PendingIntent.FLAG_CANCEL_CURRENT);
       views.setOnClickPendingIntent(R.id.mybutton, pendingIntent);
       将RemoteViews的更新传入AppWidget进行更新
       appWidgetManager.updateAppWidget(appWidgetId, views);
    } 
    ...... 
需要指出的是如图所示的PendingIntent的几个常量值(用于getActivity等方法的参数):
    这里因为Intent带有数据,使用了PendingIntent.FLAG_CANCEL_CURRENT。
(2)MyActivity类的代码:
......
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_my);
       Bundle bundle = getIntent().getExtras();
       int appWidgetID = bundle.getInt("appWidgetId");
       Log.i(TAG,"another ID:"+appWidgetID);
       final Context context = this;
       RemoteViews views = new RemoteViews(context.getPackageName(),
             R.layout.appwidget_provider_layout);
       更新文本内容,指定布局的组件
       views.setTextViewText(R.id.appwidget_text, "点击按钮更新内容");
       取得AppWidgetManager实例
       AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
       appWidgetManager.updateAppWidget(appWidgetID, views);
       this.finish();
    }
......
示例程序×××
上述功能也可以通过广播消息进行处理。因为AppWidgetProvider本身就继承自BroadcastReceiver,所以可以在SimpleWidgetProvider类的onReceive方法中实现对自定义消息的处理。关键代码如下:
(1)SimpleWidgetProvider类代码:
......
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
    {
       final int N = appWidgetIds.length;
       Log.i(TAG,String.valueOf(N));
       for (int i = 0; i < N; i++)
       {
          int appWidgetId = appWidgetIds[i];
          ......   
          Intent intent = new Intent("update_appwidget_textview");
          intent.putExtra("appWidgetId", appWidgetId);
          PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
                           PendingIntent.FLAG_CANCEL_CURRENT);
          点击按钮将触发广播,当前接收器将即时接收和处理广播消息
          views.setOnClickPendingIntent(R.id.mybutton, pendingIntent);
          appWidgetManager.updateAppWidget(appWidgetId, views);
        }  
     }
......
     @Override
     public void onReceive(Context context, Intent intent)
     {
        String action = intent.getAction();
        if(action.equals("update_appwidget_textview"))
        {
           Log.i(TAG,"update_appwidget_textview");
           RemoteViews views = new RemoteViews(context.getPackageName(),
                          R.layout.appwidget_provider_layout);
           views.setTextViewText(R.id.appwidget_text, "点击按钮更新内容");
           AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
           int appWidgetId = (intent.getExtras()).getInt("appWidgetId");
           appWidgetManager.updateAppWidget(appWidgetId, views);
        }
        else
           super.onReceive(context, intent);
     }
......
(2)AndroidManifest.xml文件:

(3)应用的界面布局文件res/layout/appwidget_provider_layout.xml:

(4)应用的元数据定义文件res/xml/appwidget_provider.xml:

示例程序代码下载
    另外,从资料中还查到一种利用ComponentName类修改AppWidget实例的方法,只需对上面代码稍加改动:
onUpdate方法:
    for (int i = 0; i < N; i++)
       {
          为了看到每次调用该方法时内容的变化
String message = System.currentTimeMillis()+"";
RemoteViews views = new RemoteViews(context.getPackageName(),
                        R.layout.appwidget_provider_layout);
          views.setTextViewText(R.id.appwidget_text, message);
......
          Intent intent = new Intent("update_appwidget_textview");
          PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
                           PendingIntent.FLAG_CANCEL_CURRENT);
          views.setOnClickPendingIntent(R.id.mybutton, pendingIntent);
          appWidgetManager.updateAppWidget(appWidgetId, views);
        }  
    onReceive方法:
......
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
    ComponentName componentName = new ComponentName(context, SimpleWidgetProvider.class);
    appWidgetManager.updateAppWidget(componentName, views); 
......
使用广播消息处理的方式当然也可以另外创建一个接收器,不再具体分析。示例程序下载
使用本地Service服务更新Widget实例的代码下载
4、一个较实用的例子
例子比较简单,只是在HomeScreen桌面实时显示时间。
(1)SimpleWidgetProvider类关键代码(onUpdate方法):
......
int appWidgetId = appWidgetIds[i];
     Intent intent = new Intent("com.example.updatetime");
     intent.putExtra("appWidgetId", appWidgetId);
     context.startService(intent);
......
(2)ExampleService类代码:
......
     static int appWidgetId;
     static RemoteViews views;
     static AppWidgetManager appWidgetManager;
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         views = new RemoteViews(this.getPackageName(),
                        R.layout.appwidget_provider_layout);
         appWidgetManager = AppWidgetManager.getInstance(this);
         appWidgetId = (intent.getExtras()).getInt("appWidgetId");        
         new TimeThread().start();
         return super.onStartCommand(intent, flags, startId);
     }
     class TimeThread extends Thread
     {
         @Override
         public void run ()
         {
            do{                
               try
               {                    
                  Thread.sleep(1000);
                  views.setTextViewText(R.id.appwidget_text, DateFormat.format("hh:mm:ss",
                          System.currentTimeMillis()));
                  appWidgetManager.updateAppWidget(appWidgetId, views);
               }
               catch (InterruptedException e)
               {
                  e.printStackTrace();
               }
             } while(true);
          }
      }
......
桌面显示时钟的示例程序下载
该程序的更有效的代码
(5)时钟显示程序的另一种解决方法
主要变化是使用android.content.Intent.ACTION_TIME_TICK时钟服务,主要代码如下:
SimpleWidgetProvider类:
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
    {
       final int N = appWidgetIds.length;
       Log.i(TAG,String.valueOf(N));
       String message = N+"个Widget实例";
       RemoteViews views = new RemoteViews(context.getPackageName(),    
               R.layout.appwidget_provider_layout);
       views.setTextViewText(R.id.appwidget_text, message);
       MyReceiver myReceiver = new MyReceiver();
       IntentFilter myFilter = new IntentFilter(); 
       myFilter.addAction(android.content.Intent.ACTION_TIME_TICK);
       context.getApplicationContext().registerReceiver(myReceiver, myFilter);
       ComponentName myComponentName = new ComponentName(context, SimpleWidgetProvider.class);
       appWidgetManager.updateAppWidget(myComponentName,views);
     }
     public static void updateWidget(Context context,RemoteViews views)
     {
        ComponentName myComponentName = new ComponentName(context, SimpleWidgetProvider.class);
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
        appWidgetManager.updateAppWidget(myComponentName,views);
     }
MyReceiver类:
    @Override
    public void onReceive(Context context, Intent intent) {
       String action = intent.getAction();
       if(action.equals("android.intent.action.TIME_TICK"))
       {
           RemoteViews views = new RemoteViews(context.getPackageName(),   
               R.layout.appwidget_provider_layout);
               views.setTextViewText(R.id.appwidget_text, DateFormat.format("hh:mm",
                    System.currentTimeMillis()));
               SimpleWidgetProvider.updateWidget(context, views);
       }
    }
示例程序×××
需要说明的是ACTION_TIME_TICK这个广播消息是系统时钟消息,该消息只能由系统以每分钟一次的形式发送。不能在自定义类中通过sendbroadcast方法发出,否则会抛出“Permission Denial: not allowed to send broadcast android.intent.action.TIME_TICK”异常。
而且,程序中不能通过在manifest.xml里注册的方式接收到这个广播,只能在代码里通过registerReceiver()方法注册。
SDK文档原文内容:Broadcast Action: The current time has changed. Sent every minute. You can not receive this through components declared in manifests, only by exlicitly registering for it withContext.registerReceiver().
通过测试,发现在配置文件中设置小组件更新周期不起作用。android:updatePeriodMillis="1000"设置一秒更新一次,完全没有反应。只在长按程序生成桌面组件时,重新启动模拟器后,才会调用onUpdate方法。
以上测试验证了其它资料中提到新版本的Android屏蔽了小组件更新周期设置的说法。如果需要修改组件界面,需要在程序中使用如updateAppWidget(componentName, views)语句主动更新。
参考文章:
App Widgets
Android—AppWidget技术路线
Appwidget深入 -- 按钮事件
Android之桌面组件App Widget案例
Android Service学习之本地服务
TextView显示系统时间
解析APP触发Widget实例
android.content.ReceiverCallNotAllowedException: 解决方法
android之IntentFilter的用法_Intent.ACTION_TIME_TICK在manifest.xml不起作用
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。