Mobile Development 8 min read

Implementing System-Level and In-App Floating Windows on Android and HarmonyOS

The article compares Android’s fragmented system‑level floating‑window permissions and lack of built‑in in‑app support with HarmonyOS’s simpler Application Sub‑Window approach, detailing how to obtain a WindowStage, create and configure a draggable sub‑window, handle gestures, and forward navigation and back‑press events to the main window.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Implementing System-Level and In-App Floating Windows on Android and HarmonyOS

The article starts with a critique of Android's floating‑window experience, noting that implementing system‑level floating windows requires handling a non‑standard permission that varies across device manufacturers. A code snippet from the EasyFloat library demonstrates how to check permissions for Huawei, Miui, Oppo, Meizu, 360 and other ROMs, and how to fall back to the standard Settings.canDrawOverlays method on Android M and above.

For in‑app global floating windows on Android, the author admits there is no built‑in support and suggests inserting a view directly into the root DecorView .

The discussion then shifts to HarmonyOS, where creating floating windows is considerably simpler. System‑level overlays still need permission, but the platform provides an "Application Sub‑Window" feature that can be used for in‑app global floating windows.

To create a sub‑window, the developer must obtain a WindowStage object in the EntryAbility.onWindowStageCreate() callback and call WindowStage.createSubWindow() . The article provides the initialization code for a FloatManager that stores the windowStage_ reference.

FloatManager.init(windowStage)

init(windowStage: window.WindowStage) {
  this.windowStage_ = windowStage
}

After creating the sub‑window, the code sets its position, size, UI content, and background color, enabling drag‑and‑drop behavior through repeated calls to moveWindowTo and resize .

// 创建子窗口
showSubWindow() {
    if (this.windowStage_ == null) {
        Log.error(TAG, 'Failed to create the subwindow. Cause: windowStage_ is null');
    } else {
        this.windowStage_.createSubWindow("HarmonyWorld", (err: BusinessError, data) => {
            ...
            this.sub_windowClass = data;
            // 设置位置、大小、内容等
            this.sub_windowClass.moveWindowTo(this.locationX, this.locationY, (err: BusinessError) => { ... });
            this.sub_windowClass.resize(this.size, this.size, (err: BusinessError) => { ... });
            this.sub_windowClass.setUIContent("pages/float/FloatPage", (err: BusinessError) => {
                ...
                (this.sub_windowClass as window.Window).showWindow((err: BusinessError) => {
                    ...
                    data.setWindowBackgroundColor("#00000000")
                });
            });
        })
    }
}

Gesture handling is implemented with GestureGroup in Exclusive mode. A PanGesture updates the floating window's position via FloatManager.updateLocation , while a TapGesture triggers navigation using router.pushUrl() . However, tapping the sub‑window routes within the sub‑window's own UI context, not the main window.

To forward navigation events to the main window, the author uses EventHub (or an emitter). The sub‑window emits an event on tap, and the main EntryAbility subscribes to this event, invoking the main router's pushUrl method.

TapGesture({ count: 1 })
  .onAction(() => {
      this.context.eventHub.emit("event_click_float")
  })
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    eventHub.on("event_click_float", () => {
        if (this.mainRouter) {
            this.mainRouter.pushUrl(...)
        }
    })
}

The main router is obtained after loading the main window's content:

windowStage.loadContent('pages/Index', (err, data) => {
    this.mainRouter = this.windowClass!.getUIContext().getRouter()
});

Finally, the article addresses back‑press handling. When the floating window is focused, the system back gesture targets the sub‑window. By listening to onBackPress() inside the sub‑window's page and emitting a "float_back" event, the main window can respond with mainRouter.back() .

onBackPress(): boolean | void {
    this.context.eventHub.emit("float_back")
}
eventHub.on("clickFloat", () => {
    if (this.mainRouter) {
        this.mainRouter.back()
    }
})

With these steps, a globally draggable floating window is realized on HarmonyOS, offering a smoother developer experience compared to Android's fragmented permission handling.

References: EasyFloat library – https://github.com/princekin-f/EasyFloat/ FloatManager implementation – https://github.com/lulululbj/HarmonyWorld/

AndroidHarmonyOSKotlingestureFloating WindowSubwindow
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.