一年好景君须记,最是橙黄橘绿时。这篇文章主要讲述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, 在桌面上长按, 等待弹出对话框, 选择“小部件”, 从列表中选择拖拽带手机桌面。
【Android小部件Widget----全解析】完整工程: https://github.com/ljheee/MyTimeWidget
推荐阅读
- SQLServer中的cross apply和FOR XML PATH
- android7.x Launcher3源码解析---workspace和allapps加载流程
- Android 使用PopupWindow实现弹出更多的菜单
- Android DataBinding库(MVVM设计模式)
- Android 关于java.util.NoSuchElementException错误
- Android控件介绍
- Android中Alarm的机制
- Android判断App是否在前台运行
- Android控件onClick事件