准备工作
app\src\main\res\xml\下新建一个appwidget-provider文件,文件名:test_widget_info
内容如下:
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/test_widget" //widget 布局文件
android:minWidth="300dp" //widget最小宽度
android:minHeight="200dp" // widget最小高度
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000" // 最短刷新时间间隔
android:previewImage="@drawable/widget_preview" //widget选择列表里的预览图
android:widgetCategory="home_screen"></appwidget-provider> // 类型 主屏,还有种是基于锁屏的
androidmanifest文件里注册下widget,可以看出widget是继承自receiver的。
public class AppWidgetProvider extends BroadcastReceiver
我们的widget继承AppWidgetProvider
AndroidManifest配置文件里加上
<receiver android:name=".widget.appwidget.TestWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> // 这个action类似于广播的action
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/test_widget_info" /> //指向我们刚配置的文件
</receiver>
写一个widget继承AppWidgetProvider
public class TestWidget extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
//更新widget会调用这个方法
}
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
//删除widget时调用这个方法
}
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
//widget被第一次添加时会调用这个方法,这个方法应该只会有系统来调用
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
// 最后一个widget被删除时会调用这个方法
}
}
简单看下AppWidgetProvider里onReceive方法
public void onReceive(Context context, Intent intent) {
// Protect against rogue update broadcasts (not really a security issue,
// just filter bad broacasts out so subclasses are less likely to crash).
String action = intent.getAction();
if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds != null && appWidgetIds.length > 0) {
this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
}
}
} else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
this.onDeleted(context, new int[] { appWidgetId });
}
} else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
&& extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
appWidgetId, widgetExtras);
}
} else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
this.onEnabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
this.onDisabled(context);
} else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
Bundle extras = intent.getExtras();
if (extras != null) {
int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (oldIds != null && oldIds.length > 0) {
this.onRestored(context, oldIds, newIds);
this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
}
}
}
}
具体UI相关的。
widget的view还比较特殊,叫RemoteViews
跟我们常见的view集成viewgroup不同,它继承的是Parcelable, Filter
自然不能用findviewbyid之类的,代码里动态设置布局什么的都不太好搞了
但官方也给我们开放了一些接口用来设置控件。
基础的 设置textview,imageview
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.test_widget); //test_widget.xml 就是widgetview的布局文件
views.setTextViewText(R.id.tv, "helloworld");
views.setImageViewResource(R.id.iv,R.drawable/test_iv);
设置点击事件
Intent intent = new Intent(context, NewActivity.class);
PendingIntent pIntent = PendingIntent.getActivity(context, UUID.randomUUID().hashCode(),
upload, PendingIntent.FLAG_UPDATE_CURRENT);//widget如果有多个点击事件需要这么写,否则点击事件只响应最先设置的那个
views.setOnClickPendingIntent(R.id.btn, pIntent);
结束语:
基础的东西大概就这么多了。
个人觉得remoteview需要仔细研究下源码,不然没法使用一些图片加载框架,很不方便
另外widget也不支持constraint-layout,(手头华为,三星,vivo,小米都试了不行),很不美好
强上后报的异常日志
Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.constraint.ConstraintLayout" on path: DexPathList[[zip file "/system/app/HwLauncher6/HwLauncher6.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
at java.lang.ClassLoader.loadClass(ClassLoader.java:511)
at java.lang.ClassLoader.loadClass(ClassLoader.java:469)
at android.view.LayoutInflater.createView(LayoutInflater.java:590)
at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:762)
at android.view.LayoutInflater.inflate(LayoutInflater.java:501)
at android.view.LayoutInflater.inflate(LayoutInflater.java:425)
at android.widget.RemoteViews.apply(RemoteViews.java:2636)
at android.appwidget.AppWidgetHostView.updateAppWidget(AppWidgetHostView.java:401)
at android.appwidget.AppWidgetHost.createView(AppWidgetHost.java:325)
at com.huawei.android.launcher.Launcher.addWidgetToScreen(Launcher.java:3053)
at com.huawei.android.launcher.Launcher.completeAddAppWidget(Launcher.java:3118)
at com.huawei.android.launcher.Launcher.addAppWidgetImpl(Launcher.java:4056)
at com.huawei.android.launcher.Launcher.addAppWidgetFromDrop(Launcher.java:4130)
at com.huawei.android.launcher.Workspace.addPendingAddItemInfoToDesktop(Workspace.java:9914)
at com.huawei.android.launcher.Workspace$9.run(Workspace.java:5676)
at com.huawei.android.launcher.DragLayer$3.onAnimationEnd(DragLayer.java:982)
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1171)
at android.animation.ValueAnimator$AnimationHandler.doAnimationFrame(ValueAnimator.java:722)
at android.animation.ValueAnimator$AnimationHandler.run(ValueAnimator.java:738)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:799)
at android.view.Choreographer.doCallbacks(Choreographer.java:612)
at android.view.Choreographer.doFrame(Choreographer.java:580)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:785)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5593)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:967)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:762)
Suppressed: java.lang.ClassNotFoundException: android.support.constraint.ConstraintLayout
at java.lang.Class.classForName(Native Method)
at java.lang.BootClassLoader.findClass(ClassLoader.java:781)
at java.lang.BootClassLoader.loadClass(ClassLoader.java:841)
at java.lang.ClassLoader.loadClass(ClassLoader.java:504)
... 30 more