DEV Community

Cover image for Resource to RGBA Conversion, Web Component Safe Area, Custom Dialog Closure, Pixel Object Passing
kouwei qing
kouwei qing

Posted on

Resource to RGBA Conversion, Web Component Safe Area, Custom Dialog Closure, Pixel Object Passing

【Daily HarmonyOS Next Knowledge】 Resource to RGBA Conversion, Web Component Safe Area, Custom Dialog Closure, Pixel Object Passing, List Performance Optimization

1. Are there any system methods in HarmonyOS to convert ResourceColor to ordinary RGBA?

Are there any system methods to convert ResourceColor to ordinary RGBA?

Currently, this capability is unavailable. It is necessary to limit the parameter type and define a custom conversion method to transform color values of string or number type into the corresponding RGBA type.

2. After setting expandSafeArea for a HarmonyOS web component, the keyboard still pushes up bottom elements when popped up?

Monitor the pop-up and retraction of the soft keyboard. When the soft keyboard pops up, set the sub-window height to screen height minus soft keyboard height; when the soft keyboard retracts, set the sub-window height to screen height.

import webview from '@ohos.web.webview';
import window from '@ohos.window';

@Entry
@Component
export struct SubWindowPage {
  @State webViewVisibility: Visibility = Visibility.Visible;
  private pageWidth = 320;
  private pageHeight = 500;
  private controller: webview.WebviewController = new webview.WebviewController();
  @State flexAlign: FlexAlign = FlexAlign.Center;
  @State screenHeight: number | string = '100%';

  aboutToAppear() {
    window.getLastWindow(getContext(this)).then(currentWindow => {
      // Monitor soft keyboard pop-up and retraction
      currentWindow.on('avoidAreaChange', async data => {
        let property = currentWindow.getWindowProperties();
        let avoidArea = currentWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_KEYBOARD);
        this.screenHeight = px2vp(property.windowRect.height - avoidArea.bottomRect.height);
      });
    });
  }

  build() {
    Stack() {
      Column() {
        Web({ src: $rawfile('index.html'), controller: this.controller })
          .javaScriptAccess(true)
          .fileAccess(false)
          .zoomAccess(false)
          .domStorageAccess(true)
          .onlineImageAccess(true)
          .horizontalScrollBarAccess(false)
          .verticalScrollBarAccess(false)
          .cacheMode(CacheMode.Online)
          .width(this.pageWidth)
          .height(this.pageHeight)
          .border({ radius: 6 })
          .visibility(this.webViewVisibility)
          .backgroundColor(Color.Pink);
      }
      .justifyContent(this.flexAlign)
      .alignItems(HorizontalAlign.Center)
      .width('100%')
      .height('100%');
    }
    .width('100%')
    .height(this.screenHeight)
    .backgroundColor('#999955')
    .alignContent(Alignment.Center);
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Calling the close() method on child controls within a HarmonyOS CustomDialog is ineffective?

The controller is undefined when called, making it impossible to close the dialog.

The controller in the @CustomDialog decorator must not be declared with ?:, and when using the component, the dialogController should be declared within the current component, not in a click event. Declaring it in an event causes the this in the CustomDialog's child components to not reference the current controller, preventing the dialog from closing. Modify the component as follows:

@Entry
@Component
struct CustomDialogUser {
  @Builder mycomp() {
    Button('showtosast')
      .onClick(() => {
        this.dialogController.open();
      });
  }

  dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({
      cancel: () => { this.onCancel(); },
      confirm: () => { this.onAccept(); },
    }),
  });

  onCancel() {
    console.info('Callback when the first button is clicked');
  }

  onAccept() {
    console.info('Callback when the second button is clicked');
  }

  build() {
    Column() {
      this.mycomp();
    }.width('100%').margin({ top: 5 });
  }
}

@CustomDialog
struct CustomDialogExample {
  cancel?: () => void;
  confirm?: () => void;
  controller: CustomDialogController;

  build() {
    Column() {
      Text('I am the content').fontSize(20).margin({ top: 10, bottom: 10 });
      Flex({ justifyContent: FlexAlign.SpaceAround }) {
        Button('cancel')
          .onClick(() => {
            this.controller.close();
            if (this.cancel) {
              this.cancel();
            }
          }).backgroundColor(0xffffff).fontColor(Color.Black);
        Button('confirm')
          .onClick(() => {
            this.controller.close();
            if (this.confirm) {
              this.confirm();
            }
          }).backgroundColor(0xffffff).fontColor(Color.Red);
      }.margin({ bottom: 10 });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

When passing the builder, this does not reference the original controller. To avoid changes in this reference, declare the following code in TopNavigationView:

@Builder MenuBuilder() {
  Text('123')
    .width(20)
    .height(20)
    .onClick(() => {
      this.dialogController.open();
    });
}

dialogController: CustomDialogController = new CustomDialogController({
  builder: DescriptionDialog(),
  alignment: DialogAlignment.Center
});
Enter fullscreen mode Exit fullscreen mode

If the builder is declared in LeaderBoardPage, this in the builder refers to LeaderBoardPage. After passing it to TopNavigationView, this refers to TopNavigationView, which does not declare dialogController, making it impossible to close the dialog. For the issue of hardcoding the dialog, the dialog's child component is builder: DescriptionDialog(), and modifying the DescriptionDialog component will update the dialog form. In this builder, this refers to CustomDialogController, allowing the dialog to be opened.

4. Can the HarmonyOS PixelMap type not be passed from parent to child components?

The parent component obtains image data as base64, converts it to a Pixelmap, and passes it to the child component using the @State and @Prop decorators, but it cannot be displayed.

Reference demo for dynamic pixelmap passing in components:

@Observed
class Params {
  imagePixelMap: image.PixelMap | undefined;

  constructor(imaagePixelMap: image.PixelMap | undefined) {
    this.imagePixelMap = imaagePixelMap;
  }
}

@Component
struct ObjectLinkChild {
  @ObjectLink pixelMapParams: Params;

  build() {
    Column() {
      Image(this.pixelMapParams.imagePixelMap).width(100).height(100);
    }
  }
}

@Builder
function imageBuilder(params: Params) {
  ObjectLinkChild({ pixelMapParams: params });
}

@Entry
@Component
struct ImageExample {
  @State imagePixelMap: image.PixelMap | undefined = undefined;
  @State pixelMapParams: Params = new Params(this.imagePixelMap);
  private baseNode: MyNodeController = new MyNodeController(this.imagePixelMap);
  wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder<[Params]>(imageBuilder);

  async aboutToAppear() {
    this.imagePixelMap = await this.getPixmapFromMedia($r('app.media.app_icon'));
    let node = new BuilderNode<[Params]>(this.getUIContext());
    node.build(this.wrapBuilder, this.pixelMapParams);
    this.baseNode.setNode(node);
  }

  build() {
    Column() {
      Image(this.imagePixelMap).enableAnalyzer(true).width(200).height(200);
    }
  }

  private async getPixmapFromMedia(resource: Resource) {
    let unit8Array = await getContext(this)?.resourceManager?.getMediaContent({
      bundleName: resource.bundleName,
      moduleName: resource.moduleName,
      id: resource.id
    });
    let imageSource = image.createImageSource(unit8Array.buffer.slice(0, unit8Array.buffer.byteLength));
    let createPixelMap: image.PixelMap = await imageSource.createPixelMap({ desiredPixelFormat: image.PixelMapFormat.RGBA_8888 });
    await imageSource.release();
    return createPixelMap;
  }
}

class MyNodeController extends NodeController {
  private rootNode: BuilderNode<[Params]> | null = null;
  private pixelMap: image.PixelMap | undefined = undefined;
  private wrapBuilder: WrappedBuilder<[Params]> = wrapBuilder(imageBuilder);

  constructor(pixelMap: image.PixelMap | undefined) {
    super();
    this.pixelMap = pixelMap;
  }

  setNode(node: BuilderNode<[Params]> | null) {
    this.rootNode = node;
  }

  makeNode(uiContext: UIContext): FrameNode | null {
    if (this.rootNode === null) {
      this.rootNode = new BuilderNode(uiContext);
      this.rootNode.build(this.wrapBuilder, new Params(this.pixelMap));
    }
    return this.rootNode.getFrameNode();
  }
}
Enter fullscreen mode Exit fullscreen mode

5. HarmonyOS Tab + List performance optimization?

class Book {
  bookId: string;
}

prices = new Map<string, Price>; // Mapping from book ID to price data
tabList: Book[][] = []; // Lists under all tabs
Enter fullscreen mode Exit fullscreen mode

There are multiple tabs under Tabs, each with a list whose data source is tabList[tabIndex]. When building ListItems, prices are looked up from prices to display book prices. The backend continuously pushes real-time book prices (high frequency). Upon receiving real-time prices, prices is updated, and data changes notify corresponding list items to redraw their views.

  • Actual testing shows list scrolling is卡顿.
  • Tried optimizing with LazyForEach.
  • After app startup, displaying the list under the first tab makes scrolling smooth.
  • After switching to multiple tabs, scrolling becomes卡顿.

Preliminary conclusion after analysis: After switching tabs, the new tab and its list are built. At this point, each tab (visible or not) has its own list. When the backend pushes price updates to the price map, list items with price updates in all tabs are redrawn. How to prevent invisible views from redrawing?

Use the custom component freezing feature: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-custom-components-freeze-V5

The custom component freezing feature is designed to optimize performance for complex UI pages, especially scenarios with multiple page stacks, long lists, or grid layouts. In these cases, when state variables are bound to multiple UI components, changes can trigger refreshes of numerous UI components, leading to interface卡顿 and response delays. To enhance refresh performance for such heavy UI interfaces, developers can attempt to use the custom component freezing feature.

The working principle of component freezing:

  1. Developers activate the component freezing mechanism by setting the freezeWhenInactive property.
  2. Once enabled, the system only updates active custom components, allowing the UI framework to minimize the update scope to only the active (visible) custom components, thereby improving refresh efficiency in complex UI scenarios.
  3. When an inactive custom component becomes active again, the state management framework performs necessary refresh operations to ensure correct UI display.

In summary, component freezing aims to optimize UI refresh performance in complex interfaces. In scenarios with multiple invisible custom components (e.g., multiple page stacks, long lists, or grids), component freezing enables on-demand refresh: only the currently visible custom components are refreshed, while refreshing invisible custom components is deferred until they become visible.

Note that component active/inactive is not equivalent to visibility. Component freezing currently applies only to the following scenarios:

  1. Page routing: The top-of-stack page is active, and non-top invisible pages are inactive.
  2. TabContent: Only custom components in the currently displayed TabContent are active; others are inactive.
  3. LazyForEach: Only custom components in the currently displayed LazyForEach are active; components in cached nodes are inactive.
  4. Navigation: Custom components in the currently displayed NavDestination are active; components in other undisplayed NavDestinations are inactive.
  5. Component reuse: Components entering the reuse pool are inactive; nodes mounted from the reuse pool are active.

Other scenarios, such as masked components in a Stack layout, are not considered inactive despite being invisible and thus are not covered by component freezing.

Top comments (0)