目录

Float View Development

When to Use

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.

Constraints

Interaction Modes

The float view provides the following interaction modes:

Specifications

[h2]Window Dimensions Restrictions

[h2]Template Types

For details about the template types supported by the float view, see FloatViewTemplateType.

Prerequisites

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.

Development Scenario

[h2]Basic Scenario: Performing Operations on the Float View Independently

  1. 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}.`);
        });
    }
  2. 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}`);
      }
    }
  3. Use the create() API to create a float window controller instance and register a callback for status changes.

  4. Use the setUIContext() method to set the content of the float view.

  5. Use the getFloatViewLimits() method to obtain the window dimension limit and the setWindowSize() method to set proper window dimensions.

  6. 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');
    }
  7. (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;
        }
      });
    }
  8. 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;
        });
      }
    }

[h2]Complex Scenario: Binding the Float View to the Floating Ball

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.

  1. 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}.`);
        });
    }
  2. 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}`);
      }
    }
  3. 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');
    }
  4. 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.

  5. Use the setUIContext() method to set the content of the float view.

  6. Use the getFloatViewLimits() method to obtain the window dimension limit and the setWindowSize() method to set proper window dimensions.

  7. 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}`);
      }
    }
  8. 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;
        });
      }
    }
  9. (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}`);
      });
    }

[h2]Complex Scenario: Used Together with Screen Privacy

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

  1. 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);
        }
      });
    }
  2. 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;
      }
    }
  3. 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);
    }
  4. 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;
        }
      });
    }
  5. 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}`);
        });
      }
    }