Android开发5:应用程序窗口小部件App Widgets的实现

摘要:
可以容纳其它AppWidget的应用程序组件被称为AppWidget宿主。AppWidgetProvider类的实现  定义基本方法以允许你编程来和AppWidget连接,这基于广播事件。在AndroidStudio中创建Widget类后,会直接生成相关文件。android:resource–指定AppWidgetProviderInfo资源路径。RemoteView架构允许用户程序更新主屏幕的View,点击Widget激活点击事件,Android会将其转发给用户程序,由AppWidgetProviders类处理,使得用户程序可更新主屏幕Widget。AppWidgetProvider只接收和这个AppWidget相关的事件广播,比如这个AppWidget被更新,删除,启用,以及禁用。

前言

  本次主要是实现一个Android应用,实现静态广播、动态广播两种改变 widget内容的方法,即在上篇博文中实验的基础上进行修改,所以此次实验的重点是AppWidget小部件的实现啦~

  首先,我们简单说一下Widget是一个啥玩意~

  应用程序窗口小部件(Widget)是微小的应用程序视图,可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget provider来发布一个Widget。可以容纳其它App Widget的应用程序组件被称为App Widget宿主。

Widget是在桌面上的一块显示信息的东西,也通过单击Widget跳转到一个程序里面。而系统自带的程序,典型的Widget是music,这个Android内置的音乐播放小程序。这个是典型的Widget+app应用。就是一个程序既可以通过Widget启动,也可以通过App启动。Widget就是一个AppWidgetProvider+一个UI界面显示(预先绑定了好多Intent),界面上的信息可以通过程序控制而改变,单击Widget,上的控件只能激发发送一个Intent,或发出一个Service的启动通知。而AppWidgetProvider可以拦截这个Intent,而进行相应的处理(比如显示新的信息)。

基础知识

为了创建一个App Widget,你需要下面这些:

AppWidgetProviderInfo对象

  描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider类。这应该在XML里定义。

AppWidgetProvider类的实现

  定义基本方法以允许你编程来和App Widget连接,这基于广播事件。通过它,当这个App Widget被更新,启用,禁用和删除的时候,你都将接收到广播通知。

视图布局

  为这个App Widget定义初始布局,在XML中。

另外,你可以实现一个App Widget配置活动。这是一个可选的活动Activity,当用户添加App Widget时加载并允许他在创建时来修改App Widget的设置。

widget 的添加:长按菜单键,点击 widgets 选项。找到对应的 widget 将其拖入桌面。对 于不同的 API 版本显示会稍有不同。

Android开发5:应用程序窗口小部件App Widgets的实现第1张Android开发5:应用程序窗口小部件App Widgets的实现第2张

典型的 Android Widget 有三个主要组件,一个边框、一个框架和图形控件以及其他元素。 在 Android Studio 中创建 Widget 类后,会直接生成相关文件。

Android开发5:应用程序窗口小部件App Widgets的实现第3张

首先,在应用程序AndroidManifest.xml文件中声明AppWidgetProvider类,比如:

<receiver Android:name="ExampleAppWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"android:resource="@xml/example_appwidget_info" />
</receiver>

<receiver>元素需要android:name属性,它指定了App Widget使用的AppWidgetProvider。

<intent-filter>元素必须包括一个含有android:name属性的<action>元素。该元素指定AppWidgetProvider接受ACTION_APPWIDGET_UPDATE广播。这是唯一你必须显式声明的广播。当需要的时候,AppWidgetManager会自动发送所有其他App Widget广播给AppWidgetProvider。

<meta-data>元素指定了AppWidgetProviderInfo资源并需要以下属性:

      • android:name–指定元数据名称。
      • android:resource–指定AppWidgetProviderInfo资源路径。

1. Widget 布局文件 widget_demo.xml,布局中有一个 ImageView,一个 TextView。 要求:文字颜色为红色,大小为 20dp,整体背景为透明。最后效果如下:

  Android开发5:应用程序窗口小部件App Widgets的实现第4张

2.增加AppWidgetProviderInfo元数据

  AppWidgetProviderInfo定义一个App Widget的基本特性,比如最小布局尺寸,初始布局资源,刷新频率,以及(可选的)创建时加载的一个配置活动。使用单独的一个<appwidget-provider>元素在XML资源里定义AppWidgetProviderInfo对象并保存到项目的res/xml/目录下。

Widget 内容提供者文件 widget_demo_info.xml,编辑该文件,设置其大小属性和布 局,如下图:

  Android开发5:应用程序窗口小部件App Widgets的实现第5张

  其中,minWidth 为最小宽度,minHeight 为最小高度,initialLayout 为初始布局。

3. 修改 WidgetDemo.java 代码,重写 onUpdate 方法,为 Widget 添加事件,使得能够返 回主页面。

  这里需要使用到一种用户程序访问主屏幕和修改特定区域内容的方法:RemoteView 架 构 。RemoteView 架构允许用户程序更新主屏幕的 View,点击 Widget 激活点击事件,Android 会将其转发给用户程序,由 AppWidgetProviders 类处理,使得用户程序可更新主 屏幕 Widget。

Android开发5:应用程序窗口小部件App Widgets的实现第6张

pendingIntent是一种特殊的 Intent。主要的区别在于 Intent 的执行立刻的,而 pendingIntent 的执行不是立刻的。本次使用方法类的静态方法为 getActivity(Context, int, Intent, int),对应 Intent 的跳转到一个 activity 组件的操作。

使用AppWidgetProvider类

你必须通过在清单文件中使用<receiver>元素来声明你的AppWidgetProvider类实现为一个广播接收器(参见上面的Declaring an App Widget in the Manifest)。

AppWidgetProvider类扩展BroadcastReceiver为一个简便类来处理App Widget广播。AppWidgetProvider只接收和这个App Widget相关的事件广播,比如这个App Widget被更新,删除,启用,以及禁用。当这些广播事件发生时,AppWidgetProvider将接收到下面的方法调用:

  onUpdate(Context, AppWidgetManager, int[])

  这个方法调用来间隔性的更新App Widget,间隔时间用AppWidgetProviderInfo里的updatePeriodMillis属性定义(参见添加AppWidgetProviderInfo元数据)。这个方法也会在用户添加App Widget时被调用,因此它应该执行基础的设置,比如为视图定义事件处理器并启动一个临时的服务Service,如果需要的话。但是,如果你已经声明了一个配置活动,这个方法在用户添加App Widget时将不会被调用,而只在后续更新时被调用。配置活动应该在配置完成时负责执行第一次更新。(参见下面的创建一个App Widget配置活动Creating an App Widget Configuration Activity。)

onDeleted(Context, int[])

  当App Widget从宿主中删除时被调用。

onEnabled(Context)

  当一个App Widget实例第一次创建时被调用。比如,如果用户添加两个你的App Widget实例,只在第一次被调用。如果你需要打开一个新的数据库或者执行其他对于所有的App Widget实例只需要发生一次的设置,那么这里是完成这个工作的好地方。

onDisabled(Context)

  当你的App Widget的最后一个实例被从宿主中删除时被调用。你应该在onEnabled(Context)中做一些清理工作,比如删除一个临时的数据库。

onReceive(Context, Intent)

  这个接收到每个广播时都会被调用,而且在上面的回调函数之前。你通常不需要实现这个方法,因为缺省的AppWidgetProvider实现过滤所有App Widget广播并恰当的调用上述方法。

注意:在Android 1.5中,有一个已知问题,onDeleted()方法在该调用时不被调用。为了规避这个问题,你可以像Group post中描述的那样实现onReceive()来接收这个onDeleted()回调。

  最重要的AppWidgetProvider回调函数是onUpdated(),因为它是在每个App Widget添加进宿主时被调用的(除非你使用一个配置活动)。如果你的App Widget要接受任何用户交互事件,那么你需要在这个回调函数中注册事件处理器。如果你的App Widget不创建临时文件或数据库,或者执行其它需要清理的工作,那么onUpdated()可能是你需要定义的唯一的回调函数。

4.重写 onReceive 方法

  在 Widget 类中重写 onReceive 方法,这里需要使用到 RemoteView 以及 Bundle。当接 收到对应广播时进行数据处理。

Android开发5:应用程序窗口小部件App Widgets的实现第7张

if 条件语句中主要用到的函数为:setTextViewText、setImageViewResource。之后使用 AppWidgetManager 类对 Widget 进行更新。

实验内容

  实现一个 Android 应用,实现静态广播、动态广播两种改变 widget 内容的方法。在上次实 验的基础上进行修改,所以一些关于静态动态广播的内容会简略。

  具体要求:

(1)该界面为应用启动后看到的界面。

  Android开发5:应用程序窗口小部件App Widgets的实现第8张

  widget 初始情况如下

  Android开发5:应用程序窗口小部件App Widgets的实现第9张

(2)点击静态注册按钮,跳转至如下界面。

  Android开发5:应用程序窗口小部件App Widgets的实现第10张

  点击表单项目。如 banana。widget 会发生对应变化。点击 Widget 上的图片可以跳转回主页面

  Android开发5:应用程序窗口小部件App Widgets的实现第11张

(3)点击动态注册按钮,跳转至如下界面。 实现以下功能:

  a)可以编辑广播的信息,点击 Send 按钮发送广播。

  b)设置一个按钮进行广播接收器的注册与注销。

  c)广播接收器若已被注册,发送出的广播信息能够及时更新桌面上 Widget 上文字内容及 更新为默认 dynamic 图片。

  d)点击 Widget 上的图片可以跳转回主页面。

  Android开发5:应用程序窗口小部件App Widgets的实现第12张

实验步骤

  首先,在Android Studio中创建Widget类,直接生成相关文件,其中包括界面布局XML文件、widget的provider文件信息(xml)以及在项目的AndroidMenifest.xml文件中添加了一个receiver标签,需要我们添加过滤更新事件,并需要指向之前创建的Widget类。

  AndroidMenifest.xml文件中,intent-filter中过滤了APPWIDGET_UPDATE事件,这个事件是由系统触发的更新事件,每个widget必须包含这个事件;meta-data标签描述的是widget的配置文件指向,该文件描述了widget的一些基本信息(其中由于需要在静态注册中实现,intent-filter中也过滤了staticreceiver):

<receiver
            android:name=".MyAppWidget"android:enabled="true"android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
                <action android:name="com.example.yanglh6.myapplication4.staticreceiver" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"android:resource="@xml/my_app_widget_info"/>
</receiver>

  

  接下来根据要求编写widget的provider文件信息(xml),minWidth和minHeight是widget的最小宽度和高度,这个值是一个参考值,系统会根据实际情况进行改变,initialLayout属性指明widge的视图布局文件,updatePeriodMillis属性是widget每隔多久更新一次的时间,单位为毫秒:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"android:initialKeyguardLayout="@layout/my_app_widget"android:initialLayout="@layout/my_app_widget"android:minHeight="55dp"android:minWidth="200dp"android:previewImage="@drawable/example_appwidget_preview"android:resizeMode="horizontal|vertical"android:updatePeriodMillis="86400000"android:widgetCategory="home_screen"></appwidget-provider>

  接下来就是界面布局,在这个示例中需要一个ImageView控件和一个TextView控件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center">

    <ImageView
        android:id="@+id/WidgetImage"android:layout_width="60dp"android:layout_height="60dp"android:gravity="center"android:src="@mipmap/apple"/>

    <TextView
        android:id="@+id/WidgetName"android:layout_width="wrap_content"android:layout_height="60dp"android:textColor="@color/red"android:textSize="20dp"android:layout_toRightOf="@+id/WidgetImage"android:text="Apple"android:gravity="center"/>


</RelativeLayout>

  布局文件实现了一个如下图的布局:

Android开发5:应用程序窗口小部件App Widgets的实现第13张

  然后在Widget中,重写onUpdate方法,为Widget添加事件,使得能够返回主页面。这里需要使用到一种用户程序访问主屏幕和修改特定区域内容的方法RemoteView架构。RemoteView架构允许用户程序更新主屏幕的View,点击 Widget激活点击事件,Android会将其转发给用户程序,由AppWidgetProviders类处理,使得用户程序可更新主屏幕Widget。

@Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        Intent clickInt = new Intent(context, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, clickInt, 0);
        RemoteViews view = newRemoteViews(context.getPackageName(),R.layout.my_app_widget);
        view.setOnClickPendingIntent(R.id.WidgetImage, pendingIntent);
        appWidgetManager.updateAppWidget(appWidgetIds, view);
    }

  接下来在Widge类中重写onReceive方法,这里需要使用到RemoteView以及Bundle。当接收到对应广播时进行数据处理(由于我们在AndroidMenifest.xml文件中注册时将APPWIDGET_UPDAT事件和staticreceiver都指向Widge类,所以在这里我们StaticReceiver类删掉,将里面对OnReceive函数重写的部分添加在Widget类中):

@Override
    public voidonReceive(Context context, Intent intent) {
        Log.i("debug", intent.toString());
        super.onReceive(context, intent);
        RemoteViews view = newRemoteViews(context.getPackageName(),R.layout.my_app_widget);
        Bundle bundle =intent.getExtras();
        String widgetName = bundle.getString("name");
        int widgetImage = bundle.getInt("ItemImage");
        if (intent.getAction().equals("com.example.yanglh6.myapplication4.staticreceiver")) {
            view.setTextViewText(R.id.WidgetName, widgetName);
            view.setImageViewResource(R.id.WidgetImage, widgetImage);
            AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(context);
            appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidget.class), view);

            Bitmap bitmap= BitmapFactory.decodeResource(context.getResources(),bundle.getInt("ItemImage"));
            int imageId = (int) bundle.get("ItemImage");
            NotificationManager notificationManager =(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            Notification.Builder builder = newNotification.Builder(context);
            builder.setContentTitle("静态广播")
                    .setContentText(bundle.getString("name"))
                    .setLargeIcon(bitmap)
                    .setSmallIcon(imageId)
                    .setTicker("您有一条新消息")
                    .setAutoCancel(true);
            Intent Intent1 = new Intent(context, MainActivity.class);

            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, Intent1, 0);
            builder.setContentIntent(pendingIntent);
            Notification notify =builder.build();
            notificationManager.notify(0, notify);
        }
    }

  单独把Widget部分onReceive方法的重写列出:

public voidonReceive(Context context, Intent intent) {
        Log.i("debug", intent.toString());
        super.onReceive(context, intent);
        RemoteViews view = newRemoteViews(context.getPackageName(),R.layout.my_app_widget);
        Bundle bundle =intent.getExtras();
        String widgetName = bundle.getString("name");
        int widgetImage = bundle.getInt("ItemImage");
        if (intent.getAction().equals("com.example.yanglh6.myapplication4.staticreceiver")) {
            view.setTextViewText(R.id.WidgetName, widgetName);
            view.setImageViewResource(R.id.WidgetImage, widgetImage);
            AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(context);
            appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidget.class), view);
     }
    }

  对于动态注册来说,不需要在AndroidMenifest.xml添加receiver,但在DynamicActivity中进行注册:

 dynamicReceiver = newDynamicReceiver();
                    IntentFilter dynamic_filter = newIntentFilter();
                    dynamic_filter.addAction("com.example.yanglh6.myapplication4.dynamicreceiver");
                    registerReceiver(dynamicReceiver, dynamic_filter);

  所以动态注册时只能在DynamicReceiver中对Onreceive函数进行重写,完成Widget的更新(与静态注册类似):

@Override
    public voidonReceive(Context context, Intent intent) {
        if (intent.getAction().equals("com.example.yanglh6.myapplication4.dynamicreceiver")) {
            Bundle bundle =intent.getExtras();
            Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bundle.getInt("ItemImage"));
            int imageId = bundle.getInt("ItemImage");
            RemoteViews view = newRemoteViews(context.getPackageName(),R.layout.my_app_widget);
            String widgetName = bundle.getString("name");

            view.setTextViewText(R.id.WidgetName, widgetName);
            view.setImageViewResource(R.id.WidgetImage, imageId);
            AppWidgetManager appWidgetManager=AppWidgetManager.getInstance(context);
            appWidgetManager.updateAppWidget(new ComponentName(context, MyAppWidget.class), view);

            NotificationManager notificationManager =(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            Notification.Builder builder = newNotification.Builder(context);
            builder.setContentTitle("动态广播")
                    .setContentText(widgetName)
                    .setLargeIcon(bitmap)
                    .setSmallIcon(imageId)
                    .setTicker("您有一条新消息")
                    .setAutoCancel(true);
            Intent mIntent = new Intent(context, MainActivity.class);

            PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mIntent, 0);
            builder.setContentIntent(pendingIntent);
            Notification notify =builder.build();
            notificationManager.notify(0, notify);
        }
    }

完成实验~

运行截图

  Android开发5:应用程序窗口小部件App Widgets的实现第14张

  Android开发5:应用程序窗口小部件App Widgets的实现第15张

注意事项

  自己要充分理解AndroidMenifest.xml各部分的含义以及Android的机制,在AndroidMenifest.xml的注册和指向必须清晰。

  对于静态来说,在sendBroadcast(intent)实现后,在AndroidMenifest.xml找到intent注册时的receiver并指向对应的广播接收函数,在这个函数中实现各个事件;对于动态来说,由于在DynamicActivity中进行注册,在那时可以定义指向的动态广播接收类。

源码下载

源码下载点击这里~

1、本实验实验环境:

操作系统 Windows 10

实验软件 Android Studio 2.2.1

虚拟设备:Galaxy_Nexus

API:21

2、贴代码的时候由于插入代码框的大小问题,代码格式不太严整,望见谅~

谢谢大家~

免责声明:文章转载自《Android开发5:应用程序窗口小部件App Widgets的实现》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Mongodb数据更新命令解决git本地代码推服务器每次都要输入用户名和密码的问题下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Win10系统的SurfacePro4如何重装系统-1 SurfacePro专用的PE

下载SurfacePro专用的PE(普通的PE可能不支持触摸屏操作,甚至没法启动Surface,所以务必要重新制作PE),下面提供百度云下载地址,下载之后,双击EXE,会进行检测 链接:https://pan.baidu.com/s/1RHK8EUaLzoPnNfRyH9ZrLg 密码:owno   然后进入下面的窗口,选择目标优盘,所有参数都是默...

iOS: Crash文件解析(一)

iOS Crash文件的解析(一)   开发程序的过程中不管我们已经如何小心,总是会在不经意间遇到程序闪退。脑补一下当你在一群人面前自信的拿着你的App做功能预演的时候,流畅的操作被无情地Crash打断。联想起老罗在发布Smartisan OS的时候说了,他准备了10个手机,如果一台有问题,就换一台,如果10台后挂了他就不做手机了。好了不闲扯了,今天就跟大...

压倒程序员的最后一个面试题,iOS性能优化的面试题

点赞再看,养成习惯,欢迎大家关注我面试小专栏 :iOS中高级进阶之路有我准备的一线大厂面试资料和简历模板,欢迎Star! 这是我前面几天碰到的面试题: 如何对定位和分析项目中影响性能的地方?以及如何进行性能优化?我的答案:定位方法:instruments在iOS上进行性能分析的时候,首先考虑借助instruments这个利器分析出问题出在哪,不要凭空想象,...

Visual C++ 6.0编程环境的使用

1.1 编制并运行程序的四部曲 (1)编辑(把程序代码输入,交给计算机)。 (2)编译(成目标程序文件.obj)。编译就是把高级语言变成计算机可以识别的2进制语言,计算机只认识1和0,编译程序把人们熟悉的语言换成2进制的。编译程序把一个源程序翻译成目标程序的工作过程分为五个阶段:词法分析;语法分析;语义检查和中间代码生成;代码优化;目标代码生成。主要是进行...

android蓝牙通讯开发(详细)

新建一个工程之后,我们可以先看到界面左边的项目栏,我们可以看到,除了app目录以外,大多数的文件和目录都是自动生成的,我们也不需要对他们进行修改,而app目录之下的文件才是我们工作的重点。下面,我先对app目录下的内容进行一些讲解。 1.AndroidManifest.xml 这是整个项目的配置文件,我们在程序中定义的四大组件都需要在这里注册,另外,也可以...

iOS -证书制作

iOS证书制作攻略及配置注意事项 使用APICloud平台开发APP商用,首先得有开发者账号和各种证书,之后云编译打包正式版,上传到appstore审核上架。现在APICloud特别推出,证书申请和配置在控制台配置证书的注意事项的攻略,提供给大家。 云编译p12证书制作 生成certSigningRequest文件 如图,打开应用程序->实用工具-&...