HarmonyOS鸿蒙Next中实践过的悬浮窗口
HarmonyOS鸿蒙Next中实践过的悬浮窗口
背景
悬浮视图或者窗体,在Android和iOS两大移动平台均有使用,HarmonyOS 也实现了此功能,如下为大家分享一下效果
准备
-
熟读HarmonyOS 手势指导 https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-gesture-events-single-gesture-0000001450596854-V3#section128381857165115
-
熟读ALC签名指导,用于可以申请 “ohos.permission.SYSTEM_FLOAT_WINDOW” 权限 https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/signing-0000001587684945-V3#section157591551175916
-
拜读帖子 https://developer.huawei.com/consumer/cn/forum/topic/0202125832632308225?fid=0101587866109860105
实践总结
-
如果开启了悬浮窗口,任何界面的物理返回键事件都会被悬浮窗口拦截掉,即 手势返回废了
-
参数类型易混淆, 拖动 PanGesture 中的
onActionUpdate
接口,数据单位为 vp,window 中的moveWindowTo
接口参数,数据单位为 px -
采用
moveWindowTo
实现的窗口 拖动效果十分不平滑 -
通过
requestPermissionsFromUser
申请ohos.permission.SYSTEM_FLOAT_WINDOW
权限时,无法弹出系统权限提示框
片段代码
配置 module.json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
......
{
"name": "FloatWindowAbility",
"srcEntry": "./ets/myentryability/FloatWindowAbility.ts",
"description": "$string:FloatWindowAbility_desc",
"icon": "$media:icon",
"label": "$string:FloatWindowAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackground": "$color:start_window_background",
},
],
"requestPermissions": [
{
"name": "ohos.permission.SYSTEM_FLOAT_WINDOW",
"usedScene": {
"abilities": [
"FloatWindowAbility"
],
"when": "always"
}
}
]
}
}
悬浮窗口UIAbility
import window from '@ohos.window';
import BaseUIAbility from '../baseuiability/BaseUIAbility';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import bundleManager from '@ohos.bundle.bundleManager';
const permissions: Array<Permissions> = ['ohos.permission.SYSTEM_FLOAT_WINDOW'];
export default class FloatWindowAbility extends BaseUIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
let context = this.context;
let atManager = abilityAccessCtrl.createAtManager();
checkPermissions().then((result)=>{
if(result){
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
let length: number = grantStatus.length;
for (let i = 0; i < length; i++) {
if (grantStatus[i] === 0) {
console.log('用户授权,可以继续访问目标操作')
} else {
console.log('用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限')
return;
}
}
// 授权成功
// 1.创建悬浮窗。
let windowClass = null;
let config = {name: "floatWindow", windowType: window.WindowType.TYPE_FLOAT, ctx: this.context};
window.createWindow(config, (err, data) => {
if (err.code) {
console.error('Failed to create the floatWindow. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in creating the floatWindow. Data: ' + JSON.stringify(data));
windowClass = data;
// 2.悬浮窗窗口创建成功后,设置悬浮窗的位置、大小及相关属性等。
windowClass.moveWindowTo(0, 200, (err) => {
if (err.code) {
console.error('Failed to move the window. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in moving the window.');
});
windowClass.resize(1080, 151, (err) => {
if (err.code) {
console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in changing the window size.');
});
// 3.为悬浮窗加载对应的目标页面。
windowClass.setUIContent("custompages/FloatPage", (err) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content.');
// 3.显示悬浮窗。
windowClass.showWindow((err) => {
if (err.code) {
console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in showing the window.');
});
try {
windowClass.setWindowBackgroundColor('#00000000')
} catch (exception) {
console.error('Failed to set the background color. Cause: ' + JSON.stringify(exception));
}
});
})
}).catch((err) => {
console.error(`requestPermissionsFromUser failed, code is ${err.code}, message is ${err.message}`);
})
}
})
}
}
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus;
// 获取应用程序的accessTokenID
let tokenId: number;
try {
let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
tokenId = appInfo.accessTokenId;
} catch (err) {
console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (err) {
console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
async function checkPermissions(): Promise<boolean> {
const permissions: Array<Permissions> = ['ohos.permission.SYSTEM_FLOAT_WINDOW'];
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions[0]);
if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
// 已经授权,可以继续访问目标操作
console.log('没有授权')
return true
} else {
// 申请日历权限
console.log('已授权')
return false
}
}
悬浮窗口页面
/**
*
* 官方指导: https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/application-window-stage-0000001427584712-V3#ZH-CN_TOPIC_0000001523968638__设置悬浮窗
* 参见帖子:https://developer.huawei.com/consumer/cn/forum/topic/0202125832632308225?fid=0101587866109860105
*
*/
import common from '@ohos.app.ability.common';
import window from '@ohos.window';
@Entry
@Component
struct Index {
@State lasttime: number = 0
@State message: string = '悬浮窗'
@State foldStatus: boolean = false
@State idleName: string = '收起'
@State floatWindowWidth: number = 0
@State offsetX: number = 0
@State offsetY: number = 0
@State positionX: number = 0
@State positionY: number = 0
@State windowPosition: Position = { x: 0, y: 0 };
private context = getContext(this) as common.UIAbilityContext;
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
floatWindow: window.Window
aboutToAppear(){
this.eventHubFunc()
this.floatWindow = window.findWindow("floatWindow")
this.floatWindowWidth = 1080
this.panOption.setDistance(1)
}
onBackPress(){
console.log('返回')
}
build() {
Row() {
Text('X').width(px2vp(140))
.textAlign(TextAlign.Center)
.fontColor(Color.Red).onClick(() =>{
//关闭所依赖的UIAbility
this.context.terminateSelf()
//销毁悬浮窗。当不再需要悬浮窗时,可根据具体实现逻辑,使用destroy对其进行销毁。
this.floatWindow.destroyWindow((err) => {
if (err.code) {
console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in destroying the window.');
});
})
Text(this.idleName)
.width(px2vp(140))
.height('100%')
.fontSize(18)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.backgroundColor(Color.Gray)
.onClick(() =>{
this.foldStatus = !this.foldStatus
if(this.foldStatus){
this.idleName = "展开"
this.floatWindowWidth = 280
} else {
this.idleName = "收起"
this.floatWindowWidth = 1080
}
})
Divider().vertical(true).color(Color.Red)
if(!this.foldStatus) {
Text(this.message)
.width(px2vp(800))
.fontSize(18)
.fontColor(Color.White)
.padding('12vp')
}
}
.width(px2vp(this.floatWindowWidth))
.height(px2vp(150))
.borderRadius('12vp')
.backgroundColor(Color.Green)
.gesture(
// 绑定PanGesture事件,监听拖拽动作
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
console.info('Pan start');
})
// 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition
.onActionUpdate((event: GestureEvent) => {
console.log(event.offsetX + ' ' + event.offsetY)
this.offsetX = this.positionX + event.offsetX
this.offsetY = this.positionY + event.offsetY
this.floatWindow.moveWindowTo(vp2px(this.offsetX), vp2px(this.offsetY));
})
.onActionEnd(() => {
this.positionX = this.offsetX
this.positionY = this.offsetY
console.info('Pan end');
})
)
}
eventHubFunc() {
this.context.eventHub.on('info', (data) => {
this.message = data
});
}
}
更多关于HarmonyOS鸿蒙Next中实践过的悬浮窗口的实战教程也可以访问 https://www.itying.com/category-93-b0.html
是全局的吗?退到后台悬浮窗还在吗
更多关于HarmonyOS鸿蒙Next中实践过的悬浮窗口的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
标题
这是主要内容。
这是另一段内容。
有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html
全局的,退到后台依然还在,
在HarmonyOS鸿蒙Next中,悬浮窗口通过WindowManager
和AbilitySlice
实现。首先,创建WindowManager
实例,并设置窗口参数如WindowManager.LayoutConfig
。然后,通过WindowManager
的addWindow
方法添加悬浮窗口,并指定其布局文件。为确保窗口可交互,需处理触摸事件和生命周期管理。最后,通过removeWindow
方法移除窗口。这一机制适用于需要临时显示或快速交互的场景,如通知、快捷操作等。