DEV Community

Cover image for Recommended Project Structure, Global Popup, Background Image Filling, Indicator Component Issues
kouwei qing
kouwei qing

Posted on

Recommended Project Structure, Global Popup, Background Image Filling, Indicator Component Issues

【Daily HarmonyOS Next Knowledge】Recommended Project Structure, Global Popup, Background Image Filling, Indicator Component Issues, Semi-Modal Transition Layer

1. How many UIAbilities are recommended for a HarmonyOS project?

What are the main scenarios for using one UIAbility versus multiple UIAbilities?

  1. Regarding project structure design, whether to use one UIAbility + multiple pages or multiple UIAbilities + multiple pages: If mini-programs are not involved, it is recommended to use one UIAbility. Multiple UIAbilities will appear as separate entries in the task list, so the choice should be determined by actual requirements. For development cases, refer to best practices on layered architecture design: https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-layered-architecture-design-V5
  2. Scenarios mainly involve interaction; refer to this document: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/uiability-intra-device-interaction-V5

2. HarmonyOS global popup demo?

Requirement for mutual logout by phone number: When logging in with a phone number on device A, if the same number is logged in on device B, a dialog should pop up on device A to inform the user that the number is logged in on another device and this device has been logged out. The dialog must be visible on any page of the app on device A.

Refer to the following demo:

index.ets
import { GlobalContext } from './GlobalContext';
import { testPromptDialog } from './HttpUtil';
import { promptAction } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  aboutToAppear(): void {
    GlobalContext.getContext().setObject('UIContext', this)
  }
  build() {
    Row() {
      Column() {
        Button("promptAction Popup")
          .onClick(() => {
            testPromptDialog()
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}
GlobalContext.ets

export class GlobalContext {
  private constructor() {
  }

  private static instance: GlobalContext;
  private _objects = new Map<string, Object>();
  public static getContext(): GlobalContext {
    if (!GlobalContext.instance) {
      GlobalContext.instance = new GlobalContext();
    }
    return GlobalContext.instance;
  }
  getObject(value: string): Object | undefined {
    return this._objects.get(value);
  }
  setObject(key: string, objectClass: Object): void {
    this._objects.set(key, objectClass);
  }
}
HttpUtil.ets



let customDialogId: number = 0
@Builder
export function customDialogBuilder(content: String) {
  Column() {
    Text(`Tip: ${content}`).fontSize(20).height("30%")
    Text('Failure Reason: Failure!').fontSize(16).height("30%")
    Row() {
      Button("Confirm").onClick(() => {
        promptAction.closeCustomDialog(customDialogId)
      })
      Blank().width(50)
      Button("Cancel").onClick(() => {
        promptAction.closeCustomDialog(customDialogId)
      })
    }
    .margin({ top: 30 })
  }.height(200).padding(5)
}
export function testPromptDialog() {
  const that = GlobalContext.getContext().getObject('UIContext') as UIContext;
  if (that) {
    promptAction.openCustomDialog({
      builder: customDialogBuilder.bind(that, "Network Request Failed!")
    }).then((dialogId: number) => {
      customDialogId = dialogId;
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

3. How to make the background or image fill the entire phone screen in HarmonyOS?

Refer to immersive development: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-develop-apply-immersive-effects-V5

Typical full-screen UI elements include the status bar, app interface, and bottom navigation bar. The status bar and navigation bar are usually called避让区 (avoidance areas) in immersive layouts, while the area outside is the安全区 (safe area). Developing immersive effects mainly involves adjusting the display of the status bar, app interface, and navigation bar to reduce the abruptness of system UI elements like the status bar and navigation bar, thereby enhancing the user's UI experience.

4. Issues modifying the indicator in HarmonyOS Swiper?

The built-in properties of Swiper do not support setting indicator spacing. To set spacing, custom UI is required. Example code:

class MyDataSource implements IDataSource {
  private list: number[] = []

  constructor(list: number[]) {
    this.list = list
  }

  totalCount(): number {
    return this.list.length
  }

  getData(index: number): number {
    return this.list[index]
  }

  registerDataChangeListener(listener: DataChangeListener): void {
  }

  unregisterDataChangeListener() {
  }
}


@Entry
@Component
struct SwiperExample {
  private swiperController: SwiperController = new SwiperController()
  private data: MyDataSource = new MyDataSource([])
  @State widthLength: number = 0
  @State heightLength: number = 0
  @State currentIndex: number = 0
  // Implement custom animation for navigation dots

  private swiperWidth: number = 0

  private getTextInfo(index: number): Record<string, number> {
    let strJson = getInspectorByKey(index.toString())
    try {
      let obj: Record<string, string> = JSON.parse(strJson)
      let rectInfo: number[][] = JSON.parse('[' + obj.$rect + ']')
      return { 'left': px2vp(rectInfo[0][0]), 'width': px2vp(rectInfo[1][0] - rectInfo[0][0]) }
    } catch (error) {
      return { 'left': 0, 'width': 0 }
    }
  }

  private getCurrentIndicatorInfo(index: number, event: TabsAnimationEvent): Record<string, number> {
    let nextIndex = index
    if (index > 0 && event.currentOffset > 0) {
      nextIndex--
    } else if (index < 3 && event.currentOffset < 0) {
      nextIndex++
    }
    let indexInfo = this.getTextInfo(index)
    let nextIndexInfo = this.getTextInfo(nextIndex)
    let swipeRatio = Math.abs(event.currentOffset / this.swiperWidth)
    let currentIndex = swipeRatio > 0.5 ? nextIndex : index // Switch to the next page when swiped over half.
    let currentLeft = indexInfo.left + (nextIndexInfo.left - indexInfo.left) * swipeRatio
    let currentWidth = indexInfo.width + (nextIndexInfo.width - indexInfo.width) * swipeRatio
    return { 'index': currentIndex, 'left': currentLeft, 'width': currentWidth }
  }

  aboutToAppear(): void {
    let list: number[] = []
    for (let i = 1; i <= 6; i++) {
      list.push(i);
    }
    this.data = new MyDataSource(list)
  }

  build() {
    Column({ space: 5 }) {
      Stack({ alignContent: Alignment.Bottom }) {
        Swiper(this.swiperController) {
          Image($r('app.media.background'))
            .backgroundColor(Color.Red)
          Image($r('app.media.startIcon'))
            .backgroundColor(Color.Red)
          Image($r('app.media.background'))
            .backgroundColor(Color.Red)
          Image($r('app.media.startIcon'))
            .backgroundColor(Color.Red)
          Image($r('app.media.background'))
            .backgroundColor(Color.Red)
          Image($r('app.media.startIcon'))
            .backgroundColor(Color.Red)
        }
        .width('100%')
        .height('100%')
        .cachedCount(2)
        .index(0)
        .autoPlay(false)
        .interval(4000)
        .loop(true)
        .duration(1000)
        .itemSpace(0)
        .indicator(false)
        .curve(Curve.Linear)
        .onChange((index: number) => {
          console.info(index.toString())
          this.currentIndex = index
        })
        .onGestureSwipe((index: number, extraInfo: SwiperAnimationEvent) => {
          console.info("index: " + index)
          console.info("current offset: " + extraInfo.currentOffset)


          let currentIndicatorInfo = this.getCurrentIndicatorInfo(index, extraInfo)
          this.currentIndex = currentIndicatorInfo.index
        })
        .onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {
          console.info("index: " + index)
          console.info("targetIndex: " + targetIndex)
          console.info("current offset: " + extraInfo.currentOffset)
          console.info("target offset: " + extraInfo.targetOffset)
          console.info("velocity: " + extraInfo.velocity)
          this.currentIndex = targetIndex
        })
        .onAnimationEnd((index: number, extraInfo: SwiperAnimationEvent) => {
          console.info("index: " + index)
          console.info("current offset: " + extraInfo.currentOffset)
        })


        Row() {
          LazyForEach(this.data, (item: string, index: number) => {
            Column()
              .width(this.currentIndex === index ? 15 : 15)
              .height(5)
              .margin(0)
              .borderRadius(5)
              .backgroundColor(this.currentIndex === index ? Color.Red : Color.Gray)
          }, (item: string) => item)
        }
        .margin({ bottom: 4 })
      }
    }.width('100%')
    .margin({ top: 5 })
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Does HarmonyOS have an API to customize the corner radius of semi-modal transition layers?

@Entry
@Component
struct Index {
  @State isShow: boolean = false
  @State isShow2: boolean = false
  @State sheetHeight: number = 300;

  @Builder
  myBuilder() {
    Column() {
      Button("change height")
        .margin(10)
        .fontSize(20)
        .onClick(() => {
          this.sheetHeight = 500;
        })

      Button("Set Illegal height")
        .margin(10)
        .fontSize(20)
        .onClick(() => {
          this.sheetHeight = -1;
        })

      Button("close modal 1")
        .margin(10)
        .fontSize(20)
        .onClick(() => {
          this.isShow = false;
        })
    }
    .width('100%')
    .height('100%')
    .backgroundColor("#36D")
    .border({ width: 20, color: "#36D", radius: "200px" })
  }

  build() {
    Column() {
      Button("transition modal 1")
        .onClick(() => {
          this.isShow = true
        })
        .fontSize(20)
        .margin(10)
        .bindSheet($$this.isShow, this.myBuilder(), {
          height: this.sheetHeight,
          maskColor: Color.Red,
          backgroundColor: Color.Red,
          onAppear: () => {
            console.log("BindSheet onAppear.")
          },
          onDisappear: () => {
            console.log("BindSheet onDisappear.")
          }
        })
    }.justifyContent(FlexAlign.Center).width('100%').height('100%')
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)