原文:https://blog.csdn.net/harvic880925/article/details/41445407
AppWidget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新。你可以通过一个App Widget Provider来发布一个Widget。官方文档地址:《App Widgets》
这里涉及到两个方面的内容:AppWidgetProvider类和appwidget-provider标签;
1、appwidget-provider标签:
用来定义桌面widget的大小,初始状态等等信息的,它的位置应该放在res/xml文件夹下,具体的xml参数如下:
android:minWidth: 最小宽度
android:minHeight: 最小高度
android:updatePeriodMillis: 更新widget的时间间隔(ms),"86400000"为1个小时
android:previewImage: 预览图片(长按Home键出现的预览图片)
android:initialLayout: 加载到桌面时对应的布局文件
android:resizeMode: widget可以被拉伸的方向。horizontal表示可以水平拉伸,vertical表示可以竖直拉伸
android:widgetCategory: widget可以被显示的位置。home_screen表示可以将widget添加到桌面,keyguard表示widget可以被添加到锁屏界面。
android:initialKeyguardLayout: 加载到锁屏界面时对应的布局文件
2、AppWidgetProvider类:
当widget要实时更新,要响应用户操作时,就需要额外的类来辅助处理了,这个类就是AppWidgetProvider。
由于AppWidgetProvider要接收到当前widget的状态(是否被添加,是否被删除等),所以要接收通知,必然是派生自BroadcastReceiver。
AppWidgetProvider中的广播处理函数如下:(根据不同的使用情况,重写不同的函数)
onUpdate():
在3种情况下会调用OnUpdate()。onUpdate()是在main线程中进行,因此如果处理需要花费时间多于10秒,处理应在service中完成。
(1)在时间间隔到时调用,时间间隔在widget定义的android:updatePeriodMillis中设置;
(2)用户拖拽到主页,widget实例生成。无论有没有设置Configureactivity,我们在Android4.4的测试中,当用户拖拽图片至主页时,widget实例生成,会触发onUpdate(),然后再显示activity(如果有)。这点和资料说的不一样,资料认为如果设置了Configure acitivity,就不会在一开始调用onUpdate(),而实验显示当实例生成(包括创建和重启时恢复),都会先调用onUpate()。在本例,由于此时在preference尚未有相关数据,创建实例时不能有效进行数据设置。
(3)机器重启,实例在主页上显示,会再次调用onUpdate()
onDeleted(Context,int[]):
当 widget 被删除时被触发。
onEnabled(Context):
当第1个 widget 的实例被创建时触发。也就是说,如果用户对同一个 widget 增加了两次(两个实例),那么onEnabled()只会在第一次增加widget时触发。
onDisabled(Context):
当最后1个 widget 的实例被删除时触发。
onReceive(Context,Intent):
在接收到广播时,调用。
3,清单配置
LedClockWidget .java
com/example/administrator/LedClockWidget.java
packagecom.example.administrator;
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.widget.RemoteViews;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class LedClockWidget extends AppWidgetProvider {
private Timer mTimer = new Timer();
private AppWidgetManager mAppWidgerManager;
private Context mContext;
//将0-9的液晶数字图片定义为数组
private int[] digits = new int[]{R.drawable.p0, R.drawable.p1, R.drawable.p2, R.drawable.p3, R.drawable.p4, R.drawable.p5, R.drawable.p6, R.drawable.p7, R.drawable.p8, R.drawable.p9,};
//将显示小时、分钟、秒钟的ImageView定义为数组
private int[] digitViews = new int[]{R.id.img01, R.id.img02, R.id.img04, R.id.img05, R.id.img07, R.id.img08,};
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds){
this.mAppWidgerManager =appWidgetManager;
this.mContext =context;
//定义计时器
mTimer = new Timer();
//启动周期性调度
mTimer.schedule(new TimerTask() {
@Override
public void run() {
//发送空消息,通知界面更新
handler.sendEmptyMessage(0x123);
}
}, 0, 1000);
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.main);
//定义SimpleDateFormat对象
SimpleDateFormat df = new SimpleDateFormat("HHmmss");
//将当前时间格式化为HHmmss的形式
String timeStr = df.format(new Date());
for (int i = 0; i
layout/main.xml
xml/my_lock.xml
app/src/main/AndroidManifest.xml
1、有关布局错误
在构造Widget布局时,AppWidget支持的布局和控件非常有限,有如下几个:
AppWidget支持的布局:
AppWidget支持的控件:
AnalogClock
AnalogClock
Button
Chronometer
ImageButton
ImageView
ProgressBar
TextView
ViewFlipper
ListView
GridView
StackView
AdapterViewFlipper
除此之外的所有控件(包括自定义控件)都无法显示,无法显示时,添加出来的widget会显示“加载布局出错”
2、appwidget-provider出现错误
如果appwidget-provider页面出现错误提示:error:No resource identifier found for attribute ‘widgetCategory’ in package’android’
这是由于buildtarget应该在17以上
setRemoteAdapter(intviewId,Intentintent):该方法可以使用Intent更新RemoteViews中viewId对应的组件。
上面方法的Intent参数应该封装一个RemoteViewsService参数,RemoteViewsService虽然继承了Service组件,但它的主要作用是为RemoteViews中viewId对应的组件提供列表项。
由于Intent参数负责提供列表项,因此viewId参数对应的组件可以是ListView、GridView、StackView和AdapterViewFlipper等,这些组件都是AdapterView的子类,由此可见RemoteViewsService负责提供的对象,应该是一个类似于Adapter的对象。
RemoteViewsService通常用于被继承,继承该基类时需要重写它的onGetViewFactory()方法,该方法就需要返回一个类似于Adapterr对象——但不是Adapter,而是RemoteViewsFactory对象,RemoteViewsFactory的功能完全类似于Adapter。
实例:
StackWidgetService.java
package org.crazyit.desktop;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
public class StackWidgetService extends RemoteViewsService
{
// 重写该方法,该方法返回一个RemoteViewsFactory对象。
// RemoteViewsFactory对象的的作用类似于Adapter,
// 它负责为RemoteView中指定组件提供多个列表项。
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent)
{
return new StackRemoteViewsFactory(this.getApplicationContext(),
intent); //①
}
class StackRemoteViewsFactory implements
RemoteViewsService.RemoteViewsFactory
{
// 定义一个数组来保存该组件生成的多个列表项
private int[] items = null;
private Context mContext;
public StackRemoteViewsFactory(Context context, Intent intent)
{
mContext = context;
}
@Override
public void onCreate()
{
// 初始化items数组
items = new int[] { R.drawable.bomb5, R.drawable.bomb6,
R.drawable.bomb7, R.drawable.bomb8, R.drawable.bomb9,
R.drawable.bomb10, R.drawable.bomb11, R.drawable.bomb12,
R.drawable.bomb13, R.drawable.bomb14, R.drawable.bomb15,
R.drawable.bomb16
};
}
@Override
public void onDestroy()
{
items = null;
}
// 该方法的返回值控制该对象包含多少个列表项
@Override
public int getCount()
{
return items.length;
}
// 该方法的返回值控制各位置所显示的RemoteViews
@Override
public RemoteViews getViewAt(int position)
{
// 创建RemoteViews对象,加载/res/layout目录下widget_item.xml文件
RemoteViews rv = new RemoteViews(mContext.getPackageName(),
R.layout.widget_item);
// 更新widget_item.xml布局文件中的widget_item组件
rv.setImageViewResource(R.id.widget_item,
items[position]);
// 创建Intent、用于传递数据
Intent fillInIntent = new Intent();
fillInIntent.putExtra(StackWidgetProvider.EXTRA_ITEM, position);
// 设置当单击该RemoteViews时传递fillInIntent包含的数据
rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);
// 此处使用让线程暂停0.5秒来模拟加载该组件
try
{
System.out.println("加载【" + position + "】位置的组件");
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
return rv;
}
@Override
public RemoteViews getLoadingView()
{
return null;
}
@Override
public int getViewTypeCount()
{
return 1;
}
@Override
public long getItemId(int position)
{
return position;
}
@Override
public boolean hasStableIds()
{
return true;
}
@Override
public void onDataSetChanged()
{
}
}
}
widget_item.xml
StackWidgetProvider.java
package org.crazyit.desktop;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import android.widget.Toast;
public class StackWidgetProvider extends AppWidgetProvider
{
public static final String TOAST_ACTION
= "org.crazyit.desktop.TOAST_ACTION";
public static final String EXTRA_ITEM
= "org.crazyit.desktop.EXTRA_ITEM";
@Override
public void onUpdate(Context context,
AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
// 创建RemoteViews对象,加载/res/layout目录下的widget_layout.xml文件
RemoteViews rv = new RemoteViews(context.getPackageName(),
R.layout.widget_layout);
Intent intent = new Intent(context, StackWidgetService.class);
// 使用intent更新rv中stack_view组件(StackView)
rv.setRemoteAdapter(R.id.stack_view, intent); //①
// 设置当StackWidgetService提供的列表项为空时,直接显示empty_view组件
rv.setEmptyView(R.id.stack_view, R.id.empty_view);
// 创建启动StackWidgetProvider组件(作为BroadcastReceiver)的Intent
Intent toastIntent = new Intent(context,
StackWidgetProvider.class);
// 为该Intent设置Action属性
toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
// 将Intent包装成PendingIntent
PendingIntent toastPendingIntent = PendingIntent
.getBroadcast(context, 0, toastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// 将PendingIntent与stack_view进行关联
rv.setPendingIntentTemplate(R.id.stack_view,
toastPendingIntent);
// 使用AppWidgetManager通过RemteViews更新AppWidgetProvider
appWidgetManager.updateAppWidget(
new ComponentName(context, StackWidgetProvider.class), rv); //②
super.onUpdate(context, appWidgetManager, appWidgetIds);
}
@Override
public void onDeleted(Context context, int[] appWidgetIds)
{
super.onDeleted(context, appWidgetIds);
}
@Override
public void onDisabled(Context context)
{
super.onDisabled(context);
}
@Override
public void onEnabled(Context context)
{
super.onEnabled(context);
}
// 重写该方法,将该组件当成BroadcastReceiver使用
@Override
public void onReceive(Context context, Intent intent)
{
if (intent.getAction().equals(TOAST_ACTION))
{
// 获取Intent中的数据
int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
// 显示Toast提示
Toast.makeText(context, "点击第【" + viewIndex + "】个列表项",
Toast.LENGTH_SHORT).show();
}
super.onReceive(context, intent);
}
}
widget_layout.xml
Manifest.xml
stackwidgetinfo.xml