桌面widget详解(三)——桌面widget中的控件交互方法

悠悠 2023-08-17 15:28 236阅读 0赞

这篇我们依然撇开播放器,讲一讲桌面widget中最基本的功能,大家先把最基本的给弄懂了,下篇再来实现播放器的桌面widget,可见实现一个复杂的桌面widget是多么的困难。

先看下这篇的实现效果:

在widget中实现一个text和两个button,当点击第一个button的时候,text中显示一个随机数。
20141202173949327

在上一篇中《桌面widget详解(一)——基本demo构建》,我们简单介绍了怎么显示了widget,但如何让widget中的按钮得到交互等问题还没有涉及,这篇中,虽然是新开的布局,但有关显示的部分,我就不再细讲,大家可以参考上一篇。

一、widget布局

本来我不打算列出这部分内容,但考虑到这个布局是新的,所以还是给大家简单列一下吧。但有关xml下的<appwidget-provider和AndroidManifest.xml下的注册就不再讲了。不过,为了区分,我把丑小孩的头像改成了小猫咪。其它没变化,看代码就知道了,下面列出widget的布局:
Center

  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="match_parent"
  5. android:background="#33000000"
  6. android:orientation="vertical" >
  7. <TextView
  8. android:id="@+id/text"
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content"
  11. android:layout_marginBottom="5dip"
  12. android:layout_marginTop="30dip"
  13. android:gravity="center_horizontal"
  14. android:text="text"
  15. android:textSize="14sp" />
  16. <Button
  17. android:id="@+id/btn1"
  18. android:layout_width="match_parent"
  19. android:layout_height="wrap_content"
  20. android:text="btn_1" />
  21. <Button
  22. android:id="@+id/btn2"
  23. android:layout_width="match_parent"
  24. android:layout_height="wrap_content"
  25. android:layout_marginTop="5dip"
  26. android:text="btn2" />
  27. </LinearLayout>

这个布局很简单,一个textView和两个btn,没什么难度。

二、AppWidget中控件交互(ExampleAppWidgetProvider.java)

1、发送广播与按钮事件绑定

因为appwidget运行的进程和我们创建的应用不在一个进程中,所以我们也就不能像平常引用控件那样来获得控件的实例。这时候,我们就要靠RemoteViews,直译成中文应该是远程视图; 也就是说通过这个东西我们能够获得不在同一进程中的对象,这也就为我们编写appwidget的处理事件提供了帮助。我们使用一下代码来创建一个RemoteViews。

  1. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  2. // 绑定事件
  3. remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);

第一句创建了一个remoteViews实例,然后将其中的按钮与点击事件绑定,后面的参数中又出来了一个PendingIntent。下面看看PendingIntent又是个什么玩意。

intent英文意思是意图,pending表示即将发生或来临的事情。
PendingIntent这个类用于处理即将发生的事情。比如在通知Notification中用于跳转页面,但不是马上跳转。所以我们可以将它理解成一个封装成消息的intent的。即这个intent并不是立即start,而是像消息一样被发送出去,等接收方接到以后,再分析里面的内容。

  1. Intent intent = new Intent();
  2. intent.setClass(context, ExampleAppWidgetProvider.class);
  3. intent.setData(Uri.parse("harvic:" + R.id.btn1));
  4. // 设置pendingIntent的作用
  5. PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);

可以看到,PendingIntent是Intent的封装,在构造PendingIntent前,也要先构造一个Intent,并可以利用Intent的属性传进去action,Extra等,同样,在接收时,对方依然是接收Intent的,而不是接收PaddingIntent。这个问题,我们后面可以看到。
PendingIntent.getBroadcast(context, 0,intent, 0);指从系统中获取一个用于可以发送BroadcastReceiver广播的PendingIntent对象。

讲完这两个之后,我们看一下OnUpdate的内容:

其中:

  1. String broadCastString = "harvic.provider";
  2. @Override
  3. public void onUpdate(Context context, AppWidgetManager appWidgetManager,
  4. int[] appWidgetIds) {
  5. Intent intent = new Intent();
  6. intent.setClass(context, ExampleAppWidgetProvider.class);
  7. intent.setData(Uri.parse("harvic:" + R.id.btn1));
  8. // 设置pendingIntent的作用
  9. PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
  10. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  11. remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);
  12. // 更新Appwidget
  13. appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
  14. }

这里的总体步骤就是,构造一个RemoteView,然后利用updateAppWidget()将构造的RemoteView更新指定的widget界面。

先看构造pendingIntent的过程:

  1. Intent intent = new Intent();
  2. intent.setClass(context, ExampleAppWidgetProvider.class);
  3. intent.setData(Uri.parse("harvic:" + R.id.btn1));
  4. // 设置pendingIntent的作用
  5. PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);

首先是构造一个广播时发送的intent,注意这个intent构造的是显示intent,直接将这个广播发送给ExampleAppWidgetProvider,附带的数据通过Data传送(最后附录中会讲为什么不通过putExtra传送额外值),这里传送过去的btn1的id值。

然后通过PendingIntent.getBroadcast();将intent封装到pendingIntent中,以待发送。
在构造了pendingIntent之后,就是将这个pendingIntent与btn1绑定,当用户点击btn1的时候,将广播发送出去。代码如下:

  1. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  2. remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);

最后,是将这个remoteView更新到所有widget上。(因为用户对某一个apps可以创建多个widget,要保持所有的widget状态统一)

  1. appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

注意这里有个appWidgetIds,这个参数是通过OnUpdate()传过来的,它是一个int数组,里面存储了用户所创建的所有widget的ID值。更新的时候也是通过widget的ID值,一个个更新的。
再絮叨一遍,我们这里做了两件事:

(1)、将按钮控件(R.id.btn1)与pendingIntent绑定。当用户点击按钮时,就会把所构造的intent发送出去。

涉及代码为:

  1. Intent intent = new Intent();
  2. intent.setClass(context, ExampleAppWidgetProvider.class);
  3. intent.setData(Uri.parse("harvic:" + R.id.btn1));
  4. // 设置pendingIntent的作用
  5. PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent, 0);
  6. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  7. remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);

(2)、更新所有的widget

主要利用updateAppWidget(appWidgetIds, remoteViews);将remoteView根据widget的id值,一个个更新界面,涉及代码为:

  1. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  2. // 更新Appwidget
  3. appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

可以看到,这里如果不算绑定按钮的话,其实什么都没有更新,因为RemoteView除了绑定控件的点击事件,还可以设置textView的文字,ImageVIew的图片Resource等,我们这里都还没有涉及

2、接收广播

由于我们在创建广播的Intent时,使用的显示Intent,所以我们的广播不需要注册就会发到这们这个类(ExampleAppWidgetProvider.java)里面。

在接收到广播后,我们先判断Intent中是不是包含data,因为我们在发送广播时放data中塞了数据(btn1的ID),所以只要data中有数据就可以判定是用户点击btn1发来的广播。然后同样利用RemoteView将textView的文字改成btn click加一串随机数字,代码如下:

  1. @Override
  2. public void onReceive(Context context, Intent intent) {
  3. if (intent == null) {
  4. return;
  5. }
  6. String action = intent.getAction();
  7. if (broadCastString.equals(action)) {
  8. // 只能通过远程对象来设置appwidget中的控件状态
  9. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
  10. R.layout.example_appwidget);
  11. // 通过远程对象将按钮的文字设置为”一个随机数”
  12. Random random1 = new Random();
  13. remoteViews.setTextViewText(R.id.text,"btn click:" + random1.nextInt());
  14. // 获得appwidget管理实例,用于管理appwidget以便进行更新操作
  15. AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
  16. // 相当于获得所有本程序创建的appwidget
  17. ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
  18. // 更新appwidget
  19. appWidgetManager.updateAppWidget(componentName, remoteViews);
  20. }
  21. super.onReceive(context, intent);
  22. }

同样还是利用updateAppWidget()函数来更新widget,对比OnUpdate()中的代码,这里有两点不同:

1、少了btn绑定,仅仅是更新界面,代码为:

  1. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
  2. R.layout.example_appwidget);
  3. // 通过远程对象将按钮的文字设置为”一个随机数”
  4. Random random1 = new Random();
  5. remoteViews.setTextViewText(R.id.text,"btn click:" + random1.nextInt());

在这里,我们将R.id.text的文字更新为btn click加一个随机数。因为每次点击按钮都会发送一个消息,所以每次点击产生的随机数是不同的,在界面上可以明显的表现出来。

2、改变了更新界面的方式

在OnUpdate中,我们更新界面是通过传过来的widget的id数组来更新所有widget的。而这里是通过获取ComponentName来更新的。其实这里还有另一种实现方式,即我们可以把OnUpdate中传过来的appWidgetIds保存起来,在这里同样使用OnUpdate中的appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);来更新。但我更推荐这里利用ComponentName的这种方式来更新,因为当我们的应用程序进程一被杀掉,当应用程序再起来的时候,使用ComponentName这种更新方式的代码还是可以继续响应的,而利用保存appWidgetIds的代码是不会响应的。

三、进阶——如何响应多个按钮控件

上面的例子中,我们简单实现了所谓的交互,但并没有办法识别出当前是哪个控件发出的,并根据不同的控件做出响应,先看看这部分效果:

20141203205553171

下面对上面的代码进行补充,首先在发送时,就应该加以区别当前是哪个控件发出的intent,代码如下:

  1. private PendingIntent getPendingIntent(Context context,int resID){
  2. Intent intent = new Intent();
  3. intent.setClass(context, ExampleAppWidgetProvider.class);
  4. intent.setData(Uri.parse("harvic:" + resID));
  5. PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent,0);
  6. return pendingIntent;
  7. }
  8. @Override
  9. public void onUpdate(Context context, AppWidgetManager appWidgetManager,
  10. int[] appWidgetIds) {
  11. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  12. remoteViews.setOnClickPendingIntent(R.id.btn1, getPendingIntent(context, R.id.btn1));
  13. remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));
  14. // 更新Appwidget
  15. appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);
  16. }

先看OnUpdate中设置RemoteView的代码:

  1. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  2. remoteViews.setOnClickPendingIntent(R.id.btn1, getPendingIntent(context, R.id.btn1));
  3. remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));
  4. // 更新Appwidget
  5. appWidgetManager.updateAppWidget(appWidgetIds, remoteViews);

创建一个RemoteView,然后将btn1,btn2分别进行绑定,这里我将PendingIntetn进行了封装:

  1. private PendingIntent getPendingIntent(Context context,int resID){
  2. Intent intent = new Intent();
  3. intent.setClass(context, ExampleAppWidgetProvider.class);
  4. intent.setData(Uri.parse("harvic:" + resID));
  5. PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,intent,0);
  6. return pendingIntent;
  7. }

要设置data域的时候,把控件ID设置进去,因为我们在绑定的时候,是将同一个ID绑定在一起的,所以哪个控件点击,发送的intent中data中的id就是哪个控件的id,绑定Id的代码就是下面这行:

  1. remoteViews.setOnClickPendingIntent(R.id.btn2, getPendingIntent(context, R.id.btn2));

然后就是接收的部分:

接收时主要就是先根据传送过来的Intent,找到data中的控件id:

  1. Uri data = intent.getData();
  2. int resID = -1;
  3. if(data != null){
  4. resID = Integer.parseInt(data.getSchemeSpecificPart());
  5. }

然后根据ID,定制RemoteView的TextView的字体内容

  1. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  2. Random random1 = new Random();
  3. switch (resID) {
  4. case R.id.btn1:
  5. remoteViews.setTextViewText(R.id.text,"btn1 click:" + random1.nextInt());
  6. break;
  7. case R.id.btn2:
  8. remoteViews.setTextViewText(R.id.text,"btn2 click:" + random1.nextInt());
  9. break;
  10. }

同样,最后是更新所有的wiget界面:

  1. // 获得appwidget管理实例,用于管理appwidget以便进行更新操作
  2. AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
  3. // 相当于获得所有本程序创建的appwidget
  4. ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
  5. // 更新appwidget
  6. appWidgetManager.updateAppWidget(componentName, remoteViews);

经过上面的讲解,总体的接收代码就是这样的:

  1. @Override
  2. public void onReceive(Context context, Intent intent) {
  3. Uri data = intent.getData();
  4. int resID = -1;
  5. if(data != null){
  6. resID = Integer.parseInt(data.getSchemeSpecificPart());
  7. }
  8. // 通过远程对象将按钮的文字设置为”一个随机数”
  9. RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.example_appwidget);
  10. Random random1 = new Random();
  11. switch (resID) {
  12. case R.id.btn1:
  13. remoteViews.setTextViewText(R.id.text,"btn1 click:" + random1.nextInt());
  14. break;
  15. case R.id.btn2:
  16. remoteViews.setTextViewText(R.id.text,"btn2 click:" + random1.nextInt());
  17. break;
  18. }
  19. // 获得appwidget管理实例,用于管理appwidget以便进行更新操作
  20. AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
  21. // 相当于获得所有本程序创建的appwidget
  22. ComponentName componentName = new ComponentName(context,ExampleAppWidgetProvider.class);
  23. // 更新appwidget
  24. appWidgetManager.updateAppWidget(componentName, remoteViews);
  25. super.onReceive(context, intent);
  26. }

好了,到这所有的代码都讲完了。

四、附(通过匿名Intent发送广播———不推荐):

在很多例子中使用的是匿名Intent来发送广播,即设定intent的Action来发送广播,这种方法我是极不推荐的,因为不能识别发送控件的id,这主要是由于pendingIntent的原因。针对这个工程,我也写了一个匿名Intent广播的例子,在这我就不讲了,大家可以看源码。

发表评论

表情:
评论列表 (有 0 条评论,236人围观)

还没有评论,来说两句吧...

相关阅读