【HarmonyOS 7 闪控窗深度实战】04 闪控窗调整窗口形态与显示区域
前言
任务名称、百分比和操作按钮一起进入闪控窗以后,窗口需要容纳的信息明显增加。用户暂停或取消整理时,需要找到完整的操作区域;用户只想确认进度时,同样大小的窗口又可能挡住其他页面。两种使用目的对空间的要求不同,应用需要在完整控制和简洁查看之间作出安排。缩小外框如果没有同时减少内容,按钮仍然可能被挤到看不见或点不到的位置。
空间问题还包含系统窗口与应用页面之间的边界。窗口外框占据的区域,并不全部交给应用处理手势,标题栏等系统区域也位于其中。因此,画面上能够看到一个按钮,不足以说明这个按钮所在位置能够收到点击。应用需要先知道窗口实际有多大,再确认哪些区域应当避让,文字布局和交互位置才能使用同一组有依据的数据。
HarmonyOS 7 为闪控窗提供圆角矩形和水平条状模板,并允许应用查询模板限制、请求调整尺寸和监听矩形变化。系统处理窗口大小与形态,应用根据业务内容选择请求值,再读取实际结果安排 ArkUI 页面。不同模板的限制并不相同,模板切换后也不能沿用原来的一组宽高。系统提供这些窗口能力以后,工程需要新增的工作就是把模板、尺寸条件和内容取舍连接起来。
请求尺寸与实际显示大小需要分别检查。尺寸接口接收像素值,ArkUI 布局还会使用 vp,窗口属性中又包含缩放和避让区域。开发者需要先确认每个数值描述什么,再进行单位换算,避免把外框宽高直接当作可以放置按钮的空间。
当前任务只有一项,完整形态保留名称、进度条和控制按钮,紧凑形态只保留状态与百分比。应用调整形态时继续使用原任务,进度不会重新开始。尺寸策略因此可以从用户需要完整操作还是简洁查看出发,再查询目标模板的限制,为相应内容安排空间。

一、按照模板选择尺寸,再比较系统返回的矩形
完整操作需要容纳按钮,简洁查看则可以减少内容。两种需求都受到模板尺寸限制,所以应用需要先选择形态,再决定请求的宽高。尺寸选择完成以后,应用还要读取系统最终采用的矩形,判断页面拥有多少显示空间。
尺寸策略需要从模板开始。当前 API 26 提供 ROUNDED_RECTANGLE 和 HORIZONTAL_BAR,分别表示圆角矩形和水平条状矩形。两个模板的限制不同,应用不能读取圆角矩形的宽高,再直接当作水平条状模板的有效范围。
当前代码在选择尺寸前查询模板限制,没有固定一组适用于所有设备的宽高。设备密度和可用屏幕区域变化以后,相同像素大小可能呈现不同的视觉效果。因此,即使设计稿已经给出尺寸,应用仍然需要结合当前限制检查宽高与比例。
getFloatViewLimits() 接收模板类型,返回最小尺寸、最大尺寸和宽高比范围。尺寸使用像素,宽高比由宽除以高得到。应用需要同时检查这些条件,宽度和高度分别落在范围内,并不必然保证两者组合后的比例符合要求。
const limits = floatView.getFloatViewLimits(this.template);
const size: window.Size = small ? limits.minSize : limits.maxSize;
主页面提供紧凑尺寸和完整尺寸两个按钮,分别使用返回的最小与最大尺寸。它们用于比较同一任务在两种空间条件下的表现,并不表示产品只能选择这两个极端。实际产品可以在限制范围内选择中间尺寸,再根据最长状态文字和操作区域检查是否合适。
圆角矩形与水平条状模板允许的空间不同,条状布局尤其需要控制内容高度。应用切换模板以后,会重新读取目标模板的限制,再使用它的尺寸。原模板的宽高不能直接沿用,否则请求可能不满足目标模板的有效范围。
显示条件变化也可能使限制更新,应用通过 onLimitsChange() 接收新的范围。当前实现保存这些信息,由用户再次选择尺寸,没有在回调中自动决定业务布局。产品如果需要随屏幕变化自动调整,还需要明确内容取舍与尺寸重算规则。
开发者选择中间尺寸时,可以先按业务需要确定宽度,再结合内容高度检查比例。只把宽度限制到最大值、把高度限制到最小值,可能得到不合适的组合,因为两个独立边界并没有表达内容如何排列。当前端点按钮便于观察系统返回范围,实际产品仍然需要把名称长度、状态文字和按钮数量带入尺寸选择。
比例限制以数组形式返回,每项包含最小与最大比例。代码不应该假设所有设备永远只有一个连续区间,也不应该只读取数组第一项后丢弃其余条件。当前实现直接采用接口返回的端点,没有实现任意尺寸搜索算法;开发者增加自由尺寸输入时,需要明确选择的比例落在哪个有效区间,并对无效输入提供反馈。
窗口大小和内容密度也需要分别调整。单纯缩小窗口而保留全部内容,会使滚动范围增长;单纯隐藏内容而保持大窗口,又没有减少对其他应用的遮挡。当前紧凑标记与尺寸请求共同变化,分别控制内容取舍和外部大小。这个设计让用户能够明确选择查看简洁状态还是操作完整任务,不依赖系统替业务猜测应该隐藏哪个按钮。
应用通过 setWindowSize() 请求窗口大小,宽高必须大于零,无效参数可能返回 1300016。开发者不能假定系统会自动修正所有超出推荐范围的输入。请求完成以后,应用继续通过 onRectChange() 取得实际矩形,判断系统最终采用的尺寸。
console.info('FloatTask04 request=' + JSON.stringify(size));
await this.controller.setWindowSize(size);
系统处理尺寸请求以后,矩形回调会提供窗口实际结果。开发者需要把回调中的宽高与请求使用的模板一起检查,不能只根据请求方法成功返回就认定布局符合预期。窗口还可能因为系统安排或其他操作发生变化,因此每次请求与回调也不能简单理解为固定的一一对应。
回调包含 windowRect、windowScale 和变化原因。windowRect 说明窗口位置与宽高,windowScale 单独说明窗口缩放比例。开发者比较请求尺寸时,需要保留这两个字段,不能把缩放后的视觉大小与矩形宽高直接当作同一种数据。
当前实现同时保留尺寸请求和实际矩形,用来区分应用选择与系统结果。应用请求有效端点以后,可以根据返回宽高判断请求是否生效,再检查内容是否容纳得下。关系表说明不同数据各自解决什么问题,开发者排查时可以据此选择需要读取的字段。
| 需要回答的问题 | 使用的数据 | 对布局的影响 |
|---|---|---|
| 目标模板允许多大窗口 | minSize、maxSize、宽高比范围 | 约束应用请求的宽高组合 |
| 系统最终采用多大窗口 | onRectChange() 中的 windowRect | 确认实际窗口范围 |
| 视觉大小是否受缩放影响 | windowScale | 避免混用矩形尺寸与缩放后的大小 |
| 哪些位置不应放置操作 | avoidArea | 为系统保留区域安排留白 |
模板限制约束应用请求,实际矩形说明系统结果,避让区域则影响业务控件的位置。应用调整窗口时继续使用同一个任务,所以进度变化不会来自任务重新创建。窗口停止后再次建立时,当前实现恢复圆角矩形与完整内容,避免旧的条状标记影响新窗口。
窗口尚未进入可查询状态时,应用也可能收到矩形回调。getWindowProperties() 对窗口状态有要求,尚未启动、已经停止或错误状态不能随意读取。当前属性方法捕获异常并记录等待状态,收到 STARTED 后再次读取,避免一条过早回调中断整个窗口流程。
返回矩形中的位置不由当前尺寸按钮直接设置。窗口在调整大小时仍可能受到屏幕空间影响,因此位置变化也值得保留,不能只挑出宽高两项。用户认为窗口突然移动时,开发者需要知道改变的是位置、尺寸还是缩放,再决定检查系统安排还是应用布局。当前没有持久化窗口坐标,也不会把本次坐标写成下次启动的固定位置。
二、把系统避让区域留给标题栏与手势
窗口尺寸已经确定以后,按钮仍然可能无法点击,因为系统在窗口内部保留了一部分交互区域。应用需要读取这些区域,再给业务控件留下空间。否则,即使文字和按钮完整绘制,点击也可能由系统处理,无法进入任务方法。
窗口标题栏、图标等系统内容可能占用窗口内部区域,外框宽高不能直接代表业务可交互空间。getWindowProperties() 返回的 avoidArea 描述需要避让的矩形,应用可以据此安排内容。页面使用这些数据时还要注意单位,系统返回像素,布局留白需要按当前上下文换算。
当前实现读取顶部和底部避让矩形的高度,换算为 vp 后与业务最小留白比较。横向留白仍固定为 12vp,没有根据左右避让矩形计算,因此条状模板的横向适配还不完整。紧凑形态虽然隐藏了按钮,较长的状态文字仍然可能受到左右系统区域影响;应用增加横向控件以前,需要补齐这部分计算。
const props = this.controller.getWindowProperties();
console.info('FloatTask04 properties=' + JSON.stringify(props));
runtime.storage.set<number>('topInset', Math.max(12, this.getUIContext().px2vp(props.avoidArea.topRect.height)));
runtime.storage.set<number>('bottomInset', Math.max(16, this.getUIContext().px2vp(props.avoidArea.bottomRect.height)));
系统避让区域可能随模板和显示条件变化,应用不能把某次返回的高度固定写成所有窗口的标题栏高度。当前代码每次读取属性后更新上下留白,页面随后使用这些结果布局。开发者调整模板时,需要继续读取属性,确认旧的留白是否仍然适用。
最小业务留白保证系统返回空避让矩形时,内容仍与边缘保持基本间距。系统避让值来自窗口属性,业务留白来自当前布局选择,因此两种数值需要分别理解。调整业务留白可以改变内容密度,但不能用零留白忽略系统真实占用的区域。
当前像素换算使用主页面的 UIContext,适用范围是两处窗口位于同一显示屏的实现。不同显示屏可能具有不同密度,窗口缩放也会影响对坐标的理解。应用涉及跨屏或非 1 缩放时,需要重新核对实际坐标关系,当前计算没有覆盖这些条件。
页面外层使用滚动容器,完整内容中的按钮允许换行。信息超过可见高度时,用户可以滚动查看;窗口较宽时,按钮仍然并排排列。这两项改动处理内容容纳问题,系统避让留白处理事件接收问题,开发者需要分别观察文字是否被裁剪和按钮是否能够响应。

内容能够滚动以后,应用仍然需要让用户知道哪里可以继续操作。窗口很小、内容很多时,用户未必知道下面还有按钮,因此当前紧凑形态直接减少内容,不要求用户在条状区域内寻找完整操作区。完整形态允许滚动则是为了容纳正常字号下的内容增长,不能被解释成任意小窗口都能容纳全部功能。开发者增加新按钮时,应该重新确认它是否属于随时需要的操作,还是可以留在主页面。
系统字号放大以后,原本一行的状态文字可能换行,按钮也需要更多宽度。滚动与换行能够为内容增长提供空间,但它们不能保证任何字号下都能完成操作。开发者仍然需要检查最长文字是否被截断,以及滚动后按钮是否避开系统保留区域。
三、条状窗口减少内容,完整窗口保留操作
可交互区域清楚以后,页面还需要决定有限空间中保留哪些内容。水平条状模板高度较小,继续显示完整操作区会挤占状态文字的位置。因此,当前条状形态承担简洁查看,任务操作保留在完整形态和主页面,用户切换显示方式后仍有明确的操作入口。
当前紧凑形态只保留状态与百分比,完整形态增加名称、进度条和操作区。这个取舍来自单任务的查看需求,应用没有让系统自动决定隐藏哪个按钮。需要暂停或取消时,用户可以恢复完整尺寸或返回主页面,因此内容精简没有同时删除业务操作路径。
模板切换只修改窗口呈现,原来的任务对象继续维护进度。页面根据 compact 决定控件是否出现,不在形态变化时初始化业务。用户因此可以先缩小窗口查看进展,再恢复完整形态操作同一任务。
switchTemplate() 同时接收目标模板与尺寸。应用读取目标模板最大尺寸后调用,再更新当前模板标记与紧凑内容标记。窗口状态没有因此变成新的任务状态,任务进度继续使用同一份存储。
await this.controller.switchTemplate({ templateType: next, size: limits.maxSize });
this.template = next;
runtime.storage.set<boolean>('compact', next === floatView.FloatViewTemplateType.HORIZONTAL_BAR);
切换到水平条状模板以后,页面隐藏名称、进度条和操作区,只显示状态与百分比。目标模板的尺寸来自重新查询的限制,页面内容也同步采用紧凑形式。任务仍然使用原存储,显示形态改变不会重置已经完成的进度。

条状内容减少以后,应用仍然需要考虑最长状态文字与左右避让区域。当前只有一项任务,状态与百分比能够表达基本进展;多个任务同时存在时,页面还需要提供可以区分任务的标识。单任务下的内容选择不能直接覆盖多任务查看需求。

用户恢复完整形态以后,页面重新显示名称、进度条和操作按钮。控件读取原来的任务状态,所以任务在紧凑期间已经完成时,完整窗口也会显示完成结果。页面恢复的是操作区域,任务本身不会因为控件重新出现而再次开始。
当前尺寸调整方法在请求前写入 compact,模板切换则在请求成功后更新显示标记。尺寸请求失败时,页面可能已经变成紧凑内容,外部窗口却没有完成调整。开发者后续完善异常处理时,可以考虑在请求成功后再更新标记,或在失败时恢复原标记;当前代码尚未实现这项改进。
四、根据实际内容决定适配范围
窗口已经能够切换形态以后,尺寸选择还需要回到用户要完成的操作。完整形态应该容纳暂停、恢复和取消,紧凑形态应该让状态与百分比可以辨认。应用采用滚动、换行和内容精简处理这两种需求,但具体尺寸仍然需要结合最长文字、系统区域和设备显示条件判断。
开发者选择默认尺寸时,可以检查首次显示是否遮挡主页面的关键操作,再判断小窗口中的内容是否仍然可用。当前采用最大尺寸容纳多个按钮,这个选择符合完整控制的需要,也占用更多屏幕空间。产品如果主要提供进度查看,可以在有效范围内选择较小尺寸,同时把完整操作保留在主页面。
当前上下留白已经使用实际避让高度,横向仍然采用固定留白,因此应用还没有覆盖所有方向的系统区域。多屏、缩放与大字号也会改变内容的实际空间。开发者扩展这些条件时,需要同时检查单位换算和控件布局,不能只确认尺寸接口成功。
总结
当前应用能够根据模板限制选择窗口大小,并在完整操作与紧凑查看之间切换。窗口回调提供实际矩形,属性提供系统避让区域,页面据此安排上下留白。页面始终读取原有任务状态,用户调整窗口不会重新开始整理。
尺寸可调以后,用户还会拖动、暂存或关闭窗口,这些动作影响的是显示入口。应用需要继续区分窗口是否可见与任务是否运行,避免一次布局调整结束原来的处理。当前布局仍需补充横向避让、多屏和不同缩放条件,默认大小也应随业务内容重新选择。
我目前手里还没有可以测试 HarmonyOS 7 的真机,所以相关内容现阶段主要通过 HarmonyOS 7 模拟器进行验证,真机上的系统表现、设备差异和实际体验,后面有条件再继续补测,最终还是以实际设备运行结果为准。
完整代码
FloatViewPage.ets
import { runtime } from './Index';
@Entry({ useSharedStorage: true })
@Component
struct FloatViewPage {
@LocalStorageLink('taskState') taskState: string = '尚未开始';
@LocalStorageLink('progress') progress: number = 0;
@LocalStorageLink('compact') compact: boolean = false;
@LocalStorageLink('topInset') topInset: number = 48;
@LocalStorageLink('bottomInset') bottomInset: number = 24;
build() {
Scroll() {
Column({ space: 8 }) {
if (!this.compact) { Text('文件整理').fontSize(22).fontWeight(FontWeight.Bold) }
Text(this.taskState + ' ' + this.progress + '%').fontSize(this.compact ? 12 : 20)
if (!this.compact) {
Progress({ value: this.progress, total: 100 }).width('80%')
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) {
Button('暂停').enabled(this.taskState === '正在整理文件').onClick(() => runtime.pause()).margin(4)
Button('恢复').enabled(this.taskState === '已暂停').onClick(() => runtime.resume()).margin(4)
Button('取消').enabled(this.taskState === '正在整理文件' || this.taskState === '已暂停')
.onClick(() => runtime.cancel()).margin(4)
}
}
}.width('100%').padding({ top: this.topInset, bottom: this.bottomInset, left: 12, right: 12 })
}.width('100%').height('100%')
}
}
Index.ets
import { floatView, window } from '@kit.ArkUI';
import { abilityAccessCtrl, common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
class TaskRuntime {
readonly storage: LocalStorage = new LocalStorage();
private timer: number = -1;
constructor() {
this.storage.setOrCreate<string>('taskState', '尚未开始');
this.storage.setOrCreate<number>('progress', 0);
this.storage.setOrCreate<boolean>('compact', false);
this.storage.setOrCreate<number>('topInset', 48);
this.storage.setOrCreate<number>('bottomInset', 24);
}
start(): void {
if (this.timer !== -1 || this.storage.get<string>('taskState') === '已暂停') { return; }
this.storage.set<number>('progress', 0);
this.storage.set<string>('taskState', '正在整理文件');
this.startTimer();
}
pause(): void {
if (this.storage.get<string>('taskState') !== '正在整理文件') { return; }
this.dispose();
this.storage.set<string>('taskState', '已暂停');
console.info('FloatTask04 action=pause progress=' + this.storage.get<number>('progress'));
}
resume(): void {
if (this.storage.get<string>('taskState') !== '已暂停') { return; }
this.storage.set<string>('taskState', '正在整理文件');
this.startTimer();
}
cancel(): void {
const state = this.storage.get<string>('taskState');
if (state !== '正在整理文件' && state !== '已暂停') { return; }
this.dispose();
this.storage.set<string>('taskState', '已取消');
console.info('FloatTask04 action=cancel progress=' + this.storage.get<number>('progress'));
}
private startTimer(): void {
if (this.timer !== -1) { return; }
this.timer = setInterval(() => {
const progress = Math.min(100, (this.storage.get<number>('progress') ?? 0) + 1);
this.storage.set<number>('progress', progress);
console.info('FloatTask04 progress=' + progress);
if (progress === 100) {
this.storage.set<string>('taskState', '文件整理完成');
this.dispose();
}
}, 1000);
}
dispose(): void {
if (this.timer !== -1) { clearInterval(this.timer); this.timer = -1; }
}
}
export const runtime: TaskRuntime = new TaskRuntime();
@Entry(runtime.storage)
@Component
struct Index {
@LocalStorageLink('taskState') taskState: string = '尚未开始';
@LocalStorageLink('progress') progress: number = 0;
@State windowState: string = '尚未启动';
@State supported: boolean = false;
@State busy: boolean = false;
private template: floatView.FloatViewTemplateType = floatView.FloatViewTemplateType.ROUNDED_RECTANGLE;
private controller: floatView.FloatViewController | undefined = undefined;
aboutToAppear(): void {
this.supported = canIUse('SystemCapability.Window.SessionManager') && floatView.isFloatViewEnabled();
console.info('FloatTask04 capability=' + this.supported);
}
async openFloat(): Promise<void> {
if (this.busy || this.controller) { return; }
if (!this.supported) { this.windowState = '当前设备不支持闪控窗'; return; }
this.busy = true;
try {
const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
const result = await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context,
['ohos.permission.FLOAT_VIEW']);
console.info('FloatTask04 permission=' + JSON.stringify(result.authResults));
if (result.authResults[0] !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
this.windowState = '闪控窗权限未授权'; return;
}
const controller = await floatView.create({ context: context,
templateType: floatView.FloatViewTemplateType.ROUNDED_RECTANGLE });
this.controller = controller;
this.template = floatView.FloatViewTemplateType.ROUNDED_RECTANGLE;
runtime.storage.set<boolean>('compact', false);
controller.onRectChange((info: floatView.FloatViewRectChangeInfo) => {
console.info('FloatTask04 rect=' + JSON.stringify(info));
this.readProperties();
});
controller.onLimitsChange((limits: floatView.FloatViewLimits) => {
console.info('FloatTask04 changedLimits=' + JSON.stringify(limits));
});
controller.onStateChange((info: floatView.FloatViewStateChangeInfo) => {
this.windowState = '窗口状态:' + info.state;
console.info('FloatTask04 state=' + JSON.stringify(info));
if (info.state === floatView.FloatViewState.STARTED) { this.readProperties(); }
if (info.state === floatView.FloatViewState.STOPPED) {
controller.offRectChange();
controller.offLimitsChange();
controller.offStateChange();
if (this.controller === controller) { this.controller = undefined; }
}
});
await controller.setUIContext('pages/FloatViewPage', runtime.storage);
const limits = floatView.getFloatViewLimits(floatView.FloatViewTemplateType.ROUNDED_RECTANGLE);
console.info('FloatTask04 limits=' + JSON.stringify(limits));
await controller.setWindowSize(limits.maxSize);
await controller.start();
} catch (error) {
const err = error as BusinessError;
this.windowState = '启动失败:' + err.code;
console.error('FloatTask04 error=' + err.code + ':' + err.message);
this.controller?.offStateChange();
this.controller = undefined;
} finally { this.busy = false; }
}
readProperties(): void {
if (!this.controller) { return; }
try {
const props = this.controller.getWindowProperties();
console.info('FloatTask04 properties=' + JSON.stringify(props));
runtime.storage.set<number>('topInset', Math.max(12, this.getUIContext().px2vp(props.avoidArea.topRect.height)));
runtime.storage.set<number>('bottomInset', Math.max(16, this.getUIContext().px2vp(props.avoidArea.bottomRect.height)));
} catch (error) { console.info('FloatTask04 propertiesPending=' + (error as BusinessError).code); }
}
async resize(small: boolean): Promise<void> {
if (!this.controller) { return; }
try {
const limits = floatView.getFloatViewLimits(this.template);
const size: window.Size = small ? limits.minSize : limits.maxSize;
runtime.storage.set<boolean>('compact', small || this.template === floatView.FloatViewTemplateType.HORIZONTAL_BAR);
console.info('FloatTask04 request=' + JSON.stringify(size));
await this.controller.setWindowSize(size);
} catch (error) { console.error('FloatTask04 resize=' + (error as BusinessError).code); }
}
async changeTemplate(): Promise<void> {
if (!this.controller) { return; }
try {
const next = this.template === floatView.FloatViewTemplateType.ROUNDED_RECTANGLE ?
floatView.FloatViewTemplateType.HORIZONTAL_BAR : floatView.FloatViewTemplateType.ROUNDED_RECTANGLE;
const limits = floatView.getFloatViewLimits(next);
await this.controller.switchTemplate({ templateType: next, size: limits.maxSize });
this.template = next;
runtime.storage.set<boolean>('compact', next === floatView.FloatViewTemplateType.HORIZONTAL_BAR);
console.info('FloatTask04 template=' + next + ' requested=' + JSON.stringify(limits.maxSize));
} catch (error) { console.error('FloatTask04 templateError=' + (error as BusinessError).code); }
}
async closeFloat(): Promise<void> {
if (!this.controller || this.busy) { return; }
try { await this.controller.stop(); }
catch (error) {
console.error('FloatTask04 stop=' + (error as BusinessError).message);
}
}
aboutToDisappear(): void {
runtime.dispose();
}
build() {
Column({ space: 8 }) {
Text('文件整理').fontSize(30).fontWeight(FontWeight.Bold)
Text('阶段 04 · 窗口空间调整').fontSize(18)
Text(this.taskState).fontSize(24)
Text(this.progress + '%').fontSize(32)
Progress({ value: this.progress, total: 100 }).width(260)
Text('闪控窗能力:' + this.supported).fontSize(18)
Text(this.windowState).fontSize(18)
Row({ space: 12 }) {
Button('暂停任务').onClick(() => runtime.pause())
Button('恢复任务').onClick(() => runtime.resume())
Button('取消任务').onClick(() => runtime.cancel())
}
Row({ space: 8 }) {
Button('紧凑尺寸').onClick(() => this.resize(true))
Button('完整尺寸').onClick(() => this.resize(false))
Button('切换模板').onClick(() => this.changeTemplate())
}
Button('开始整理').onClick(() => runtime.start())
Button('显示闪控窗').enabled(!this.busy).onClick(() => this.openFloat())
Button('停止闪控窗').onClick(() => this.closeFloat())
}.width('100%').height('100%').padding(32).justifyContent(FlexAlign.Center)
}
}
module.json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"requestPermissions": [{"name":"ohos.permission.FLOAT_VIEW","reason":"$string:float_reason","usedScene":{"abilities":["EntryAbility"],"when":"inuse"}}],
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
],
"actions": [
"ohos.want.action.home"
]
}
]
}
],
"extensionAbilities": [
{
"name": "EntryBackupAbility",
"srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
"type": "backup",
"exported": false,
"metadata": [
{
"name": "ohos.extension.backup",
"resource": "$profile:backup_config"
}
],
}
]
}
}
main_pages.json
{"src":["pages/Index","pages/FloatViewPage"]}
string.json
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "文件整理"
},
{
"name": "float_reason",
"value": "在闪控窗中查看本地任务状态"
}
]
}
更多推荐



所有评论(0)