MIUI Widget 数据恢复适配
5.1.描述
Android 系统提供了 onRestored 方法用于数据从云端备份恢复时的 Widget 配置迁移。在此基础之上,MIUI Widget 新增了一个 Widget 配置迁移的时机。开发者无需关心何时回调,只需将新的WidgetId与数据绑定并更新UI。
注意:当 MIUI Widget 卡片有配置信息且存在多张卡片的配置信息不一致时需要进行相应适配(例如股票卡片,用户可以添加多张卡片,且每张卡片展示不同的股票),其他情况无需适配。
5.2.示例
public class ExampleWidgetProvider extends AppWidgetProvider {
@Override
public void onRestored(Context context, int[] oldWidgetIds, int[]
newWidgetIds) {
super.onRestored(context, oldWidgetIds, newWidgetIds);
onIdRemap(oldWidgetIds, newWidgetIds, null);
}
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager
appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOpt网站封装ipaionsChanged(context, appWidgetManager,
appWidgetId, newOptions);
// MIUI Widget 新增配置迁移时机
if (newOptions.getBoolean("miuiIdChanged") &&
!newOptions.getBoolean("miuiIdChangedComplete")) {
onIdRemap(newOptions.getIntArray("miuiOldIds"),
newOptions.getIntArray("miuiNewIds"), newOptions);
}
}
private void onIdRemap(int[] oldWidgetIds, int[] newWidgetIds, Bundle options) {
int length = oldWidgetIds.length;
for (int i = 0; i < length; i++) {
int newWidgetId= newWidgetIds[i];
RemoteViews views = new RemoteViews(context.getPackageName(),
R.layout.appwidget_provider_layout);
//开发者进行数据迁移,并完成新的数据获取
...
//以上操作完成后,先调用updateOptions,再调用updateAppWidget
if(options != null) {
//这一步必须执行
options.putBoolean("miuiIdChangedComplete", true);
AppWidgetManager.getInstance(context)
.updateAppWidgetOptions(newWidgetId, options);
}
AppWidgetManager.getInstance(context)
.updateAppWidget(newWidgetId, views);
}
}
}
6.页面跳转规范
6.1.描述
点击MIUI Widget 跳转应用页面时,推荐使用 PendingIntent 设置相应的 Activity 进行跳转。若业务有分发逻辑可以使用Activity进行中转。
不建议使用PendingIntent 启动BroadcastReceiver/Service,然后在BroadcastReceiver/Service里面启动 Activity。
6.2.示例
// step1: 构建跳转页面的 PendingIntent
Intent intent = new Intent(context, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// step2: 生成 RemoteViews 关联 PendingIntent
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.appwidget_provider_layout);
views.setOnClickPendingIntent(R.id.button, pendingIntent);
// step3: 关联 widget 和 RemoteViews
appWidgetManager.updateAppWidget(appWidgetId, views);
7.MIUI Widget 布局规范
7.1.描述
系统通过固定的ID找到相应控件并添加圆角,保证所有MIUI Widget圆角统一。因此开发者需要在小部件的根布局上声明ID为”@android:id/background”。由于MIUI Widget 切换页面的动画使用到了背景色,因此根布局
必须有背景色且不能为全透明。
7.2.示例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
// 颜色值为示例
android:background="#fff"
android:id="@android:id/background">
...
</LinearLayout>
8.MIUI Widget 布局兼容适配
8.1.描述
为了让用户有更好的体验,MIUI Widget 需要保证以下场景正常显示:
- 负一屏、默认布局下的桌面正常显示
- 桌面图标行列数变化后正常显示 需保证桌面4×6、5×6网格体系都能正常显示(设置方式:设置—桌面—桌面布局)
- 桌面搜索框变化时正常显示 需保证有搜索框、无搜索框都能正常显示(设置方式:设置—桌面—桌面搜索框)
- 桌面虚拟键变化时正常显示 需保证有虚拟键、无虚拟键都能正常显示(设置方式:设置—桌面—系统导航方式)
- 1k、2k屏幕手机都能正常显示
为了使卡片在各种场景下都有较好的展现,需要做以下适配:
- MIUI Widget 根布局宽高必须使用match_parent, 内容区需要在根布局里居中
- 内容区控件尽量不要使用绝对尺寸和绝对位置,可以使用RelativeLayout以及LinearLayout的layout_weight等控制控件的位置和尺寸
8.2.示例
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
<!--widget_root_padding 与设计图保持一致,如示例图为40px-->
android:padding="@dimen/widget_root_padding"
android:gravity="center">
<!--内容区-->
<LinearLayout
android:id="@+id/widget_content"
android:layout_width="match_parent"
android:orientation="vertical"
android:layout_height="match_parent">
</LinearLayout>
</FrameLayout>
9.应用清除数据适配
9.1.描述
用户在使用过程中可能存在清除应用数据的行为,用户清除数据时MIUI系统会给 MIUI Widget 发送刷新广播,此时应用处在无数据或未授权状态,开发者需要在该时机将小部件恢复成默认视图或授权视图。
9.2.示例
<receiver android:name="com.miui.ExampleWidgetProvider" >
...
// MIUI Widget 标识
<meta-data
android:name="miuiWidget"
android:value="true" />
<intent-filter>
//MIUI展现刷新
<action android:name="miui.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>
public class ExampleWidgetProvider extends AppWidgetProvider {
...
@Override
public void onReceive(Context context, Intent intent) {
if ("miui.appwidget.action.APPWIDGET_UPDATE".equals(intent.getAction())) {
int[] appWidgets = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
// 根据应用自身逻辑更新视图
...
} else {
super.onReceive(context, intent);
}
}
}
10.小部件版本号
10.1.描述
MIUI小部件增加版本号,方便小部件版本迭代管理。小部件版本号针对应用内所有小部件,而非单个。当应用内任一小部件发生功能变动或新增小部件时需升级小部件版本号。若小部件较前一应用版本未发生变动,则无需更改小部件版本号。
注意:
- 名称为”miuiWidgetVersion” 的meta-data标签,应放置在application下而不是某个小部件provider下。
- 小部件发生变动或新增时一定要修改小部件版本,否则存在应用所有小部件被下线的风险。
- 小部件版本号为整数,从1开始,同应用版本号的升级一致,都只能升高。
10.2.示例
AndroidManifest.xml 文件参考如下配置:
<application
...
<meta-data
android:name="miuiWidgetVersion"
android:value="1" />
...
</application>
11.MIUI Widget 大屏适配
11.1.描述
为了使MIUI Widget可以在大屏设备(折叠屏,平板)上提供更好的用户体验,需要对MIUI Widget在大屏幕设备上进行适配。
11.2.编辑页适配
若开发者在MIUI Widget中设置了编辑页,大屏设备上适配后的展示效果如下图所示:
适配方法:
- 对于小部件视图树上,所有可以通过点击调起编辑页面的视图控件,需要在代码中进行以下调用:
public class ExampleWidgetProvider extends AppWidgetProvider {
...
@Override
public void onUpdate(Context context, AppWidgetManager
appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_ui);
// 编辑页适配
val widgetOptions = AppWidgetManager.getInstance(context).getAppWidgetOptions(appWidgetId)
//由host提供,业务直接读取,以判断当前设备是否支持大屏预览模式
if (widgetOptions != null && widgetOptions.getBoolean("miuiLargeScreenDevice", false)) {
//如果当前设备支持大屏预览模式,则先生成一个Bundle实例,再分别执行步骤1,2
val largeScreenOptions = Bundle().apply {
//1. 传入widgetId
putInt("miuiWidgetId", widgetId)
}
//2. 通过setBundle方法,调用控件的supportLargeScreenEditPreviewMode方法,并传入以上生成的Bundle
//R.id.item_container_1 为一个示例,所有点击事件为调起编辑页的控件,都必须进行类似的调用
remoteViews.setBundle(R.id.item_container_1, "supportLargeScreenEditPreviewMode", largeScreenOptions)
}
appWidgetManager.updateAppWidget(widgetId, views);
}
....
}
}
- 如果业务小部件有长按编辑入口,则在生成编辑页面链接时,需要新增miuiWidgetId参数:
//示例:
public class ExampleWidgetProvider extends AppWidgetProvider {
@Override
public void onUpdate(Context context, AppWidgetManager
appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
Bundle options =
appWidgetManager.getAppWidgetOptions(appWidgetId);
String path =
"widgetdemo://com.miui.widgetdemo/widget/WidgetEditActivity";
Uri.Build uriPath = Uri.parse(path).buildUpon();
//本次新增参数,host依据此参数来判断,业务编辑页是否支持大屏预览模式
uriPath.appendQueryParameter("miuiWidgetId", widgetId.toString());
options.putString("miuiEditUri", uriPath);
appWidgetManager.updateAppWidgetOptions(appWidgetId, options);
RemoteViews views =
new RemoteViews(context.getPackageName(),
R.layout.widget_ui);
appWidgetManager.updateAppWidget(widgetId, views);
}
....
}
}
- 业务侧在编辑页面Activity的onCreate方法中,需要从Intent中解析对应参数,判断是否以大屏预览模式展示,并对大屏预览模式下的编辑页面进行改造:
public class WidgetEditActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
val intent = getIntent()
//是否以大屏预览模式展示
if (intent.getBooleanExtra("isLargeScreenMode", false)) {
//该字段返回一个矩形区域,代表小部件在屏幕坐标系上的具体位置
val rect = intent.getParcelableExtra("miuiWidgetScreenBound")
if (rect != null) {
//业务需要根据rect提供的小部件位置信息,来计算业务内容视图的具体展示位置:
//1、业务内容视图应当始终展示在小部件的左侧,或者右侧,距离小部件固定间距
//2、业务内容视图应当在小部件的左侧和右侧中,选择空间较大的一侧展示
}
}
}
}
需要注意的事项:
- 不要在生成PendingIntent时,put自定义的Parcelable对象或者Serializable对象,这样会导致Host处理intent时因找不到类而抛异常。请转换成基础数据类型进行传递。
- 请不要在生成PendingIntent时,添加FLAG_IMMUTABLE,否则业务将无法解析到Host添加的参数,android s以上可以换成FLAG_MUTABLE。
- 编辑页面打开时,不允许横竖屏旋转(需要支持横竖屏,只是不允许动态旋转)。
- 业务只需要负责编辑页面内容区域(如上图示中的白色背景覆盖区域)的展示,背景高斯以及小部件的预览图部分,均由Host实现。
11.3.布局适配
在大屏设备上,负一屏和桌面对业务小部件布局进行了全局缩放,因此一般情况网页生成APP下,不需要业务再对小部件进行额外适配。
如果业务希望自行对小部件进行更完美,精细的适配,可以选择将全局缩放能力关闭,只需要在小部件对应的provider里,加上如下所示的配置即可
<receiver
android:name=".service.normal.widget.NormalExampleWidgetProvider">
<meta-data
android:name="miuiAutoScale"
android:value="false"/>
</receiver>
提示: 对于使用了GridView或者ListView的小部件,该方法不能使用,如遇到问题,请联系相关技术支持。
12.注意事项
12.1.圆角
MIUI Widget 虽然会在MIUI13上统一裁切圆角,但为了能够兼容旧版系统和非MIUI 手机,开发者仍需为MIUI Widget添加圆角 ,圆角大小与设计规范保持一致。
12.2.刷新兼容
曝光刷新仅存在支持MIUI Widget的系统中(可通过“MIUI 小部件系统能力说明–是否支持MIUI Widget”判断),在不支持MIUI Widget 的系统以及非MIUI手机上,MIUI Widget刷新机制与原生Widget保持一致,各项目需要根据原生刷新机制以及业务需求进行适配。
12.3.Activity 进程
系统会对 Widget 进程进行优化,系统资源紧张时,Widget 进程容易被回收。为了保证 Activity 的正常显示,Activity所在进程不能是Widget 进程。
12.4.小部件名称
MIUI Widget必须配置小部件名称。开发者可以根据每个小部件的功能,为小部件撰写一个简洁的名字(2~8个汉字),帮助用户理解小部件的用途。在小部件中心里,小部件名称会展示为“应用名称·小部件名称”,为了展示体验更佳,小部件名称与应用名称不得相同。小部件名称对应代码AndroidManifest.xml中receiver的label。设置方法如下:
<receiver
android:name="com.miui.widgetdemo.provider.ExampleWidgetProvider"
android:label="@string/app_widget_example"
android:process=":widgetProvider">
...
</receiver>
12.5.深色模式
MIUI Widget 只能在xml中静态适配深色模式(通过配置drawable-night、values-night等资源文件适配),不支持在RemoteViews通过代码动态设置深色模式。
12.6.禁止随意修改 MIUI Widget的receiver类名
Android系统会根据receiver类名标识 MIUI Widget。一旦改名,用户在旧版添加的小部件升级后就会消失。因此 MIUI Widget 一旦审核通过,禁止随意修改receiver类名。
12.7.小部件通过审核后,禁止随意移除 MIUI Widget 标识
通过审核的小部件移除MIUI Widget 标识,会导致系统无法识别,造成一些严重的错误,因此小部件一旦审核通过,禁止随意移除MIUI Widget 标识。
编辑:yimen,如若转载,请注明出处:https://www.yimenapp.com/kb-yimen/12406/
部分内容来自网络投稿,如有侵权联系立删