目录

Immersive Effect Development

Overview

In a typical full-screen application window, the UI elements consist of the top status bar, the main application interface, and the bottom navigation area (displayed as a navigation bar or three-button navigation, depending on user settings). In immersive layouts, the status bar and the navigation area are usually referred to as an avoid area, while the reset of the screen is referred to as a safe area. You can create an immersive effect for your application by fine-tuning the visibility and appearance of the aforementioned elements.

To create an immersive application experience, focus on the following key design elements:

Based on the preceding design requirements, the immersive effect can be implemented in the following methods:

Full-Screen Layout Scheme

The full-screen layout scheme includes the following: Application Extended Layout, Full-screen Display, with Avoid Area Not Hidden and Application Extended Layout with Hidden Avoid Areas.

[h2]Application Extended Layout, Full-screen Display, with Avoid Area Not Hidden

Call the setWindowLayoutFullScreen() API to extend the UI elements to the status bar and navigation area. Then, call the getWindowAvoidArea() and on('avoidAreaChange') APIs to obtain and dynamically listen for the change information about the avoid area, based on which the page layout is dynamically adjusted. Set the attributes such as the color and visibility of the status bar or navigation area to match the UI elements.

  1. Call the setWindowLayoutFullScreen() API to set the window to full screen.

    // EntryAbility.ets
    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    import { window } from '@kit.ArkUI';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    export default class EntryAbility extends UIAbility {
      // ...
    
      onWindowStageCreate(windowStage: window.WindowStage): void {
        windowStage.loadContent('pages/Index', (err, data) => {
          if (err.code) {
            return;
          }
    
          let windowClass: window.Window = windowStage.getMainWindowSync(); // Obtain the main window of the application.
          // 1. Set the window to full screen.
          let isLayoutFullScreen = true;
          windowClass.setWindowLayoutFullScreen(isLayoutFullScreen).then(() => {
            console.info('Succeeded in setting the window layout to full-screen mode.');
          }).catch((err: BusinessError) => {
            console.error(`Failed to set the window layout to full-screen mode. Code is ${err.code}, message is ${err.message}`);
          });
          // Perform steps 2-3.
        });
      }
    }
  2. Call the getWindowAvoidArea() API to obtain the current avoid area, where the window cannot be displayed (such as the status bar and navigation area).

    // EntryAbility.ets
    // 2. Obtain the avoid area.
    let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // Here a navigation bar is used as an example.
    let avoidArea = windowClass.getWindowAvoidArea(type);
    let bottomRectHeight = avoidArea.bottomRect.height; // Obtain the height of the navigation area.
    AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
    
    type = window.AvoidAreaType.TYPE_SYSTEM; // The status bar is used as an example.
    avoidArea = windowClass.getWindowAvoidArea(type);
    let topRectHeight = avoidArea.topRect.height; // Obtain the height of the status bar area.
    AppStorage.setOrCreate('topRectHeight', topRectHeight);
  3. Register a listening function to dynamically obtain the real-time data of the avoid area. Common scenarios for triggering the callback of the avoid area are as follows: transitions between full-screen mode, floating mode, and split-screen mode of the application window; rotation of the application window; transitions between folded and unfolded states of a foldable device; transfer of the application window between multiple devices.

    // EntryAbility.ets
    // 3. Register a listening function to dynamically obtain the data of the avoid area.
    windowClass.on('avoidAreaChange', (data) => {
      if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
        let topRectHeight = data.area.topRect.height;
        AppStorage.setOrCreate('topRectHeight', topRectHeight);
      } else if (data.type == window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
        let bottomRectHeight = data.area.bottomRect.height;
        AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
      }
    });
  4. UI elements in the layout need to avoid blocking the status bar and navigation area.

    The size of the avoid area may be 0. If the obtained avoid area is 0, you should adapt the page area and layout to avoid issues such as content being too close to the edges or being cropped, which could compromise the appearance and functionality of the application interface.

    In the following example, set a top padding with a value the same as the height of the status bar, and a bottom padding with a value the same as the height of the bottom navigation area to avoid the status bar and navigation bar, respectively. If the top and bottom paddings are removed, UI elements would overlap with the status bar and navigation bar. For details, see the two figures in the following step.

    // Index.ets
    @Entry
    @Component
    struct Index {
      @StorageProp('bottomRectHeight')
      bottomRectHeight: number = 0;
      @StorageProp('topRectHeight')
      topRectHeight: number = 0;
    
      build() {
        Column() {
          Row() {
            Text('Top Content').fontSize(40).textAlign(TextAlign.Center).width('100%')
          }.backgroundColor('#2786d9')
    
          Row() {
            Text('Display Content 2').fontSize(30)
          }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
          Row() {
            Text('Display Content 3').fontSize(30)
          }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
          Row() {
            Text('Display Content 4').fontSize(30)
          }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
          Row() {
            Text('Display Content 5').fontSize(30)
          }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
          Row() {
            Text('Bottom Content').fontSize(40).textAlign(TextAlign.Center).width('100%')
          }.backgroundColor('#96dffa')
        }
        .width('100%')
        .height('100%')
        .alignItems(HorizontalAlign.Center)
        .backgroundColor('#d5d5d5')
        .justifyContent(FlexAlign.SpaceBetween)
        // The value of top padding is the same as the height of the status bar area, and the value of bottom padding is the same as the height of the navigation area.
        .padding({
          top: this.getUIContext().px2vp(this.topRectHeight),
          bottom: this.getUIContext().px2vp(this.bottomRectHeight)
        })
      }
    }
  5. Set the text color and background color of the status bar and the display or hiding of the navigation area based on the actual UI display so as to achieve harmonious display effect. By default, the status bar and navigation area are transparent, and the background color of the application interface is transparently transmitted.

    In this example, only two colors are used in UI elements. Therefore, the text color and background color of the status bar are not set, and the navigation area is not hidden.

    Figure1 Layout with avoiding the status bar and navigation area (illustrated with the navigation area as a navigation bar)

    Figure2 Layout without avoiding the status bar and navigation area, resulting in overlapping UI elements (illustrated with the navigation area as a navigation bar)

[h2]Application Extended Layout with Hidden Avoid Areas

In this scenario, both the status bar and navigation area should be hidden, making it ideal for immersive experiences like gaming or watching movies. Users can swipe up from the bottom of the screen to display the navigation bar or three-button navigation.

  1. Call the setWindowLayoutFullScreen() API to set the window to full screen.

    // EntryAbility.ets
    import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
    import { window } from '@kit.ArkUI';
    import { BusinessError } from '@kit.BasicServicesKit';
    
    export default class EntryAbility extends UIAbility {
      // ...
    
      onWindowStageCreate(windowStage: window.WindowStage): void {
        windowStage.loadContent('pages/Index', (err, data) => {
          if (err.code) {
            return;
          }
    
          let windowClass: window.Window = windowStage.getMainWindowSync(); // Obtain the main window of the application.
          // 1. Set the window to full screen.
          let isLayoutFullScreen = true;
          windowClass.setWindowLayoutFullScreen(isLayoutFullScreen).then(() => {
            console.info('Succeeded in setting the window layout to full-screen mode.');
          }).catch((err: BusinessError) => {
            console.error(`Failed to set the window layout to full-screen mode. Code is ${err.code}, message is ${err.message}`);
          });
          // Hide the status bar and navigation area in step 2.
        });
      }
    }
  2. Call the setSpecificSystemBarEnabled() API to display or hide the status bar and navigation area. In this scenario, the status bar and navigation area are hidden.

    // EntryAbility.ets
    // 2. Hide the status bar.
    windowClass.setSpecificSystemBarEnabled('status', false).then(() => {
      console.info('Succeeded in setting the status bar to be invisible.');
    }).catch((err: BusinessError) => {
      console.error(`Failed to set the status bar to be invisible. Code is ${err.code}, message is ${err.message}`);
    });
    // 2. Hide the navigation area.
    windowClass.setSpecificSystemBarEnabled('navigationIndicator', false).then(() => {
      console.info('Succeeded in setting the navigation indicator to be invisible.');
    }).catch((err: BusinessError) => {
      console.error(`Failed to set the navigation indicator to be invisible. Code is ${err.code}, message is ${err.message}`);
    });
  3. You do not need to avoid the navigation area.

    // Index.ets
    @Entry()
    @Component
    struct Index {
      build() {
        Row() {
          Column() {
            Row() {
              Text('Top Content').fontSize(40).textAlign(TextAlign.Center).width('100%')
            }.backgroundColor('#2786d9')
    
            Row() {
              Text('Display Content 2').fontSize(30)
            }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
            Row() {
              Text('Display Content 3').fontSize(30)
            }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
            Row() {
              Text('Display Content 4').fontSize(30)
            }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
            Row() {
              Text('Display Content 5').fontSize(30)
            }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')
    
            Row() {
              Text('Bottom Content').fontSize(40).textAlign(TextAlign.Center).width('100%')
            }.backgroundColor('#96dffa')
          }
          .width('100%')
          .height('100%')
          .alignItems(HorizontalAlign.Center)
          .justifyContent(FlexAlign.SpaceBetween)
          .backgroundColor('#d5d5d5')
        }
      }
    }

Component Safe Area Scheme

If the setWindowLayoutFullScreen() API is not called to set the window to full screen, the window is laid out within the safe area by default.

By default, the window background is displayed in full screen. However, UI elements are arranged in the safe area so that they are not blocked by the status bar or navigation area.

Figure3 Automatic avoidance of the status bar and navigation area

If the colors of the status bar and navigation area do not match the color of application UI elements, use either of the following methods to achieve the immersive effect:

[h2]Principles of the expandSafeArea Attribute

[h2]Background Image and Video Scenario

Set the size of the background image and Video component to the size of the safe area and configure the expandSafeArea attribute.

When using expandSafeArea to extend into safe areas, the Video component's content display area does not support expansion.

// xxx.ets
@Entry
@Component
struct SafeAreaExample1 {
  build() {
    Stack() {
      Image($r('app.media.bg'))
        .height('100%').width('100%')
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM]) // Extend the image component area to the status bar and navigation area.
    }.height('100%').width('100%')
  }
}

[h2]Scrolling Scenario

The expandSafeArea attribute on the scroll container takes effect. However, if the parent component is a scroll container, the expandSafeArea attribute on child components does not take effect. You can use either of the following methods to implement the immersive effect for child components of a scroll container:

  1. Set the same background color for the parent scroll container and the child components, and apply the expandSafeArea attribute to the parent scroll container to expand the safe area.

    // xxx.ets
    @Entry
    @Component
    struct ScrollExample {
      scroller: Scroller = new Scroller()
      private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
      build() {
        Stack({ alignContent: Alignment.TopStart }) {
          Scroll(this.scroller) {
            Column() {
              ForEach(this.arr, (item: number) => {
                Stack() {
                  Text('Display Content ' + item.toString()).fontSize(30)
                }
                .width('80%').padding(20).borderRadius(15).backgroundColor(Color.White).margin({ top:30, bottom:30 })
              }, (item: string) => item)
            }.width('100%').backgroundColor('rgb(213,213,213)')
          }.backgroundColor('rgb(213,213,213)')
          .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
        }.width('100%').height('100%')
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
      }
    }

    Figure4 Setting expandSafeArea for a scroll container to implement the immersive effect

  2. Set the same background color for the parent scroll container and the child components, and set the clipContent attribute of the parent scroll container to ContentClipMode.SAFE_AREA to extend the content clipping area to the avoid area.

    // xxx.ets
    @Entry
    @Component
    struct ScrollExample {
      scroller: Scroller = new Scroller()
      private arr: number[] = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
      build() {
        Stack({ alignContent: Alignment.TopStart }) {
          Scroll(this.scroller) {
            Column() {
              ForEach(this.arr, (item: number) => {
                Stack() {
                  Text('Display Content ' + item.toString()).fontSize(30)
                }
                .width('80%').padding(20).borderRadius(15).backgroundColor(Color.White).margin({ top:30, bottom:30 })
              }, (item: string) => item)
            }.width('100%').backgroundColor('rgb(213,213,213)')
          }.backgroundColor('rgb(213,213,213)')
          .clipContent(ContentClipMode.SAFE_AREA)
        }.width('100%').height('100%')
      }
    }

Figure5 Setting clipContent for a scroll container to implement the immersive effect

[h2]Bottom Tab Scenario

The background color of the tab bar must extend to the navigation area (navigation bar is used as an example here). However, the operable elements in the tab bar must be above the navigation area.

For the tabs at the bottom, the Navigation and Tabs components implement tab extension by default. Ensure that the bottom boundary of the Navigation and Tabs components overlaps with the bottom navigation area. If the expandSafeArea API is explicitly called, the safe area effect is specified by the expandSafeArea parameter.

If a custom tab bar is implemented without using the preceding components, you can set the expandSafeArea attribute for the bottom element to extend its background.

Figure6 Top and bottom UI elements with and without the expandSafeArea setting

// xxx.ets
@Entry
@Component
struct Example {
  build() {
    Column() {
      Row() {
        Text('Top Content').fontSize(40).textAlign(TextAlign.Center).width('100%')
      }.backgroundColor('#2786d9')
      // Expand the safe area to the top edge (status bar).
      .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])

      Row() {
        Text('Display Content 2').fontSize(30)
      }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')

      Row() {
        Text('Display Content 3').fontSize(30)
      }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')

      Row() {
        Text('Display Content 4').fontSize(30)
      }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')

      Row() {
        Text('Display Content 5').fontSize(30)
      }.backgroundColor(Color.White).padding(20).borderRadius(15).width('80%')

      Row() {
        Text('Bottom Content').fontSize(40).textAlign(TextAlign.Center).width('100%')
      }.backgroundColor('#96dffa')
      // Expand the safe area to the bottom edge (navigation area).
      .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
    }
    .width('100%').height('100%')
    .alignItems(HorizontalAlign.Center)
    .backgroundColor('#d5d5d5')
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

[h2]Image and Text Scenario

If the component on the status bar is different from that on the navigation area, extending the window background color or image could not achieve the immersive effect. In this case, you need to configure the expandSafeArea attribute as follows: Set expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.TOP]) for the top component. Set expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.BOTTOM]) for the bottom component.

@Entry
@Component
struct Index {
  build() {
    Swiper() {
      Column() {
        Image($r('app.media.start'))
          .height('50%').width('100%')
          // Expand the image to the status bar.
          .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP])
        Column() {
          Text ('HarmonyOS Lesson 1')
            .fontSize(32)
            .margin(30)
          Text('With a step-by-step learning path, developers regardless of their experience can master the ArkTS declarative development paradigm and experience a simpler and more friendly HarmonyOS application development journey.')
            .fontSize(20).margin(20)
        }.height('50%').width('100%')
        .backgroundColor(Color.White)
        // Expand the background of the text content area to the navigation bar.
        .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
      }
    }
    .width('100%')
    .height('100%')
    // Disable the default clip effect of the Swiper component so that subnodes can be drawn outside it.
    .clip(false)
  }
}