DEV Community

Cover image for Text Component, Navigation Top-Right Button, Simulated Dialog, Left-Aligned Tabs, Offscreen Rendering
kouwei qing
kouwei qing

Posted on

Text Component, Navigation Top-Right Button, Simulated Dialog, Left-Aligned Tabs, Offscreen Rendering

[Daily HarmonyOS Next Knowledge] Text Component, Navigation Top-Right Button, Simulated Dialog, Left-Aligned Tabs, Offscreen Rendering

1. HarmonyOS Text Component UI Styling?

How to implement a Text control with a small icon at the header, limited to 2 lines, with ellipsis and a differently colored "View More" when content overflows.

Use the RichEditor component. Reference: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-basic-components-richeditor-V5

Main supported attributes:

customKeyboard

customKeyboard(value: CustomBuilder, options?: KeyboardOptions)

Sets a custom keyboard. When a custom keyboard is set, the system input method does not open after the input box is activated; instead, the specified custom component is loaded. The height of the custom keyboard can be set via the height attribute of the custom component's root node; the width uses the system default and cannot be set. The custom keyboard cannot obtain focus but intercepts gesture events, and it closes by default when the input control loses focus. If the device supports camera input, setting a custom keyboard disables camera input for the input box.

bindSelectionMenu

bindSelectionMenu(spanType: RichEditorSpanType, content: CustomBuilder, responseType: ResponseType | RichEditorResponseType, options?: SelectionMenuOptions)

Sets a custom selection menu. When the custom menu is too long, nesting a Scroll component is recommended to avoid keyboard obstruction.

copyOptions

copyOptions(value: CopyOptions)

Sets whether the component supports text copy-paste. When copyOptions is not CopyOptions.None, long-pressing the component content pops up a text selection dialog. If a custom selection menu is set via bindSelectionMenu, the custom menu is displayed. Setting copyOptions to CopyOptions.None disables copy and cut functions.

enableDataDetector11+

enableDataDetector(enable: boolean)

Enables/disables special entity recognition in text. This interface relies on the device's underlying text recognition capability; otherwise, the setting is ineffective. When enableDataDetector is true and dataDetectorConfig is not set, all entity types are recognized by default, with their color and decoration changed to:

color: '#ff007dff'
decoration:{
  type: TextDecorationType.Underline,
  color: '#ff007dff',
  style: TextDecorationStyle.SOLID
}
Enter fullscreen mode Exit fullscreen mode

Tapping or right-clicking an entity pops up a corresponding operation menu based on the entity type; left-clicking directly triggers the first menu option. This function does not apply to text in addBuilderSpan nodes. When copyOption is CopyOptions.None, the entity menu lacks text selection and copy functions.

dataDetectorConfig11+

dataDetectorConfig(config: TextDataDetectorConfig)

Sets text recognition configuration, which takes effect only when enableDataDetector is true. When two entities A and B overlap:

  1. If A ⊂ B, retain B; otherwise, retain A.
  2. If neither A ⊂ B nor B ⊂ A, retain the entity with the smaller start index.

2. How to Add a Top-Right Button to NavDestination in HarmonyOS?

NavDestination supports adding top-right buttons via menus. Refer to: https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-navigation.md#%E7%A4%BA%E4%BE%8B2

@Entry
@Component
struct Index {
  pathStack: NavPathStack = new NavPathStack()

  private menuItems: Array<NavigationMenuItem> = [
    {
      value: "1",
      icon: 'resources/base/media/startIcon.png',
    },
    {
      value: "2",
      icon: 'resources/base/media/startIcon.png',
      isEnabled: false,
    },
    {
      value: "3",
      icon: 'resources/base/media/startIcon.png',
      isEnabled: true,
    }
  ]

  build() {
    NavDestination() {
      Column() {
        Button('pushPathByName', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pathStack.pushPathByName('pageOne', null)
          })
      }.width('100%').height('100%')
    }.title('pageTwo')
    .menus(this.menuItems)
    .onBackPressed(() => {
      this.pathStack.pop()
      return true
    })
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack;
      console.log("current page config info is " + JSON.stringify(context.getConfigInRouteMap()))
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Simulating a Dialog with window.createWindow in HarmonyOS?

Using window.createWindow to simulate a dialog causes physical back buttons to fail in secondary pages. Use createSubWindow to avoid this. Refer to the demo:

// EntryAbility.ets
onWindowStageCreate(windowStage: window.WindowStage): void {
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

  windowStage.loadContent('pages/Index', (err, data) => {
    if (err.code) {
      hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
      return;
    }
    AppStorage.setOrCreate("windowStage", windowStage);
    hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
  });
}

// Index.ets (main page)
import { window } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State message: string = 'Create Subwindow';
  @State windowStage: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.windowStage.createSubWindow("mySubWindow", (err, windowClass) => {
              if (err.code > 0) {
                console.error("failed to create subWindow Cause:" + err.message)
                return;
              }
              try {
                windowClass.setUIContent("pages/subPage", () => {
                  windowClass.setWindowBackgroundColor("#00000000")
                });
                windowClass.moveWindowTo(0, 200)
                windowClass.resize(vp2px(300), vp2px(300))
                windowClass.showWindow();
                windowClass.setWindowLayoutFullScreen(true);
              } catch (err) {
                console.error("failed to create subWindow Cause:" + err)
              }
            })
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

// subPage.ets (subwindow page)
import { router, window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct SubPage {
  @State message: string = 'SUBPAGE';
  @State windowStage: window.WindowStage = AppStorage.get("windowStage") as window.WindowStage;

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Button("Jump")
          .onClick(() => {
            let subWindowID: number = window.findWindow("mySubWindow").getWindowProperties().id
            let mainWindowID: number = this.windowStage.getMainWindowSync().getWindowProperties().id
            let promise = window.shiftAppWindowFocus(subWindowID, mainWindowID);
            promise.then(() => {
              console.info('Succeeded in shifting app window focus');
            }).catch((err: BusinessError) => {
              console.error('Failed to shift app window focus. Cause: ' + JSON.stringify(err));
            })
            this.windowStage.getMainWindowSync().getUIContext().getRouter().pushUrl({ url: "pages/routerPage" }, router.RouterMode.Single);
          })
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor(Color.Red)
  }
}

// routerPage.ets (jumped page)
@Entry
@Component
struct RouterPage {
  @State message: string = 'RouterPage';

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor(Color.Pink)
  }
}
Enter fullscreen mode Exit fullscreen mode

4. How to Left-Align Tabs in HarmonyOS When tabs.width('100%').barWidth('90%') Centers the Tab Bar by Default?

Refer to the demo:

@Entry
@Component
struct TabsExample1 {
  @State tabArray: Array<number> = [0, 1, 2]
  @State focusIndex: number = 0
  @State pre: number = 0
  @State index: number = 0
  private controller: TabsController = new TabsController()
  @State test: boolean = false

  @Builder
  Tab(tabName: string, tabItem: number, tabIndex: number) {
    Row({ space: 20 }) {
      Text(tabName).fontSize(18)
    }
    .justifyContent(FlexAlign.Center)
    .constraintSize({ minWidth: 35 })
    .width(100)
    .height(60)
    .borderRadius({ topLeft: 10, topRight: 10 })
    .onClick(() => {
      this.controller.changeIndex(tabIndex)
      this.focusIndex = tabIndex
    })
  }

  build() {
    Column() {
      Column() {
        Row({ space: 7 }) {
          Scroll() {
            Row() {
              ForEach(this.tabArray, (item: number, index: number) => {
                this.Tab("Tab " + item, item, index)
              })
            }
            .justifyContent(FlexAlign.Start)
          }
          .align(Alignment.Start)
          .scrollable(ScrollDirection.Horizontal)
          .scrollBar(BarState.Off)
          .width('90%')
        }
        .alignItems(VerticalAlign.Bottom)
        .width('100%')
      }
      .alignItems(HorizontalAlign.Start)
      .width('100%')

      Tabs({ barPosition: BarPosition.Start, controller: this.controller }) {
        ForEach(this.tabArray, (item: number, index: number) => {
          TabContent() {
            Text('Content of Page ' + item)
              .height(300)
              .width('100%')
              .fontSize(30)
          }
          .backgroundColor(Color.Pink)
        })
      }
      .width('100%')
      .barHeight(0)
      .animationDuration(100)
      .onChange((index: number) => {
        console.log('foo change')
        this.focusIndex = index
      })
    }
    .height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Does OffscreenCanvasRenderingContext2D in HarmonyOS Support Automatic Line Breaks for Text Drawing?

Yes. Consider the following methods:

  1. Multi-line text drawing: The OffscreenCanvasRenderingContext2D object provides fillText and strokeText methods. Set lineHeight and maxWidth to achieve automatic line breaks.
  2. Path drawing: Define a path and use fillText/strokeText to draw text along the path for line breaks.
  3. Layout management: Use layout managers (e.g., Flexbox or Grid) in the offscreen canvas component, setting appropriate width and height to ensure multi-line text displays correctly.

These methods enable automatic line breaks when drawing text with OffscreenCanvasRenderingContext2D.

Top comments (0)