AppWidget应用场合
这东西常用于桌面小程序。比如华为手机自带的备忘录。。天气,一键锁屏啥的。可以学来玩玩。
应用AppWidget的前戏
<receiver android:name=".MyWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_provider_activity_info" />
</receiver>
这个加到AndroidManifest中,其中name是类名,intent-filter是关键,没他系统就不会把他当成小程序。resource就是他的布局文件。
然后新建一个xml文件夹(其实value文件夹里新建xml也可以)
app_provider_activity_info.xml:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/app_provider_activity"
android:minWidth="300dp"
android:minHeight="200dp"
android:updatePeriodMillis="86400000"></appwidget-provider>
这里可以用来规定appwidget的刷新时间,最小宽度以及布局文件的指定(就是initialLayout)。其实还有其他可以配置的,但最重要的就这几个了。
布局文件:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#456ffd"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="备忘录"/>
<ListView
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/list_1"/>
</LinearLayout>
(想复刻个备忘录来着。。)
ps:appwidget只支持有限的view种类和layout布局。流行的recyclerView和自定义view是不支持的。
绑定完布局后就是重点来了。。。
appwidget重头戏
首先这个东西继承自AppWidgetProvider,关于这个类其实和广播接收器差不多,做开发的时候直接视为广播接收器也未尝不可。。。先说下重写频率比较高的几个函数。
1. onReceive
这个是appWidget用来接收广播的地方,根据广播可以进行不同的操作
2.onUpdate
顾名思义,就是更新的时候调用,在初始化的时候就会调用一次,然后就是等之前配置文件设定的时间或者自己操作更新。
3.onEnable
第一次添加到桌面的时候才会调用。一般用来定义静态变量,以及配置服务。。。
4.onDelete
删除一次调用一次。。不过appwidget里不用像广播一样解除广播。
。。一般业务开展也就在onReceive和onUpdate吧。。。
ok,了解完这些后进入下一步。。点击事件。。不能点击的界面效果就会被大大局限。
appwidget里button,imageView这类单个的点击事件还是简单的。
//onupdate代码
Intent intentClick = new Intent();
intentClick.setAction("waibibabu");
PendingIntent pendingIntent = PendingIntent.getBroadCast(context,0,intentClick,0);
remoteViews.setOnClickPendingIntent(R.id.button1,pendingIntent);
appWidgeManager.updateAppWidget(appWidgetId,remoteViews);
//onReceive代码
switch(intent.getAction){
case "waibibabu":
Log.d("niuniu","hello,world");
break;
}
这样一个简单的点击响应事件就做好了。
接下来就是重重重头,也是我写这篇博客的原因--------listView这类集合型view的点击响应事件。在Android中使用listview不是直接findViewByid然后绑定适配器,就好了嘛。但appwidget里不支持findViewById。。他靠的是两个关键函数setPendingIntentTemplate和setOnClickFillInIntent。
onUpdate里的setPendingIntentTemplate
Intent intent = new Intent(context, MyWidgetService.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
//设置RemoteViews的展示界面
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.app_provider_activity);
//绑定适配器,这里会发送一个intent到MyWidgetService。
views.setRemoteAdapter(R.id.list_1, intent);
首先设一个intent用来绑定provider和RemoteViewsService。。这个RemoteViewsService就是处理listView的item的关键。这里的setRemoteAdapter就是把remoteViews和RemoteViewsService绑定。
Intent clickIntent = new Intent(context,MyWidget.class);
clickIntent.setAction("clickAction");
clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,appWidgetId);
clickIntent.setData(Uri.parse(clickIntent.toUri(Intent.URI_INTENT_SCHEME)));
PendingIntent pendingIntentTemplate = PendingIntent.getBroadcast(context,0,clickIntent,PendingIntent.FLAG_UPDATE_CURRENT);
//item的点击事件
views.setPendingIntentTemplate(R.id.list_1,pendingIntentTemplate);
awm.updateAppWidget(appWidgetId, views);
awm.notifyAppWidgetViewDataChanged(appWidgetId, R.id.list_1);
这里设定了setPendingIntentTemplate。
ps:appwidget里不允许每个item拥有自己的pendingIntent(开销太大)。所以用用另一种方式实现。下面再讲。
RemoteViewsService里的setOnClickFillInIntent
这个类里我们要重写onGetViewFactory函数,它的返回值是RemoteViewsFactory。这个类可以由RemoteViewsService.RemoteViewsFactory得到。
需要重写的关键函数是这个getViewAt:
@Override
public RemoteViews getViewAt(int position) {
log("getViewAt, position=" + position);
RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.layout_adapter);
views.setTextViewText(R.id.name, mFoods[position]);
Intent intent = new Intent();
// 传入点击行的数据
intent.putExtra("content", mFoods[position]);
//这里的点击事件会返回到appWidget,用于处理点击事件
views.setOnClickFillInIntent(R.id.name, intent);
return views;
}
这里的setOnClickFillInIntent怎么跟setPendingIntentTemplate联动呢。。进入源码
setOnClickFillInIntent:
public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) {
RemoteResponse response = new RemoteResponse();
response.mFillIntent = fillIntent;
return response;
}
//这里返回一个response,里面有我们在getViewat写的intent
public void setOnClickResponse(int viewId, @NonNull RemoteResponse response) {
addAction(new SetOnClickResponse(viewId, response));
}
//这个response随风飘摇。
SetOnClickResponse(int id, RemoteResponse response) {
this.viewId = id;
this.mResponse = response;
}//这个response已经和remoteViews绑一块了
private void addAction(Action a) {
if (hasLandscapeAndPortraitLayouts()) {
throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
" layouts cannot be modified. Instead, fully configure the landscape and" +
" portrait layouts individually before constructing the combined layout.");
}
if (mActions == null) {
mActions = new ArrayList<>();
}
mActions.add(a);
}
//这个mActions就是老盆友了。他是一个arraylist对象,里面存着各种apply方法,只要我们调用performApply就能执行每个action对象的apply方法。显然这里把我们对item的操作加了进去。
然后来看看另一个函数setPendingIntentTemplate
public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
}
//这里也有addAction。。。
RemoteResponse response = null;
int childCount = vg.getChildCount();
for (int i = 0; i < childCount; i++) {
Object tag = vg.getChildAt(i).
getTag(com.android.internal.R.id.fillInIntent);
if (tag instanceof RemoteResponse) {
response = (RemoteResponse) tag;
break;
}
}
//里面有一段这样的代码。。意思就是获取response。okkk。这样就搭起桥了。
总结:appwidget上对listview的操作挺麻烦的,但看了源码后收获不小。之前一直苦恼这两个怎么联动的。。其实他们两都用一个action数组。。。。最后不要忘记刷新页面。。。不然列表不会有改变的