DEV Community

HarmonyOS
HarmonyOS

Posted on

Using overlay for Floating Announcement Layers in HarmonyOS

Read the original article:Using overlay for Floating Announcement Layers in HarmonyOS

Context

In HarmonyOS development, it's common to overlay text or custom components on top of existing UI elements. While the Stack component can achieve this, excessive nesting may degrade performance. The overlay property offers a more efficient alternative by reducing one layer of Stack nodes.

Description

The OverlayManager enables developers to display custom UI content above the current page, within the safe area of the window. It sits beneath components like Dialog, Popup, Menu, BindSheet, BindContentCover, and Toast, making it ideal for persistent floating elements.

To use it, retrieve the OverlayManager instance via UIContext.getOverlayManager() and manage floating layers through its methods.

Solution

To implement a fixed announcement banner using overlay, follow these steps:

1. Define a Params class to hold overlay configuration:

class Params {
  text: string = '';
  offset: Position;
  pageInfos: NavPathStack;
  overlayContent: ComponentContent<Params>[];
  overlayNode: OverlayManager;

  constructor(text: string, pageInfos: NavPathStack, overlayContent: ComponentContent<Params>[],
              overlayNode: OverlayManager, offset: Position) {
    this.text = text;
    this.offset = offset;
    this.pageInfos = pageInfos;
    this.overlayContent = overlayContent;
    this.overlayNode = overlayNode;
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Create a custom announcement component:

@Builder
function OverlayNode(params: Params) {
  Row() {
    Text(params.text)
      .maxLines(1)
  }
  .onClick(() => {
    let componentContent = params.overlayContent[0];
    params.overlayNode.hideComponentContent(componentContent);
    params.pageInfos.pushPath({ name: 'NoticePage' });
  })
  .position({
    x: params.offset.x,
    y: params.offset.y
  })
  .hitTestBehavior(HitTestMode.Transparent); // Allows interaction through the overlay
}
Enter fullscreen mode Exit fullscreen mode

3. Initialize the overlay in aboutToAppear():

aboutToAppear(): void {
  let componentContent = new ComponentContent(
    this.uiContext, wrapBuilder<[Params]>(OverlayNode), this.params
  );
  this.overlayNode.addComponentContent(componentContent, 0);
  this.overlayContent.push(componentContent);
}
Enter fullscreen mode Exit fullscreen mode

4. Clean up the overlay in aboutToDisappear():

aboutToDisappear(): void {
  let componentContent = this.overlayContent.pop();
  this.overlayNode.removeComponentContent(componentContent);
}
Enter fullscreen mode Exit fullscreen mode

5. Full implementation example:

import { ComponentContent, OverlayManager } from '@kit.ArkUI'

export class Params {
  text: string = '';
  offset: Position;
  pageInfos: NavPathStack;
  overlayContent: ComponentContent<Params>[];
  overlayNode: OverlayManager;

  constructor(text: string, pageInfos: NavPathStack, overlayContent: ComponentContent<Params>[],
              overlayNode: OverlayManager, offset: Position) {
    this.text = text;
    this.offset = offset;
    this.pageInfos = pageInfos;
    this.overlayContent = overlayContent;
    this.overlayNode = overlayNode;
  }
}

@Builder
export function OverlayNode(params: Params) {
  Row({ space: 5 }) {
    Text('Click to view')
      .fontSize(12)
      .fontColor(Color.Red);
    Text(params.text)
      .fontWeight(400)
      .lineHeight(16)
      .fontSize(14)
      .maxLines(1)
      .layoutWeight(1);
    Text('>')
      .fontSize(12)
      .fontColor(Color.Orange);
  }
  .alignItems(VerticalAlign.Center)
  .justifyContent(FlexAlign.SpaceBetween)
  .onClick(() => {
    let componentContent = params.overlayContent[0];
    params.overlayNode.hideComponentContent(componentContent);
  })
  .position({
    x: params.offset.x,
    y: params.offset.y
  })
  .expandSafeArea([SafeAreaType.KEYBOARD])
  .height(35)
  .width('100%')
  .backgroundColor(Color.Grey)
  .hitTestBehavior(HitTestMode.Transparent);
}

@Entry
@Component
struct MainPage {
  @State text: string = 'Team meeting next Monday at 3 PM to review project progress and planning.';
  @StorageProp('bottomRectHeight') bottomRectHeight: number = 0;
  @StorageProp('topRectHeight') topRectHeight: number = 0;
  @StorageLink('componentOffset') componentOffset: Position = { x: 0, y: 1 };
  @State isClicked: boolean = false;
  pageInfos: NavPathStack = new NavPathStack();
  private uiContext: UIContext = this.getUIContext();
  private overlayNode: OverlayManager = this.uiContext.getOverlayManager();
  private overlayContent: ComponentContent<Params>[] = [];
  @Provide params: Params = new Params(this.text, this.pageInfos, this.overlayContent, this.overlayNode, this.componentOffset);

  aboutToAppear(): void {
    let componentContent = new ComponentContent(
      this.uiContext, wrapBuilder<[Params]>(OverlayNode), this.params
    );
    this.overlayNode.addComponentContent(componentContent, 0);
    this.overlayContent.push(componentContent);
  }

  aboutToDisappear(): void {
    let componentContent = this.overlayContent.pop();
    this.overlayNode.removeComponentContent(componentContent);
  }

  build() {
    Navigation(this.pageInfos) {
      Column() {}
    }
    .hideTitleBar(true)
    .hideToolBar(true)
    .backgroundColor('#ffc8c6c6')
    .padding({
      top: this.getUIContext().px2vp(this.topRectHeight)
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • The overlay property is ideal for creating floating announcements or persistent UI layers.
  • Compared to Stack, it reduces component nesting and improves performance.
  • Use ComponentContent for overlays in frequently refreshed pages to avoid performance loss from repeated destruction and recreation.
  • OverlayManager provides fine-grained control over floating UI elements and integrates seamlessly with HarmonyOS navigation and layout systems.

Written by Emincan Ozcan

Top comments (0)