DEV Community

Cover image for Global Font, Non-null Assertion, Component Dialog Usage, List Scroll to Top, Soft Keyboard Control
kouwei qing
kouwei qing

Posted on

Global Font, Non-null Assertion, Component Dialog Usage, List Scroll to Top, Soft Keyboard Control

[Daily HarmonyOS Next Knowledge] Global Font, Non-null Assertion, Component Dialog Usage, List Scroll to Top, Soft Keyboard Control

1、HarmonyOS Global Font

  1. How to globally replace the system default font with a custom font without setting it for each page or component?
  2. How to globally set the app font to not change with system font size and weight?

1. Global Font

1.1 To use a custom font globally, register it in the onWindowStageCreate lifecycle of EntryAbility.ets via the windowStage.loadContent callback:

import font from '@ohos.font';
onWindowStageCreate(windowStage: window.WindowStage): void {
  windowStage.loadContent('pages/registerFont', (err, data) => {
  if (err.code) {
  return;
}
font.registerFont({
  familyName: 'Beacon',
  familySrc: $rawfile('font/Beacon.otf')
})
font.registerFont({
  familyName: 'SF',
  familySrc: $rawfile('font/SF-Pro-Text-Italic.ttf')
})
});
}
Enter fullscreen mode Exit fullscreen mode

1.2 In registerFont.ets, use the registered familyName in the page:

@Entry
@Component
struct registerFont {
  @State message: string = 'test'
  build() {
    Column() {
      Text(this.message)
        .align(Alignment.Center)
        .fontSize(50)
        .fontFamily('Beacon')
      Text(this.message)
        .align(Alignment.Center)
        .fontSize(50)
        .fontFamily('SF')
    }.width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Reference link: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-font-V5

  1. Currently, only font sizes set in fp (font pixel) units change with the system. To prevent changes, use vp units instead. By default, 1 fp = 1 vp. If the user scales the system font, 1 fp = 1 vp * scale factor.
  2. Pixel units: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-pixel-units-V5
  3. Font pixel unit (fp): https://developer.huawei.com/consumer/cn/doc/design-guides/design-layout-basics-0000001795579413#section1964738154814
  4. Use dynamic attributes or custom classes implementing the AttributeModifier interface to define fixed styles for Text components. Reference: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-universal-attributes-attribute-modifier-V5

2、What is the role of the exclamation mark after a variable in HarmonyOS?

What does the exclamation mark after a variable do? Multiple exclamation marks can be added without errors. What's the difference between one and multiple exclamation marks?

Non-null assertion operator: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/introduction-to-arkts-V5

3、HarmonyOS API12: Component usage scenario for Dialog

There are two pages, A and B. Page A pops up a CustomDialog, pushes to page B, and returns to page A to refresh the dialog data. Current issues:

  1. The dialog is closed before entering page B (if not closed, it remains when entering page B).
  2. The dialog is reopened when returning from page B to A (unfriendly to users due to repeated openings).

Expectation: The dialog should not be closed before entering B, and should not be reopened after returning to A.

Question: Why does the dialog have a higher hierarchy than pages? The dialog on page A should belong to A, but why does it persist when entering page B?

Use NavDestinationMode.DIALOG to implement custom popups. Refer to the demo:

interface RouterParams {
  name?: string,
  onPop?: (data: PopInfo) => void
}

// Encapsulated routing utility class with custom popup registration
class AppRouter {
  private static instance = new AppRouter();
  private pathStack: NavPathStack = new NavPathStack();

  public static getInstance(): AppRouter {
    return AppRouter.instance;
  }

  public getPathStack(): NavPathStack {
    return this.pathStack;
  }

  private pushPath(name: string): void {
    this.pathStack.pushPath({ name: name })
  }

  public static push(name: string): void {
    AppRouter.instance.pushPath(name);
  }

  public static openDialog(name: string, params?: RouterParams): void {
    AppRouter.instance.pathStack.pushPath({
      name: name, param: params, onPop: (data: PopInfo) => {
        if (params?.onPop) {
          params.onPop!(data);
        }
      }
    });
  }

  public static pop(): void {
    AppRouter.instance.pathStack.pop();
  }
}

@Component
  // NavDestinationMode.DIALOG
struct DefaultDialog {
  build() {
    NavDestination() {
      Stack({ alignContent: Alignment.Center }) {
        Column() {
        }
        .width("100%")
        .height("100%")
        .backgroundColor('rgba(0,0,0,0.5)')
        .transition(
          TransitionEffect.OPACITY.animation({
            duration: 300,
            curve: Curve.Friction
          })
        )
        .onClick(() => {
          AppRouter.pop();
        })

        Column() {
          Text("dialogA")
            .fontColor(Color.White)
          Button("push pageC", { stateEffect: true, type: ButtonType.Capsule })
            .onClick(() => {
              AppRouter.push("pageC")
            })
        }
        .width("50%")
        .height("30%")
        .backgroundColor('#ffae2d2d')
        .transition(
          TransitionEffect.scale({ x: 0, y: 0 }).animation({
            duration: 300,
            curve: Curve.Friction
          })
        )
      }
      .width("100%")
      .height("100%")
    }
    .mode(NavDestinationMode.DIALOG)
    .hideTitleBar(true)
  }
}

@Component
struct PageA {
  @Consume('pageInfos') pageInfos: NavPathStack;

  build() {
    NavDestination() {
      Button('push dialogA', { stateEffect: true, type: ButtonType.Capsule })
        .onClick(() => {
          AppRouter.openDialog("DefaultDialog");
        }).margin(10)
    }.title('PageA')
  }
}

@Component
struct PageC {
  @Consume('pageInfos') pageInfos: NavPathStack;

  build() {
    NavDestination() {
      Column() {
        Text('pageC')
          .margin(10)
        Button('返回', { stateEffect: true, type: ButtonType.Capsule })
          .onClick(() => {
            AppRouter.pop();
          }).margin(10)
      }
    }.mode(NavDestinationMode.STANDARD)
  }
}

@Entry
@Component
struct pageRoot01 {
  @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack()
  // Visibility control set to non-occupying
  @State visible: Visibility = Visibility.None

  @Builder
  PagesMap(name: string) {
    if (name == 'pageA') {
      PageA()
    } else if (name == 'pageC') {
      PageC()
    } else if (name == 'DefaultDialog') {
      DefaultDialog()
    }
  }

  build() {
    // Register NavPathStack in the root page
    Navigation(AppRouter.getInstance().getPathStack()) {
      Stack({ alignContent: Alignment.Center }) {
        Column() {
          Button('push PageA', { stateEffect: true, type: ButtonType.Capsule })
            .onClick(() => {
              AppRouter.push('pageA')
            })
        }
      }
      .height("100%")
      .width("100%")
    }
    .hideTitleBar(true)
    .navDestination(this.PagesMap)
  }
}
Enter fullscreen mode Exit fullscreen mode

4、HarmonyOS Scroller cannot scroll to the top, and the bottom input box pushes the page up

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';
  private list: string[] = []
  private scroller = new Scroller()

  aboutToAppear(): void {
    for (let i = 0; i < 200; i++) {
      this.list.push(`选项${i}`)
    }
  }

  build() {
    Column() {
      Text('我是固定标题')
        .fontColor(Color.White)
        .backgroundColor(Color.Red)

      List({ scroller: this.scroller }) {
        ForEach(this.list, (item: string) => {
          ListItem() {
            Text(item)
              .padding(10)
          }
        })
      }
      .layoutWeight(1)
      .onAppear(() => {
        this.scroller.scrollEdge(Edge.Bottom)
        this.scroller.scrollToIndex(this.list.length - 1)
        this.scroller.scrollToIndex(this.list.length)
      })

      TextInput()
    }
    .height('100%')
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Issues:

  1. After loading data, the list needs to scroll to the bottom (chat page scenario). Using this.scroller.scrollEdge and this.scroller.scrollToIndex does not scroll to the bottom.
  2. With a bottom input box, the entire page is pushed up when focused. How to fix the top control and allow normal list scrolling?

Solutions:

  1. Use initialIndex: this.list.length - 1:
List({ scroller: this.scroller, initialIndex: this.list.length - 1 }) {
  ForEach(this.list, (item: string) => {
    ListItem() {
      Text(item)
        .padding(10)
    }
  })
}
Enter fullscreen mode Exit fullscreen mode
  1. Set windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE). Reference: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-universal-attributes-expand-safe-area-V5#getkeyboardavoidmode

5、HarmonyOS methods to actively pop up and close the soft keyboard

When entering a search page, the soft keyboard needs to be actively popped up before searching and closed after searching.

@Entry
@Component
struct TextInputPage {
  controller: TextInputController = new TextInputController()
  @State inputValue: string = ""

  build() {
    RelativeContainer() {
      TextInput({ controller: this.controller, text: this.inputValue })
        .key('TextInput')
        .margin(10)
        .border({ width: 1 })
        .height('48vp')
        .defaultFocus(true)
    }
    .height('100%')
    .width('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)