理解与应用Android桌面组件AppWidget

本文转自我的新浪博客文章

 

一、概念

        首先要区分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);
          }
      }

     ......

 

    桌面显示时钟的示例程序下载

    该程序的更有效的代码

 

参考文章:

   

    App Widgets   

    Android—AppWidget技术路线

    Appwidget深入 -- 按钮事件

    Android之桌面组件App Widget案例

    Android Service学习之本地服务

    TextView显示系统时间

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值