转载请注明原创出处,谢谢!
- GitHub: @Ricco
ps:
因业务需求,需要给app增加Widget小组件,网上找了很多文档,文档零零散散,也比较老,所以总结一下
应用 widget 概览
小组件是什么
个人任务小组件就是,自定义在主屏幕上“一目了然”的视图。让用户在主屏幕即可进行访问,查看重要数据和功能。丰富手机可玩性。
小组件类型
- 普通
- 列表(ListView)
- 其他
桌面小组件和负一屏小部件的区别
已小米手机为例,普通开发的小组件是不能放到负一屏的,在开发阶段我们需要对小米进行适配,然后将app上传到小米小部件开发者平台才能进行调试,在正式环境,还需要去审核。
创建简单的widget
声明 AppWidgetProviderInfo XML
AppWidgetProviderInfo 对象定义了 widget 的基本特性。您可以使用单个 <appwidget-provider> 元素在 XML 资源文件中定义 AppWidgetProviderInfo 对象,并将其保存在项目的 res/xml/ 文件夹中。
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:targetCellWidth="1"
android:targetCellHeight="1"
android:maxResizeWidth="250dp"
android:maxResizeHeight="120dp"
android:updatePeriodMillis="86400000"
android:description="@string/example_appwidget_description"
android:previewLayout="@layout/example_appwidget_preview"
android:initialLayout="@layout/example_loading_appwidget"
android:configure="com.example.android.ExampleAppWidgetConfigurationActivity"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"
android:widgetFeatures="reconfigurable|configuration_optional">
</appwidget-provider>
- minWidth 和 minHeight:指定 widget 的默认大小,如果微件的最小宽度或高度的值与单元格的尺寸不匹配,则这些值会向上舍入到最接近的单元格大小。
- updatePeriodMillis:定义微件框架通过调用 onUpdate() 回调方法来从 AppWidgetProvider 请求更新的频率。不能保证实际更新按此值正好准时发生,google建议尽可能降低更新频率(不超过每小时一次),以节省电池电量。不支持小于 30 分钟的值,也就是最小设置为1800000
- initialLayout:指向用于定义微件布局的布局资源。
使用 AppWidgetProvider 类处理微件广播
AppWidgetProvider 类会处理 widget 广播,并响应 widget 生命周期事件来更新 widget。
在清单中声明微件
首先,在应用的 AndroidManifest.xml 文件中声明 AppWidgetProvider 类
<receiver android:name="ExampleAppWidgetProvider"
android:lable="小组件"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/example_appwidget_info" />
</receiver>
- android:lable:调起在 widget 选择器中显示时,widget 需要具有唯一的名称。
实现 AppWidgetProvider 类
AppWidgetProvider 类扩展了 BroadcastReceiver 作为一个辅助类来处理微件广播。它仅接收与微件相关的事件广播,例如当更新、删除、启用和停用微件时发出的广播。
- onUpdate:调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新微件。
- onAppWidgetOptionsChanged:当首次放置微件时以及每当调整微件的大小时,系统都会调用此方法。
- onDeleted:每次从微件托管应用中删除微件时,都会调用此方法。
- onEnabled:首次创建微件实例时,系统会调用此方法。例如,如果用户添加 widget 的两个实例,只有首次添加时会调用此方法。
- onDisabled:从微件托管应用中删除了微件的最后一个实例时,会调用此方法。
- onReceive:针对每个广播调用此方法,并且是在上述各个回调方法之前调用。
public class TestWidget extends AppWidgetProvider {
/**
* 调用此方法可以按 AppWidgetProviderInfo 中的 updatePeriodMillis 属性定义的时间间隔来更新微件。
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
/**
* 当首次放置微件时以及每当调整微件的大小时,系统都会调用此方法。
*/
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
Bundle appWidgetOptions = appWidgetManager.getAppWidgetOptions(appWidgetId);
// 包含微件实例宽度的下限
Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例宽度的下限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH));
// 包含微件实例的高度下限
Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例的高度下限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT));
// 包含微件实例的宽度上限
Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例的宽度上限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH));
// 包含微件实例的高度上限
Log.i("TAG", "onAppWidgetOptionsChanged: 包含微件实例的高度上限 " + appWidgetOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT));
}
/**
* 每次从微件托管应用中删除微件时,都会调用此方法。
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
}
/**
* 首次创建微件实例时,系统会调用此方法。例如,如果用户添加 widget 的两个实例,只有首次添加时会调用此方法。
*/
@Override
public void onEnabled(Context context) {
}
/**
* 从微件托管应用中删除了微件的最后一个实例时,会调用此方法。
*/
@Override
public void onDisabled(Context context) {
}
/**
* 针对每个广播调用此方法,并且是在上述各个回调方法之前调用。
* 要注意,实测!!!会收到appwidgetId为0,从而导致组件更新失败
*/
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) {
updateAppWidget(context, AppWidgetManager.getInstance(context), appWidgetId);
} else {
// 要注意,实测!!!会收到appwidgetId为0,从而导致组件更新失败
Log.i("TAG", "onReceive: 收到appwidgetId为0的广播");
int[] appWidgetIds = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, TestWidget.class));
for (int id : appWidgetIds) {
updateAppWidget(context, AppWidgetManager.getInstance(context), id);
}
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_test);
// ...
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
创建 widget 布局
RemoteViews 仅限于支持以下布局:
AdapterViewFlipper、FrameLayout、GridLayout、GridView、LinearLayout、ListView、RelativeLayout、StackView、ViewFlipper
以及以下小部件:
AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextClock、TextView
自 API 31 起,以下小部件和布局也可以使用:
CheckBox、RadioButton、RadioGroup、Switch
这些类的子类不受支持。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:id="@+id/ll_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/dp_80"
android:orientation="vertical"
android:padding="@dimen/dp_16">
</LinearLayout>
</FrameLayout>
- android:id="@android:id/background"加上这行代码,手机会自动圆角,但是还是建议你给background设置上圆角,解决适配部分手机、低版本没有圆角的问题(三星)
- android:padding="@dimen/dp_16" 更具设计规范,要是是有边距的
- android:minHeight="@dimen/dp_80"更具我结合三方app、并且多轮测试下来,强烈介意采用这种规则适配,要不然你在平板上看很完美,但是手机上会被拖拽拉升的特别大,很难看的。
应用主动刷新刷新
<intent-filter>里加一个action,例如“aaa.test.refresh”;
public static void refreshWidget(Context context) {
try {
Intent intent = new Intent(context, TestWidget.class);
intent.setAction("aaa.test.refresh");
int[] appWidgetIds = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, TestWidget.class));
for (int appWidgetId : appWidgetIds) {
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent broadcast = PendingIntent.getBroadcast(context, appWidgetId, intent, PendingIntent.FLAG_ONE_SHOT);
try {
broadcast.send();
} catch (PendingIntent.CanceledException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
应用主动添加小组件
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName myProvider = new ComponentName(context, ExampleAppWidgetProvider.class);
if (appWidgetManager.isRequestPinAppWidgetSupported()) {
PendingIntent successCallback = PendingIntent.getBroadcast(
context, 0, new Intent(...), PendingIntent.FLAG_UPDATE_CURRENT);
appWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
}
- 各大厂商的手机好像有兼容问题,不能保证都成功