方案 | 优缺点 | 适用场景 |
|---|---|---|
方案一:通过普通子窗口实现。 | 实现简单,不需要申请特殊权限,只能实现应用内悬浮窗。 | 该方案适用于应用内的全局悬浮窗。 |
方案二:通过系统提供的悬浮窗实现。 | 需要申请ohos.permission.SYSTEM_FLOAT_WINDOW权限,多人视频通话、屏幕共享特殊场景与功能可申请该权限。 | 该方案适用于应用全局悬浮窗,应用进入后台,悬浮窗显示在桌面上,如多人视频通话、屏幕共享。 |
方案三:通过多UIAbility实现。 | 需申请ohos.permission.WINDOW_TOPMOST权限,目前仅PC/2in1支持。 | 该方案适用于应用全局悬浮窗,应用进入后台,悬浮窗显示在桌面上。 |
方案四:通过闪控球实现。 | 需申请ohos.permission.USE_FLOAT_BALL权限,特殊场景与功能可申请该权限,且需要API20及以上才可使用。 | 该方案适用于应用全局悬浮窗,如跨应用的题目搜索、账单记录、商品比价、翻译等。 |
import { UIAbility } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
const DOMAIN = 0x0000;
export default class EntryAbility extends UIAbility {
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Page', (err) => {
if (err.code) {
hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
return;
}
AppStorage.setOrCreate('uiContext', windowStage.getMainWindowSync()
.getUIContext());
AppStorage.setOrCreate('windowStage', windowStage);
let windowClass: window.Window = windowStage.getMainWindowSync(); // 获取应用主窗口
// 2. 获取布局避让遮挡的区域
let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 以导航条避让为例
let avoidArea = windowClass.getWindowAvoidArea(type);
let bottomRectHeight = avoidArea.bottomRect.height; // 获取到导航条区域的高度
AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
type = window.AvoidAreaType.TYPE_SYSTEM; // 以状态栏避让为例
avoidArea = windowClass.getWindowAvoidArea(type);
let topRectHeight = avoidArea.topRect.height; // 获取状态栏区域高度
AppStorage.setOrCreate('topRectHeight', topRectHeight);
hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
});
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground');
}
}; import { window } from '@kit.ArkUI';
@Entry
@Component
struct Page {
private message: string = 'Hello World';
private windowStage: window.WindowStage = AppStorage.get('windowStage') as window.WindowStage;
aboutToAppear(): void {
this.showSubWindow();
}
build() {
RelativeContainer() {
Text(this.message)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
});
}
.backgroundColor(Color.White)
.height('100%')
.width('100%');
}
showSubWindow() {
this.windowStage.createSubWindow('FloatWindow', (err, windowClass) => {
if (err.code > 0) {
return;
}
try {
// 设置子窗口加载页
windowClass.setUIContent('pages/FloatWindowBySubWindow', () => {
});
// 设置子窗口左上角坐标
windowClass.moveWindowTo(0, 200);
// 设置子窗口大小
windowClass.resize(this.getUIContext()
.vp2px(50),
this.getUIContext()
.vp2px(50));
// 展示子窗口
windowClass.showWindow();
} catch (err) {
}
});
}
} import { window } from '@kit.ArkUI';
import { ChangeFocus, CusPosition, dragToMove, edgeDetermination } from './WindowUtil';
@Entry
@Component
struct FloatWindowBySubWindow {
private windowStage: window.WindowStage = AppStorage.get('windowStage') as window.WindowStage;
private subWindow: window.Window = window.findWindow('FloatWindow');
// 创建位置变量,变量发生变化调用moveWindow方法移动窗口
@State windowPosition: CusPosition = { x: 0, y: 200 };
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
ctx?: Context = this.getUIContext().getHostContext();
eventFunc = () => {
edgeDetermination(this.subWindow, this.windowPosition);
};
aboutToAppear(): void {
this.ctx!.eventHub.on('onForeground', this.eventFunc);
}
aboutToDisappear(): void {
this.ctx!.eventHub.off('onForeground');
}
onPageShow(): void {
ChangeFocus(this.windowStage, 'FloatWindow');
}
build() {
Column() {
Stack() {
this.floatingWindow();
}
.width(50)
.height(50)
.alignContent(Alignment.Start);
}
.backgroundColor(Color.Transparent);
}
@Builder
floatingWindow() {
Column() {
// 真实项目中可替换为开发者真实的图片
Image($r('app.media.play'))
.width(30)
.height(30);
}
.width(50)
.height(50)
.borderRadius(25)
.backgroundColor('#f1f3f5')
.justifyContent(FlexAlign.Center)
.gesture(
// 声明该组合手势的类型为Sequence类型
PanGesture(this.panOption)// 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition
.onActionStart(async () => {
})
.onActionUpdate((event: GestureEvent) => {
dragToMove(event, this.subWindow, this.windowPosition);
})
.onActionEnd(() => {
edgeDetermination(this.subWindow, this.windowPosition);
ChangeFocus(this.windowStage, 'FloatWindow');
})
);
}
} import { display, window } from '@kit.ArkUI';
let uiContext: UIContext = AppStorage.get('uiContext') as UIContext;
/**
* 位置信息
*/
export interface CusPosition {
x: number,
y: number
}
/**
* 聚焦到主窗口
* @param windowStage 窗口管理器
* @param windowName 子窗口名
*/
export function ChangeFocus(windowStage: window.WindowStage, windowName: string) {
setTimeout(() => {
// 获取子窗口ID
let subWindowID: number = window.findWindow(windowName).getWindowProperties().id;
// 获取主窗口ID
let mainWindowID: number = windowStage.getMainWindowSync().getWindowProperties().id;
// 将焦点从子窗口转移到主窗口
window.shiftAppWindowFocus(subWindowID, mainWindowID);
}, 500);
}
/**
* 贴边判断
* @param window 目标窗口
* @param windowPosition 位置信息
*/
export function edgeDetermination(window: window.Window, windowPosition: CusPosition): void {
const HALF_WIDTH = display.getDefaultDisplaySync().width * 0.5;
if (windowPosition.x < HALF_WIDTH) {
windowPosition.x = 0;
} else {
windowPosition.x = display.getDefaultDisplaySync().width - window.getWindowProperties().windowRect.width;
}
window.moveWindowTo(windowPosition.x, windowPosition.y);
}
/**
* 拖拽滑动
* @param event 手势事件
* @param window 目标窗口
* @param windowPosition 位置信息
*/
export function dragToMove(event: GestureEvent, window: window.Window, windowPosition: CusPosition): void {
let bottomRectHeight = AppStorage.get('bottomRectHeight') as number;
let topRectHeight = AppStorage.get('topRectHeight') as number;
let bottomY =
display.getDefaultDisplaySync().height - bottomRectHeight - uiContext.vp2px(50);
windowPosition.x += event.offsetX;
if (windowPosition.y > topRectHeight && windowPosition.y < bottomY) {
windowPosition.y += event.offsetY;
} else if (windowPosition.y <= topRectHeight && event.offsetY > 0) {
windowPosition.y += event.offsetY;
} else if (windowPosition.y >= bottomY && event.offsetY < 0) {
windowPosition.y += event.offsetY;
}
window.moveWindowTo(windowPosition.x, windowPosition.y);
} 效果如下:

import { window } from '@kit.ArkUI';
@Entry
@Component
struct FloatWindowBySystemApi {
private windowClass: window.Window | null = null;
private context1 = this.getUIContext().getHostContext();
build() {
Column() {
Text('打开悬浮窗')
.fontSize(25)
.fontWeight(FontWeight.Bold)
.onClick(() => {
// 1.创建悬浮窗。
let config: window.Configuration = {
name: 'floatWindow', windowType: window.WindowType.TYPE_FLOAT, ctx: this.context1
};
window.createWindow(config, (err: BusinessError, data) => {
let errCode: number = err.code;
if (errCode) {
return;
}
this.windowClass = data;
// 2.悬浮窗窗口创建成功后,设置悬浮窗的位置、大小及相关属性等。
this.windowClass.moveWindowTo(300, 300, (err: BusinessError) => {
let errCode: number = err.code;
if (errCode) {
return;
}
});
this.windowClass.resize(500, 500, (err: BusinessError) => {
let errCode: number = err.code;
if (errCode) {
return;
}
});
// 3.为悬浮窗加载对应的目标页面。
this.windowClass.setUIContent('pages/Index', (err: BusinessError) => {
let errCode: number = err.code;
if (errCode) {
return;
}
// 3.显示悬浮窗。
(this.windowClass as window.Window).showWindow((err: BusinessError) => {
let errCode: number = err.code;
if (errCode) {
return;
}
});
});
});
});
Text('关闭悬浮窗')
.fontSize(25)
.margin({ top: 15 })
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.windowClass?.destroyWindow();
});
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%');
}
} import { display, window } from '@kit.ArkUI';
@Entry
@Component
struct Index {
private windowX: number = 100;
private windowY: number = 100;
private floatWindow: window.Window | null = null;
private screenWidth: number = 0;
aboutToAppear() {
// 获取悬浮窗实例
this.floatWindow = window.findWindow('floatWindow');
// 获取屏幕尺寸
let displayInfo = display.getDefaultDisplaySync();
this.screenWidth = displayInfo.width;
}
// 处理拖拽更新
private onPan(event: GestureEvent) {
this.windowX += event.offsetX;
this.windowY += event.offsetY;
if (this.floatWindow) {
this.floatWindow.moveWindowTo(this.windowX, this.windowY);
}
}
// 处理拖拽结束,实现自动贴边
private onPanEnd() {
const midX = this.screenWidth / 2;
const threshold = 100; // 贴边阈值,可根据需要调整
let targetX = this.windowX;
// 判断是否靠近屏幕中间
if (Math.abs(this.windowX - midX) < threshold) {
targetX = this.windowX > midX ? this.screenWidth - 200 : 0; // 200为窗口宽度
}
// 更新窗口位置
if (this.floatWindow) {
this.floatWindow.moveWindowTo(targetX, this.windowY);
}
}
build() {
Row() {
Column() {
// 真实项目中可替换为开发者真实的图片
Image($r('app.media.play'))
.margin({ left: 10, top: 10 })
.width(55)
.height(55);
Text('ArkTS是HarmonyOS主力应用开发语言。')
.fontSize(15)
.fontWeight(FontWeight.Bold)
.margin({ left: 10, top: 10 });
}
.width('100%');
}
.gesture(
PanGesture({ direction: PanDirection.All })
.onActionUpdate((event: GestureEvent) => {
this.onPan(event);
})
.onActionEnd(() => {
this.onPanEnd();
})
)
.height('100%');
}
} {
"name": "ohos.permission.SYSTEM_FLOAT_WINDOW",
}, 效果如下:

import { UIAbility } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';
export default class FloatWindowAbility extends UIAbility {
private async resizeWindow(win: window.Window, width: number, height: number): Promise<void> {
try {
await win.resize(width, height);
console.info('Succeeded in resizing window.');
} catch (err) {
console.error(`resize window failed: <${err.code}>${err.message}`);
}
}
private setDecorVisible(win: window.Window, isVisible: boolean) {
try {
win.setWindowDecorVisible(isVisible);
console.info('set window decor visible success.');
} catch (err) {
console.error(`set window decor visible failed: <${err.code}>${err.message}`);
}
}
private setTitleButtonVisible(win: window.Window, isMaxVisible: boolean,
isMinVisible: boolean, isCloseVisible?: boolean) {
try {
win.setWindowTitleButtonVisible(isMaxVisible, isMinVisible, isCloseVisible);
console.info('set window title button visible success.');
} catch (err) {
console.error(`set window title button visible failed: <${err.code}>${err.message}`);
}
}
private async setTopmost(win: window.Window, isWindowTopmost: boolean): Promise<void> {
try {
await win.setWindowTopmost(isWindowTopmost);
console.info('set window topmost success.');
} catch (err) {
console.error(`set window topmost failed: <${err.code}>${err.message}`);
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// 加载主窗口对应的页面。
windowStage.loadContent('pages/FloatWindowByAbilityDetailPage', () => {
let mainWindow: window.Window | undefined = undefined;
// 获取应用主窗口。
windowStage.getMainWindow().then(async (data: window.Window) => {
if (!data) {
return;
}
mainWindow = data;
// 设置窗口大小
await this.resizeWindow(mainWindow, 300, 400);
// 设置窗口标题可见
this.setDecorVisible(mainWindow, false);
// 设置窗口最大化、最小化、关闭按钮可见
this.setTitleButtonVisible(mainWindow, false, false, false);
// 设置窗口置顶
await this.setTopmost(mainWindow, true);
}).catch((err: BusinessError) => {
if (err.code) {
console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`);
}
});
});
}
}; import { common, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct FloatWindowByAbility {
private abilityContext: common.UIAbilityContext = this.getUIContext().getHostContext() as common.UIAbilityContext;
build() {
Column({ space: 20 }) {
Button('拉起悬浮窗')
.onClick(() => {
let want: Want = {
// 此处需要根据实际包名进行更改
bundleName: 'com.example.floatwindowdemo',
abilityName: 'FloatWindowAbility',
moduleName: 'entry',
};
this.abilityContext?.startAbility(want)
.then(() => {
console.info('start ability success');
}).catch((error: BusinessError) => {
console.error(`start ability failed, code: ${error.code}, message: ${error.message}`);
});
});
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
} @Entry
@Component
struct FloatWindowByAbilityDetailPage {
build() {
Row() {
Column() {
Image($r('app.media.play'))
.margin({ left: 10, top: 10 })
.width(55)
.height(55);
Text('ArkTS是HarmonyOS主力应用开发语言,基于TypeScript(简称TS)语言扩展而来,是TS的超集。')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ left: 10, top: 10 });
}
.width('100%');
}
.height('100%')
.width('100%');
}
} {
"name": "ohos.permission.WINDOW_TOPMOST"
}, 效果如下:

闪控球是一种在设备屏幕上悬浮的非全屏应用窗口,为应用提供临时的全局能力,完成跨应用交互,具体实现可参考官网详细开发步骤:全局闪控球开发指导。