Android小部件Widget----全解析

一、Android应用的Widget介绍

App Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。

首先上一张图来给大家看一看效果。

Widget小部件,通常具备一定的功能;并且通常是和某个应用程序是关联的,通过点击手机桌面上的Widget小部件,会触发启动相对应的应用程序。Widget小部件,通常需要用户手动自行摆放到手机桌面(长按手机桌面,添加“小部件”,从小部件列表中选择)。

很多应用程序APP自带小部件,比如QQ音乐的“听音识曲”。安装了的朋友,长按手机桌面,添加“小部件”,会发现小部件列表中就会有QQ音乐的“听音识曲”。

详细介绍可以参考Android官方文本

二、Widget相关类介绍

1、AppWidgetProvider类

AppWidgetProvider 继承自BroadcastReceiver,它能接收 widget 相关的广播,例如 widget 的更新、删除、开启和禁用等。我们的Android应用程序可以通过一个AppWidgetProvider来发布一个Widget。

通常我们的Android应用程序需要定义一个类,继承AppWidgetProvider。

AppWidgetProvider中的广播处理函数

(1)onUpdate()

当 widget 更新时被执行。同样,当用户首次添加 widget 时,onUpdate() 也会被调用,这样 widget 就能进行必要的设置工作(如果需要的话) 。但是,如果定义了widget 的 configure属性(即android:config,后面会介绍),那么当用户首次添加 widget 时,onUpdate()不会被调用;之后更新 widget 时,onUpdate才会被调用。

(2)onAppWidgetOptionsChanged()

当 widget 被初次添加 或者 当 widget 的大小被改变时,执行onAppWidgetOptionsChanged()。你可以在该函数中,根据widget 的大小来显示/隐藏某些内容。可以通过getAppWidgetOptions() 来返回 Bundle 对象以读取 widget 的大小信息,Bundle中包括以下信息:

OPTION_APPWIDGET_MIN_WIDTH – 包含 widget 当前宽度的下限,以dp为单位。

OPTION_APPWIDGET_MIN_HEIGHT – 包含 widget 当前高度的下限,以dp为单位。

OPTION_APPWIDGET_MAX_WIDTH – 包含 widget 当前宽度的上限,以dp为单位。

OPTION_APPWIDGET_MAX_HEIGHT – 包含 widget 当前高度的上限,以dp为单位。

onAppWidgetOptionsChanged() 是 Android 4.1 引入的。

(3)onDeleted(Context, int[])

当 widget 被删除时被触发。

(4)onEnabled(Context)

当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。

(5)onDisabled(Context)

当最后1个 widget 的实例被删除时触发。

(6)onReceive(Context, Intent)

接收到任意广播时触发,并且会在上述的方法之前被调用。

总结,AppWidgetProvider 继承于 BroadcastReceiver。实际上,App Widge中的onUpdate()、onEnabled()、onDisabled()等方法都是在 onReceive()中分发调用的,是onReceive()对特定事件的响应。

2、AppWidgetProviderInfo

AppWidgetProviderInfo描述一个App Widget元数据,比如App Widget的布局,更新频率,以及AppWidgetProvider 类, 这个文件是在res/xml中定义的,后缀为xml。下面以XML示例来对AppWidgetProviderInfo中常用的类型进行说明。

<!--?xml version="1.0"encoding="utf-8"?-->

    <!--
    android:minWidth : 最小宽度
    android:minHeight : 最小高度
    android:updatePeriodMillis: 更新widget的时间间隔(ms),"86400000"为1个小时
    android:previewImage: 预览图片
    android:initialLayout: 加载到桌面时对应的布局文件
    android:resizeMode : widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸
    android:widgetCategory: widget可以被显示的位置。home_screen表示可以将widget添加到桌面,keyguard表示widget可以被添加到锁屏界面。
    android:initialKeyguardLayout: 加载到锁屏界面时对应的布局文件
     -->

</appwidget-provider>

(1) updatePeriodMillis

它定义了 widget 的更新频率。实际的更新时机不一定是精确的按照这个时间发生的。建议更新尽量不要太频繁,最好是低于1小时一次。 或者可以在配置 Activity 里面供用户对更新频率进行配置。实际上,当updatePeriodMillis的值小于30分钟时,系统会自动将更新频率设为30分钟!关于这部分,后面会详细介绍。

注意: 当更新时机到达时,如果设备正在休眠,那么设备将会被唤醒以执行更新。如果更新频率不超过1小时一次,那么对电池寿命应该不会造成多大的影响。 如果你需要比较频繁的更新,或者你不希望在设备休眠的时候执行更新,那么可以使用基于 alarm 的更新来替代 widget 自身的刷新机制。将 alarm 类型设置为 ELAPSED_REALTIME 或 RTC,将不会唤醒休眠的设备,同时请将 updatePeriodMillis 设为 0。

因此,我们若向动态的更新widget的某组件,最好通过service、AlarmManager、Timer等方式。

(2)initialLayout

指向 widget 的布局资源文件

(3)widgetCategory

指定了 widget 能显示的地方:能否显示在 home Screen 或 lock screen 或 两者都可以。它的取值包括:”home_screen”和 “keyguard”。Android 4.2 引入。

三、Widget程序使用步骤

  总体来说Widget程序,需要修改三个XML文件,一个class文件:

  1.第一个xml是布局XML文件(如:res\layout\my_time_widget.xml),是这个widget的界面。一般来说如果用这个部件显示时间,那就只在这个布局XML中声明一个textview就OK了。

  2.第二个xml是res\xml\my_time_widget_info.xml,主要是用于声明一个appwidget的。其中,Layout就是指定上面那个my_time_widget.xml。

  3.第三个xml是AndroidManifest.xml,注册broadcastReceiver信息。android:name=".MyTimeWidget",MyTimeWidget就是自定义的Java类继承自AppWidgetProvider。

  4.最后那个class用于做一些业务逻辑操作。新建MyTimeWidget类,让其继承类AppWidgetProvider。AppWidgetProvider中有许多回调方法。

实例:

1、 Widget界面布局res\layout\my_time_widget.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#09C"
    android:padding="@dimen/widget_margin">

    <TextView
        android:id="@+id/appwidget_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#09C"
        android:contentDescription="@string/appwidget_text"
        android:text="@string/appwidget_text"
        android:textColor="#ffffff"
        android:textSize="24sp"
        android:textStyle="bold|italic"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="106dp"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />

    <TextClock
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/appwidget_text"
        android:layout_centerHorizontal="true"
        android:id="@+id/textClock" />

</RelativeLayout>

布局文件:包含一个TextView 用于显示时间,和一个TextClock。

2、 Widget描述文件res\xml\my_time_widget_info.xml

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

3、 清单文件AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.ljheee.mytimewidget">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver android:name=".MyTimeWidget">
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
                <action android:name="com.ljheee.widget.UPDATE_TIME"/>
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/my_time_widget_info" />
        </receiver>

        <service
            android:name=".TimeService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.appwidget.action.MY_APP_WIDGET_SERVICE" />
            </intent-filter>
        </service>

    </application>
</manifest>

4、 MyTimeWidget.java

package com.ljheee.mytimewidget;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.widget.RemoteViews;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyTimeWidget extends AppWidgetProvider {

    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    // 启动ExampleAppWidgetService服务对应的action
    private final Intent TIME_SERVICE_INTENT =
            new Intent("android.appwidget.action.MY_APP_WIDGET_SERVICE");

    // 更新 widget 的广播对应的action
    private final String ACTION_UPDATE_ALL = "com.ljheee.widget.UPDATE_TIME";

    Context mContext;
    GetDate getDate;

    static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
                                int appWidgetId) {

        CharSequence widgetText = context.getString(R.string.appwidget_text);
        // Construct the RemoteViews object
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.my_time_widget);
        views.setTextViewText(R.id.appwidget_text, sdf.format(new Date()));

        // Instruct the widget manager to update the widget
        appWidgetManager.updateAppWidget(appWidgetId, views);
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        if(mContext==null){
            mContext = context;
        }

        final String action = intent.getAction();
        if (ACTION_UPDATE_ALL.equals(action)) {
            // “更新”广播
//            updateAppWidget(context, AppWidgetManager.getInstance(context));
//            onUpdate(context,AppWidgetManager.getInstance(context), mAppWidgetIds);
            getDate = new GetDate();
            getDate.execute();

        }

        super.onReceive(context, intent);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // There may be multiple widgets active, so update all of them
        for (int appWidgetId : appWidgetIds) {
            updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }

    @Override
    public void onEnabled(Context context) {
        // 在第一个 widget 被创建时,开启服务
        context.startService(TIME_SERVICE_INTENT);
        super.onEnabled(context);
    }

    @Override
    public void onDisabled(Context context) {
        // 在最后一个 widget 被删除时,终止服务
        context.stopService(TIME_SERVICE_INTENT);
        super.onDisabled(context);
    }

    //请求数据
    class GetDate extends AsyncTask {

        AppWidgetManager awm = null;
        ComponentName componentName = null;

        @Override
        protected void onPreExecute() {
            if(awm==null){
                awm = AppWidgetManager.getInstance(mContext);
            }
            if(componentName==null){
                componentName = new ComponentName(mContext, AppWidgetProvider.class);
            }

            super.onPreExecute();
        }

        @Override
        protected Object doInBackground(Object[] params) {
            return null;
        }

        @Override
        protected void onPostExecute(Object o) {
            //更新界面
            RemoteViews remoteViews = new RemoteViews(mContext.getPackageName(), R.layout.my_time_widget);
            remoteViews.setTextViewText(R.id.appwidget_text, sdf.format(new Date()));
            awm.updateAppWidget(componentName, remoteViews);

            super.onPostExecute(o);
        }
    }
}

目前Android Studio 2.23版本,直接新建一个Android工程(No Activity),在空工程中,直接new-àWidget-àApp Widget即可。输入MyTimeWidget类名,相关的widget界面布局、Widget描述文件、清单文件注册一体化自动建好。

编译并运行程序,我们知道这种Widget程序,即使装完了也不会在程序列表中出现,因为它根本就没有main Activity,在桌面上长按,等待弹出对话框,选择“小部件”,从列表中选择拖拽带手机桌面。

完整工程:https://github.com/ljheee/MyTimeWidget

时间: 2024-08-08 19:53:52

Android小部件Widget----全解析的相关文章

Android异步加载全解析之大图处理

Android异步加载全解析之大图处理 异步加载中非常重要的一部分就是对图像的处理,这也是我们前面用异步加载图像做演示例子的原因.一方面是因为图像处理不好的话会非常占内存,而且容易OOM,另一方面,图像也比文字要大,加载比较慢.所以,在讲解了如何进行多线程.AsyncTask进行多线程加载后,先暂停下后面的学习,来对图像的异步处理进行一些优化工作. 为什么要对图像处理 为什么要对图像进行处理,这是一个很直接的问题,一张图像,不管你拿手机.相机.单反还是什么玩意拍出来,它就有一定的大小,但是在不同

Android异步加载全解析之引入一级缓存

Android异步加载全解析之引入缓存 为啥要缓存 通过对图像的缩放,我们做到了对大图的异步加载优化,但是现在的App不仅是高清大图,更是高清多图,动不动就是图文混排,以图代文,如果这些图片都加载到内存中,必定会OOM.因此,在用户浏览完图像后,应当立即将这些废弃的图像回收,但是,这又带来了另一个问题,也就是当用户在浏览完一次图片后,如果还要返回去再进行重新浏览,那么这些回收掉的图像又要重新进行加载,保不准就要那些无聊到蛋疼的人在那一边看你回收GC,一边看你重新加载.这两件事情,肯定是互相矛盾的

Android异步加载全解析之Bitmap

Android异步加载全解析之Bitmap 在这篇文章中,我们分析了Android在对大图处理时的一些策略--Android异步加载全解析之大图处理  戳我戳我 那么在这篇中,我们来对图像--Bitmap进行一个更加细致的分析,掌握Bitmap的点点滴滴. 引入 Bitmap这玩意儿号称Android App头号杀手,特别是3.0之前的版本,简直就是皇帝般的存在,碰不得.摔不得.虽然后面的版本Android对Bitmap的管理也进行了一系列的优化,但是它依然是非常难处理的一个东西.在Androi

Android异步加载全解析之使用多线程

异步加载之使用多线程 初次尝试 异步.异步,其实说白了就是多任务处理,也就是多线程执行,多线程那就会有各种问题,我们一步步来看,首先,我们创建一个class--ImageLoaderWithoutCaches,从命名上,大家也看出来,这个类,我们实现的是不带缓存的图像加载,不多说,我们再创建一个方法--showImageByThread,通过多线程来加载图像: /** * Using Thread * @param imageView * @param url */ public void sh

Android异步加载全解析之使用AsyncTask

Android异步加载全解析之使用AsyncTask 概述 既然前面提到了多线程,就不得不提到线程池,通过线程池,不仅可以对并发线程进行管理,更可以提高他们执行的效率,优化整个App.当然我们可以自己创建一个线程池,不过这样是很烦的,要创建一个高效的线程池还是挺费事的,不过,Android系统给我吗提供了AsyncTask这样一个类,来帮助我们快速实现多线程开发,它的底层实现,其实就是一个线程池. AsyncTask初探 AsyncTask,顾名思义就是用来做异步处理的.通过AsyncTask,

Android异步加载全解析之IntentService

Android异步加载全解析之IntentService 搞什么IntentService 前面我们说了那么多,异步处理都使用钦定的AsyncTask,再不济也使用的Thread,那么这个IntentService是个什么鬼. 相对与前面我们提到的这两种异步加载的方式来说,IntentService有一个最大的特点,就是--IntentService不受大部分UI生命周期的影响,它为后台线程提供了一个更直接的操作方式.不过,IntentService的不足主要体现在以下几点: 不可以直接和UI做

优质Android小部件:索尼滚动相册

虽然骚尼手机卖的不怎么样,但是有些东西还是做的挺好的,工业设计就不用说了,索尼的相册的双指任意缩放功能也是尤其炫酷.其桌面小部件滚动相册我觉得也挺好的,比谷歌原生的相册墙功能好多了,网上搜了一下也没发现有人写这个,于是,下面就介绍下我的高A货. 首先是效果图: 主要手势操作有: 上/下满速移动,可以上滑/下滑一张图片 上/下快读移动,则根据滑动速度,上滑/下滑多张图片 单击则请求系统图库展示该图片 该小部件的主要优点:在屏幕内的小范围内提供一个很好的图片选择/浏览部件,尤其是切换图片时有很强的靠

android 创建桌面小部件widget

1. 创建自定义widget的广播类,继承自 AppWidgetProvider(有了这个广播就会在widgets中能够选择了吗?)这个广播的生命周期主要有五个,在第一个widget拖动到桌面和最后一个widget删除和已经有widget时拖动到桌面的生命周期是不同的 根据对生命周期的分析,在onUpdate中进行初始化,在onDisabled中进行销毁(关闭服务). public class ProcessWidgetReceiver extends AppWidgetProvider { p

Android异步加载全解析之开篇瞎扯淡

Android异步加载 概述 Android异步加载在Android中使用的非常广泛,除了是因为避免在主线程中做网络操作,更是为了避免在显示时由于时间太长而造成ANR,增加显示的流畅性,特别是像ListView.GridView这样的控件,如果getView的时间太长,就会造成非常严重的卡顿,非常影响性能. 本系列将展示在Android中如何进行异步加载操作,并使用ListView来作为演示的对象. 如何下载图像 下载自然是需要使用网络,使用网络就不能在主线程,在主线程就会爆炸.所以我们必须要在