A float view is a small window that floats on a desktop or other applications' screen, providing flexible window management capabilities. Applications can use display content or provide shortcut operations in their float view. Users can view the content in the float view while performing other operations on the screen, enhancing their overall experience.
A float view is applicable to scenarios where application content needs to be continuously displayed in an independent small window or shortcut operations need to be provided, such as stock market tracking applications and live streaming applications.
A float view can be used together with a floating ball. After they are bound, users can tap to switch between them.
Description
The float view feature is supported since API version 26.0.0.
For security purposes, the float view can be started only when the application is running in the foreground and has granted the ohos.permission.FLOAT_VIEW permission.
Only one float view can be started for an application. When multiple float views are started, error code 1300033 is returned.
If the floating ball or PiP window has been started in the application, the float view cannot be started. In this case, you need to stop the floating ball or PiP window first.
The float view capabilities can be used only in the stage model.
The float view provides the following interaction modes:
Dragging: You can manually drag the touch target of the float view to change its position. When dragging the float view, it automatically avoids system components such as the status bar, navigation bar, and input method keyboard.
Operations on the title bar: You can click the close button on the title bar to close the float view. After the float view and floating ball are bound, users can click the zoom-out button to switch the float view to the floating ball. If they are not bound, users can click the minimize button to collapse the float view to the sidebar.
Deleting a window from the trash can: On phones (not in free windows mode) and tablets (not in free windows mode or non-desktop mode), users can drag a window to the trash can area (in the middle of the bottom) and release it to delete the window.
Sidebar function: On phones (not in free windows mode) and tablets (not in free windows mode or non-desktop mode), the float view can be temporarily stored in the system sidebar.
Switching between the floating ball and the float view: When the floating ball is bound, users can tap the floating ball to switch between the floating ball and the float view. After the switch, the float view status changes to IN_FLOATING_BALL.
The window dimensions and aspect ratio restrictions of the float view can be obtained through the getFloatViewLimits() API. The FloatViewLimits object is returned.
You are advised to call getFloatViewLimits() to obtain the recommended range before setting the window dimensions, and use onLimitsChange() to listen for dimension changes. If the set window dimensions exceed the recommended range, the system will adjust the dimensions to a value within the range. You can obtain the actual window dimensions by listening for changes using onRectChange().
The float view attributes (such as windowId) can be obtained through the getWindowProperties() method. The FloatViewProperties object is returned.
For details about the template types supported by the float view, see FloatViewTemplateType.
To use the float window, you need to apply for the ohos.permission.FLOAT_VIEW permission, which is a user_grant permission. You need to declare the permission and apply for authorization from the user.
Before using the float view, apply for the ohos.permission.FLOAT_VIEW permission by referring to Prerequisites.
// Initialize the application during creation and verifies user authorization status.
aboutToAppear(): void {
let flag = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
let bundleInfo = bundleManager.getBundleInfoForSelfSync(flag);
let atManager = abilityAccessCtrl.createAtManager();
atManager.checkAccessToken(bundleInfo.appInfo.accessTokenId, 'ohos.permission.FLOAT_VIEW').then(data => {
console.info('Permission check: ' + JSON.stringify(data));
this.result = data === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED ? 'Permission granted' : 'Permission not granted'
});
let enable: boolean = floatView.isFloatViewEnabled();
console.info(TAG + 'floatView enabled is: ' + enable);
}
// Apply for user authorization.
requestPermission(): void {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let hostContext = this.getUIContext().getHostContext();
atManager.requestPermissionsFromUser(hostContext, ['ohos.permission.FLOAT_VIEW'] as Permissions[])
.then((data) => {
console.info(TAG + `grant result: ${data.authResults}.`);
this.result = data.authResults[0] === 0 ? 'Permission granted' : 'Permission denied'
})
.catch((reason: BusinessError) => {
console.error(TAG + `requestPermissionsFromUser failed, ${reason?.code}, ${reason?.message}.`);
});
} Import the module, declare the float view controller, and declare the page view component.
// Declare the float view controller.
private floatViewController: floatView.FloatViewController | undefined = undefined;
@State result: string = '';
// Create a float view.
async createWindow(): Promise<void> {
try {
await this.createWindowPlain();
} catch (err) {
console.error(TAG +
`Failed to create float view controller. Cause: ${(err as BusinessError).code}, message: ${(err as
BusinessError).message}`);
}
} Use the create() API to create a float window controller instance and register a callback for status changes.
Use the setUIContext() method to set the content of the float view.
Use the getFloatViewLimits() method to obtain the window dimension limit and the setWindowSize() method to set proper window dimensions.
Start the float view through the start() API.
// Start the float view.
async createWindowPlain(): Promise<void> {
if (!this.floatViewController) {
let ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
let floatConfig: floatView.FloatViewConfiguration = {
context: ctx,
templateType: floatView.FloatViewTemplateType.ROUNDED_RECTANGLE
};
// Create a float view controller instance.
this.floatViewController = await floatView.create(floatConfig);
// Register a status change callback.
this.registerStateChangeCallback();
// Register a dimension change callback.
this.registerLimitsChangeCallback();
}
// Set the page content of the float view.
// FloatViewPage is the float view page, which is implemented by the application based on the actual service.
await this.floatViewController.setUIContext('pages/FloatViewPage');
// Obtain the dimension limit of the float view and set the float view dimensions.
let limits: floatView.FloatViewLimits =
floatView.getFloatViewLimits(floatView.FloatViewTemplateType.ROUNDED_RECTANGLE);
let size: window.Size = {
width: limits.maxSize.width,
height: limits.maxSize.height
};
await this.floatViewController.setWindowSize(size);
// Start the float view.
await this.floatViewController.start();
console.info(TAG + 'Float view started in unbind state');
} (Optional) Use onLimitsChange() to listen for dimension limit changes of the float view and dynamically adjust the dimensions of the float view. Use onStateChange() to listen for status changes of the float view and bind the status change callback.
// Register the callback for the float view dimensions limit changes.
public registerLimitsChangeCallback(): void {
this.floatViewController?.onLimitsChange((limits: floatView.FloatViewLimits) => {
console.info(TAG + `Limits changed: minSize=${limits.minSize}, maxSize=${limits.maxSize}`);
});
}
// Register the callback for float view status changes.
public registerStateChangeCallback(): void {
this.floatViewController?.onStateChange((info: floatView.FloatViewStateChangeInfo) => {
console.info(TAG + `State changed: ${info.state}, reason: ${info.stopReason}`);
if (info.state === floatView.FloatViewState.STOPPED) {
this.floatViewController?.offStateChange();
this.floatViewController?.offLimitsChange();
this.floatViewController = undefined;
}
});
} After the onStateChange() listens to the start(), the stop() stops the flash window.
// Delete the float view.
deleteAll(): void {
console.info(TAG + 'Deleting all');
if (this.floatViewController) {
this.floatViewController.stop().then(() => {
console.info(TAG + 'Float view stopped');
this.floatViewController = undefined;
}).catch((err: BusinessError) => {
console.error(TAG + `Failed to delete float view: ${err.code}`);
this.floatViewController = undefined;
});
}
} The float view can be bound to the floating ball to implement the following capabilities:
The binding requires both the ohos.permission.USE_FLOAT_BALL and ohos.permission.FLOAT_VIEW permissions.
Before using the float view and floating ball, apply for the ohos.permission.FLOAT_VIEW and ohos.permission.USE_FLOAT_BALL permissions by referring to Prerequisites.
// Initialize the application during creation and apply for user authorization.
aboutToAppear(): void {
let flag = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION;
let bundleInfo = bundleManager.getBundleInfoForSelfSync(flag);
let atManager = abilityAccessCtrl.createAtManager();
atManager.checkAccessToken(bundleInfo.appInfo.accessTokenId, 'ohos.permission.FLOAT_VIEW').then(data => {
console.info('Permission check: ' + JSON.stringify(data));
this.result = data === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED ? 'Permission granted' : 'Permission not granted'
});
let enable: boolean = floatView.isFloatViewEnabled();
console.info(TAG + 'floatView enabled is: ' + enable);
}
// Query the user authorization status.
requestPermission(): void {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
atManager.requestPermissionsFromUser(getContext(this), ['ohos.permission.FLOAT_VIEW'] as Permissions[])
.then((data) => {
console.info(TAG + `grant result: ${data.authResults}.`);
this.result = data.authResults[0] === 0 ? 'Permission granted' : 'Permission denied'
})
.catch((reason: BusinessError) => {
console.error(TAG + `requestPermissionsFromUser failed, ${reason?.code}, ${reason?.message}.`);
});
} Import the module, declare the float view controller and floating ball controller, and declare the page view component.
// Declare the float view controller.
private floatViewController: floatView.FloatViewController | undefined = undefined;
// Declare the floating ball controller.
private floatingBallController: floatingBall.FloatingBallController | undefined = undefined;
@State result: string = '';
@State bindState: BindState = BindState.BIND;
@State statusText: string = 'Current status: bound'
@State currentDisplay: string = 'none';
// Create a float view.
async createWindow(): Promise<void> {
try {
// In the bound state: If the floating ball handle already exists, bind the floating ball to the float view.
if (this.bindState === BindState.BIND) {
await this.createWindowAdvanced();
} else {
await this.createWindowPlain();
}
} catch (err) {
console.error(TAG +
`Failed to create float view controller. Cause: ${(err as BusinessError).code}, message: ${(err as
BusinessError).message}`);
}
} Use the floatView.create() API to create a float view controller instance and the floatingBall.create() API to create a floating ball controller instance.
async createWindowAdvanced(): Promise<void> {
if (!this.floatingBallController) {
let ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
let ballConfig: floatingBall.FloatingBallConfiguration = {
context: ctx
};
// Create a floating ball controller instance.
this.floatingBallController = await floatingBall.create(ballConfig);
}
if (!this.floatViewController) {
let ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
let floatConfig: floatView.FloatViewConfiguration = {
context: ctx,
templateType: floatView.FloatViewTemplateType.ROUNDED_RECTANGLE
};
// Create a float view instance.
this.floatViewController = await floatView.create(floatConfig);
}
this.registerStateChangeCallback();
this.registerLimitsChangeCallback();
await this.bindControllersAndStart();
}
// Create a complex floating window in the bind state. Otherwise, create a plain floating window.
async bindFloatViewAndBall(): Promise<void> {
this.bindState = BindState.BIND;
this.statusText = 'Current status: bound';
this.currentDisplay = 'none';
console.info(TAG + 'State updated to BIND');
} Use the floatView.bind() API to bind the float view controller to the floating ball controller. During the binding, you need to configure the floating ball parameters.
Use the setUIContext() method to set the content of the float view.
Use the getFloatViewLimits() method to obtain the window dimension limit and the setWindowSize() method to set proper window dimensions.
Start the float view through the start() API. If the float view is bound to a floating ball, the floating ball will also be started. You can receive the callback for creating the floating ball.
private async bindControllersAndStart(): Promise<void> {
if (!this.floatViewController || !this.floatingBallController) {
console.warn(TAG + 'Controllers not ready for binding');
return;
}
const fvController = this.floatViewController;
const fbController = this.floatingBallController;
const ballParams: floatingBall.FloatingBallParams = {
template: floatingBall.FloatingBallTemplate.EMPHATIC,
title: "Title",
content: "Body"
};
try {
// Bind the float view controller to the floating ball controller.
await floatView.bind(fvController, fbController, ballParams);
// Set the page content of the float view.
// FloatViewPage is the float view page, which is implemented by the application based on the actual service.
await fvController.setUIContext('pages/FloatViewPage');
// Obtain the window dimensions constraints and set the window dimensions.
let limits: floatView.FloatViewLimits =
floatView.getFloatViewLimits(floatView.FloatViewTemplateType.ROUNDED_RECTANGLE);
let size: window.Size = {
width: limits.maxSize.width,
height: limits.maxSize.height
};
await fvController.setWindowSize(size);
// Start the float view (the floating ball will also be started if it is bound to the float view).
await fvController.start();
} catch (err) {
console.error(TAG +
`Bind and start failed. Cause: ${(err as BusinessError).code}, message: ${(err as BusinessError).message}`);
}
} Stop the float view through the stop() API. If the float view is bound to a floating ball, the floating ball will also be stopped.
deleteAll(): void {
console.info(TAG + 'Deleting all (ball and window)');
// Delete the float view.
if (this.floatViewController) {
this.floatViewController.stop().then(() => {
console.info(TAG + 'Float view stopped');
this.floatViewController = undefined;
}).catch((err: BusinessError) => {
console.error(TAG + `Failed to delete float view: ${err.code}`);
this.floatViewController = undefined;
});
}
//Delete the floating ball.
if (this.floatingBallController) {
this.floatingBallController.stopFloatingBall().then(() => {
console.info(TAG + 'Floating ball stopped');
this.floatingBallController = undefined;
}).catch((err: BusinessError) => {
console.error(TAG + `Failed to delete floating ball: ${err.code}`);
this.floatingBallController = undefined;
});
}
} (Optional) Unbind the float view from the floating ball through the floatView.unbind() API.
unbindFloatViewAndBall(): void {
this.bindState = BindState.UNBIND;
this.statusText = 'Current status: unbound';
console.info(TAG + 'State updated to UNBIND');
if (!this.floatViewController || !this.floatingBallController) {
console.warn(TAG + 'Controllers not initialized, unbinding not executed');
return;
}
floatView.unbind(this.floatViewController, this.floatingBallController).then(() => {
console.info(TAG + 'Succeeded in unbinding');
}).catch((err: BusinessError) => {
console.error(TAG + `Unbind failed. Code: ${err.code}, Message: ${err.message}`);
});
} The float view can be used together with the screen privacy function. When a user views sensitive information through the float view, the system automatically detects the surrounding environment through sensors. If it detects that someone other than the device owner is peeking, it immediately displays an overlay on the window to cover the content and prevent privacy leaks. Users can also manually disable this protection as required.
Screen privacy is implemented based on the SystemCapability.Security.DlpAntiPeep capability. The identification logic is as follows: The system marks the user who consistently uses face unlock as the device owner. When the sensor detects that both the device owner and a non‑owner are gazing at the screen simultaneously, it sends a peeping status notification (HIDE) to the application via a callback, and the application then triggers privacy protection actions accordingly.
Prerequisites
Encapsulate a screen privacy utility class. Its core capabilities include: detecting whether the device supports the screen privacy feature, querying the system switch status, obtaining the current peeping state, and invoking the system mask API to obscure the screen when necessary for privacy protection.
// Check whether screen privacy is supported.
export function canUseAntiPeep(): boolean {
return canIUse('SystemCapability.Security.DlpAntiPeep');
}
// Check whether screen privacy is enabled.
export async function isAntiPeepOn(): Promise<boolean> {
try {
let result: boolean = await dlpAntiPeep.isDlpAntiPeepSwitchOn();
console.info(TAG + `isAntiPeepOn isDlpAntiPeepSwitchOn success. ${result}`)
return result;
} catch (err) {
console.error(TAG + `[isAntiPeepOn] isDlpAntiPeepSwitchOn failed.${JSON.stringify(err)}`);
return false;
}
}
// Obtain the screen privacy status.
export function getAntiPeepInfo(): dlpAntiPeep.DlpAntiPeepStatus {
try {
let dlpAntiPeepStatus = dlpAntiPeep.getDlpAntiPeepInfo();
console.info(TAG + `getDlpHideInfo success. ${JSON.stringify(dlpAntiPeepStatus)}`);
return dlpAntiPeepStatus;
} catch (err) {
console.info(TAG + `getDlpHideInfo failed. ${JSON.stringify(err)}`);
return -1;
}
}
// Enable global protection.
export function showSystemMaskLayer(windowId: number): Promise<boolean> {
return new Promise((resolve) => {
try {
if (canUseAntiPeep()) {
dlpAntiPeep.setAntiPeepMaskLayer(windowId).catch((err: BusinessError) => {
console.error(
TAG + `Execute setAntiPeepMaskLayer failed. error code:${err.code}, error message:${err.message}`);
resolve(false);
}).then(() => {
console.info(TAG + `setAntiPeepMaskLayer success`);
resolve(true);
})
} else {
resolve(false);
}
} catch (err) {
console.error(TAG + `Call setAntiPeepMaskLayer failed. ${JSON.stringify(err)}`);
resolve(false);
}
});
} Register a listener for the screen privacy status.
Call dlpAntiPeep.on('dlpAntiPeep') to register a listener for the screen privacy status. The value PASS indicates that no one is peeping at the screen of the device, and the value HIDE indicates that someone other than the owner is peeping at the screen of the device.
export interface AntiPeepCallback {
onStatusChanged: (status: dlpAntiPeep.DlpAntiPeepStatus) => Promise<void>;
}
export function listenOnAntiPeepStatus(antiPeepCB: AntiPeepCallback): boolean {
try {
console.info(TAG + `start on('dlpAntiPeep')`);
dlpAntiPeep.on('dlpAntiPeep', (dlpAntiPeepStatus: dlpAntiPeep.DlpAntiPeepStatus) => {
if (antiPeepCB) {
antiPeepCB.onStatusChanged(dlpAntiPeepStatus);
} else {
console.warn(TAG + `antiPeepCB is empty`);
}
});
console.info(TAG + `on('dlpAntiPeep') ok`);
return true;
} catch (err) {
console.error(TAG + `dlpAntiPeep.on failed. ${JSON.stringify(err)}`);
return false;
}
} Initialize the screen privacy status, register the callback, and apply for the float view permission.
During initialization, check the device support, switch status, and current status in sequence.
Declare and initialize the screen privacy callback handler. The callback logic is as follows: When the status is HIDE, obtain the main window of the application and call setAntiPeepMaskLayer() to display the system mask layer.
isListenOn: boolean = false;
isPeep: boolean = false;
isGranted: boolean = false;
private floatViewController: floatView.FloatViewController | undefined = undefined;
antiPeepCB: AntiPeepCallback = {
onStatusChanged: async (status: dlpAntiPeep.DlpAntiPeepStatus): Promise<void> => {
await this.handleAntiPeepStatus(status);
}
};
// Declare the screen privacy callback handler.
private async handleAntiPeepStatus(status: dlpAntiPeep.DlpAntiPeepStatus) {
console.info(TAG + `[handleAntiPeepStatus] ${status}`);
switch (status) {
case dlpAntiPeep.DlpAntiPeepStatus.PASS:
console.info(TAG + 'DlpAntiPeepStatus is PASS');
break;
case dlpAntiPeep.DlpAntiPeepStatus.HIDE:
// Obtain the saved window information from the storage.
let window: window.Window = AppStorage.get('MAIN_WINDOW') as window.Window;
const windowId: number = window.getUIContext().getWindowId() as number;
console.info(TAG + `DlpAntiPeepStatus is HIDE ${windowId}`);
dlpAntiPeep.setAntiPeepMaskLayer(windowId)
.then((result) => {
console.info(TAG, `setAntiPeepMaskLayer success + ${result}`);
})
.catch((err: BusinessError) => {
console.error(TAG,
`Execute setAntiPeepMaskLayer failed. error code:${err.code}, error message:${err.message}`);
})
break;
default:
break;
}
}
// Initialize the screen privacy status and register the callback.
private initAntiPeepStatus() {
if (canUseAntiPeep()) { // Check if the device is supported
isAntiPeepOn().then((opened) => {
if (opened) {
let info = getAntiPeepInfo();
this.handleAntiPeepStatus(info);
this.isListenOn = listenOnAntiPeepStatus(this.antiPeepCB);
if (this.isListenOn) {
console.info(TAG + 'succeed in listenOnAntiPeepStatus ')
}
} else {
try {
this.getUIContext().getPromptAction().showToast({
message: $r('app.string.anti_peep_not_enable')
});
} catch (error) {
console.error(TAG + 'show toast error. code =' + error.code + ', message =' + error.message);
}
}
})
} else {
try {
this.getUIContext().getPromptAction().showToast({
message: $r('app.string.device_not_supported')
});
} catch (error) {
console.error(TAG + 'show toast error. code =' + error.code + ', message =' + error.message);
}
}
}
// Confirm the user authorization status.
private requestPermission(): void {
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let hostContext = this.getUIContext().getHostContext();
atManager.requestPermissionsFromUser(hostContext, ['ohos.permission.FLOAT_VIEW'] as Permissions[])
.then((data) => {
console.info(TAG + `grant result: ${data.authResults}.`);
this.isGranted = data.authResults[0] === 0;
})
.catch((reason: BusinessError) => {
console.error(TAG + `requestPermissionsFromUser failed, ${reason?.code}, ${reason?.message}.`);
});
}
// Initialize the application during creation and apply for user authorization.
aboutToAppear(): void {
this.initAntiPeepStatus();
this.requestPermission();
let enable: boolean = floatView.isFloatViewEnabled();
console.info(TAG + 'floatView enabled is: ' + enable);
} Create and start the float view.
Use floatView.create() to create a controller, use setUIContext(), getFloatViewLimits(), setWindowSize() and start() to set and start the page content, and use onStateChange(), onRectChange() and onLimitsChange() to register the status and dimension limit change callback.
// Start the float view.
async createWindowAntiPeep(): Promise<void> {
if (!this.floatViewController) {
let ctx = this.getUIContext().getHostContext() as common.UIAbilityContext;
let floatConfig: floatView.FloatViewConfiguration = {
context: ctx,
templateType: floatView.FloatViewTemplateType.ROUNDED_RECTANGLE
};
// Create a float view controller instance.
this.floatViewController = await floatView.create(floatConfig);
// Register a status change callback.
this.registerStateChangeCallback();
// Register a dimension change callback.
this.registerLimitsChangeCallback();
}
// Set the page content of the float view.
// FloatViewPage is the float view page, which is implemented by the application based on the actual service.
await this.floatViewController.setUIContext('pages/FloatViewPage');
// Obtain the dimension limit of the float view and set the float view dimensions.
let limits: floatView.FloatViewLimits =
floatView.getFloatViewLimits(floatView.FloatViewTemplateType.ROUNDED_RECTANGLE);
let size: window.Size = {
width: limits.maxSize.width,
height: limits.maxSize.height
};
await this.floatViewController.setWindowSize(size);
// Start the float view.
await this.floatViewController.start();
console.info(TAG + 'Float view started in unbind state');
}
// Register the callback for the float view dimensions limit change.
public registerLimitsChangeCallback(): void {
this.floatViewController?.onLimitsChange((limits: floatView.FloatViewLimits) => {
console.info(TAG + `Limits changed: minSize=${limits.minSize}, maxSize=${limits.maxSize}`);
});
}
// Register the callback for float view status changes.
public registerStateChangeCallback(): void {
this.floatViewController?.onStateChange((info: floatView.FloatViewStateChangeInfo) => {
console.info(TAG + `State changed: ${info.state}, reason: ${info.stopReason}`);
if (info.state === floatView.FloatViewState.STOPPED) {
this.floatViewController?.offStateChange();
this.floatViewController?.offLimitsChange();
this.floatViewController = undefined;
}
});
} Use the stop() API to stop the float view.
// Stop the float view.
deleteAll(): void {
console.info(TAG + 'Deleting all');
if (this.floatViewController) {
this.floatViewController.stop().then(() => {
console.info(TAG + 'Float view stopped');
}).catch((err: BusinessError) => {
console.error(TAG + `Failed to delete float view: ${err.code} reason ${err.message}`);
});
}
}