Zum Inhalt springen

Custom Dialog Closure, WaterFlow Nesting, Status Bar Color, Scroll吸附 (Scroll Snapping), Scroll Animation

[Daily HarmonyOS Next Knowledge] Custom Dialog Closure, WaterFlow Nesting, Status Bar Color, Scroll吸附 (Scroll Snapping), Scroll Animation

1. Issue with automatic closure of custom dialog in HarmonyOS

A privacy policy pop-up is set on the launch page with autoCancel: false. The UI uses Text(), ContainerSpan(), and Span() components with click events that navigate to the policy page using router.pushUrl(). However, the dialog disappears while navigating, even though no close() is called in the click event.

Solution: Simulate dialog effects using the Stack component.

import router from '@ohos.router';

@Entry
@Component
struct First {
  @State textValue: string = 'Hello World'
  @State visible: Visibility = Visibility.None

  build() {
    Stack() {
      Row() {
        Column() {
          Text('Hello World')
            .fontSize(50)
            .fontWeight(FontWeight.Bold)
          Button('click')
            .onClick(() => {
              console.log("hit me!")
              this.visible = this.visible === Visibility.Visible ? Visibility.None : Visibility.Visible
            })
            .backgroundColor(0x777474)
            .fontColor(0x000000)
        }
        .width('100%')
      }
      .height('100%')
      .backgroundColor(0x885555)

      Text('')
        .onClick(() => {
          this.visible = this.visible === Visibility.Visible ? Visibility.None : Visibility.Visible
        })
        .width('100%')
        .height('100%')
        .opacity(0.16)
        .backgroundColor(0x000000)
        .visibility(this.visible)

      Column() {
        GridRow({
          columns: { xs: 1, sm: 4, md: 8, lg: 12 },
          breakpoints: { value: ["400vp", "600vp", "800vp"], reference: BreakpointsReference.WindowSize }
        }) {
          GridCol({
            span: { xs: 1, sm: 2, md: 4, lg: 8 },
            offset: { xs: 0, sm: 1, md: 2, lg: 2 }
          }) {
            Column() {
              Text('Change text').fontSize(20).margin({ top: 10, bottom: 10 })
              TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
                .onChange((value: string) => this.textValue = value)
              Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 })
              Flex({ justifyContent: FlexAlign.SpaceAround }) {
                Button('cancel')
                  .onClick(() => this.visible = Visibility.None)
                  .backgroundColor(0xffffff).fontColor(Color.Black)
                Button('jump')
                  .onClick(() => router.pushUrl({ url: 'pages/Second' }))
                  .backgroundColor(0xffffff).fontColor(Color.Red)
              }.margin({ bottom: 10 })
            }
            .backgroundColor(0xffffff)
            .visibility(this.visible)
            .clip(true)
            .borderRadius(20)
          }
        }
      }.width('95%')
    }
  }
}

2. Nesting issue with WaterFlow in HarmonyOS

When WaterFlow is nested in a parent List control (using lazy-loaded data via LazyForEach with cachedCount: 5), if WaterFlow has 100 items, all 100 WaterFlowItems load when the List scrolls to WaterFlow (triggering onAppear()). Will this cause卡顿 (lag) for large data (e.g., 1000 items)? How to control the number of pre-loaded WaterFlowItems?

Answer: Large data volumes do not cause lag. For controlling pre-loaded WaterFlowItem counts, refer to:

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/performance/waterflow_optimization.md

3. Modifying status bar color with setWindowSystemBarProperties in HarmonyOS

When calling the system API setWindowSystemBarProperties to modify the status bar color, the status bar retains the app’s color during scrolling. Should we use the system API or custom components for status bar color changes?

Solution: Set the window to full-screen layout instead of using setWindowSystemBarProperties:

mainWindowClass.setWindowLayoutFullScreen(true, (err: BusinessError) => {
  if (err.code) {
    console.error('Failed to set full-screen layout. Cause:' + JSON.stringify(err));
    return;
  }
  console.info('Succeeded in setting full-screen layout.');
});

4. Jitter issue when mimicking AppBarLayout吸附 (snapping) with Scroll in HarmonyOS

Using onScroll to move components above Scroll for snapping causes jitter at the top, especially with onWillScroll and onDidScroll.

Cause: Jitter occurs because animations are used for returning to the top, but Text lacks animation. Avoid using margin during scrolling for snapping.

enum ScrollPosition {
  start,
  center,
  end
}

class ItemClass {
  content: string = '';
  color: Color = Color.White;
}

@Entry
@Component
struct NestedScrollDemo {
  @State listPosition: number = ScrollPosition.start;
  @State scrollPosition: number = ScrollPosition.start;
  @State showTitle: boolean = false;
  @State currentYOffset: number = 0;
  private arr: ItemClass[] = [];
  private colorArr: Color[] = [Color.White, Color.Blue, Color.Brown, Color.Green, Color.Gray];
  private scrollerForScroll: Scroller = new Scroller();
  private scrollerForList: Scroller = new Scroller();
  private scrollerForTitle: Scroller = new Scroller();
  @State currentIndex: number = 0;

  aboutToAppear() {
    for (let i = 0; i < 6; i++) {
      this.arr.push({ content: i.toString(), color: this.colorArr[i % 5] });
    }
  }

  @Builder
  myBuilder() {
    Row() {
      List({ space: 2, initialIndex: 0, scroller: this.scrollerForTitle }) {
        ForEach(this.arr, (item: ItemClass, index) => {
          ListItem() {
            Column() {
              Text(item.content);
              Divider().color('#000000').strokeWidth(5)
                .visibility(index === this.currentIndex ? Visibility.Visible : Visibility.Hidden)
            }
            .width('25%')
            .height(50)
            .onClick(() => {
              this.scrollerForList.scrollToIndex(index);
              this.scrollerForScroll.scrollEdge(Edge.Bottom);
            })
          }
        })
      }
      .listDirection(Axis.Horizontal)
      .scrollBar(BarState.Off)
    }
    .backgroundColor('#ffe2d0d0')
    .alignItems(VerticalAlign.Center)
  }

  build() {
    Stack({ alignContent: Alignment.Top }) {
      Scroll(this.scrollerForScroll) {
        Column() {
          Image($r('app.media.app_icon')).width("100%").height("40%")
          this.myBuilder();

          List({ space: 10, scroller: this.scrollerForList }) {
            ForEach(this.arr, (item: ItemClass, index) => {
              ListItem() {
                Column() {
                  Text(item.content)
                }
                .width('100%')
                .height(500)
                .backgroundColor(item.color)
              }.width("100%").height(500)
              .onVisibleAreaChange([0.8], (isVisible) => {
                if (isVisible) this.currentIndex = index;
                this.scrollerForTitle.scrollToIndex(this.currentIndex);
              })
            }, (item: ItemClass) => item.content)
          }
          .padding({ left: 10, right: 10 })
          .width("100%")
          .edgeEffect(EdgeEffect.None)
          .scrollBar(BarState.Off)
          .onReachStart(() => this.listPosition = ScrollPosition.start)
          .onReachEnd(() => this.listPosition = ScrollPosition.end)
          .onScrollFrameBegin((offset: number, state: ScrollState) => {
            if (!((this.listPosition === ScrollPosition.start && offset < 0) || 
                  (this.listPosition === ScrollPosition.end && offset > 0))) {
              this.listPosition = ScrollPosition.center;
            }
            if (this.scrollPosition === ScrollPosition.end && 
                (this.listPosition !== ScrollPosition.start || offset > 0)) {
              return { offsetRemain: offset };
            } else {
              this.scrollerForScroll.scrollBy(0, offset);
              return { offsetRemain: 0 };
            }
          })
          .width("100%")
          .height("calc(100% - 50vp)")
          .backgroundColor('#F1F3F5')
        }
      }
      .scrollBar(BarState.Off)
      .width("100%")
      .height("100%")
      .onScroll((xOffset: number, yOffset: number) => {
        this.currentYOffset = this.scrollerForScroll.currentOffset().yOffset;
        if (!((this.scrollPosition === ScrollPosition.start && yOffset < 0) || 
              (this.scrollPosition === ScrollPosition.end && yOffset > 0))) {
          this.scrollPosition = ScrollPosition.center;
        }
      })
      .onScrollEdge((side: Edge) => {
        this.scrollPosition = side === Edge.Top ? ScrollPosition.start : ScrollPosition.end;
      })
      .onScrollFrameBegin(offset => {
        return this.scrollPosition === ScrollPosition.end ? { offsetRemain: 0 } : { offsetRemain: offset };
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor(0xDCDCDC)
  }
}

5. Mismatch between Grid vertical scroll animation and requirements in HarmonyOS

Reference demo:

@Entry
@Component
export struct Page240605101412064_temp {
  weekDatas: string[] = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
  data: Array<CalendaEntity> = [];
  @State gridTop: number = 0;
  @State blankTop: number = 0;
  private endBlankY: number = 0;
  private endGridY: number = 0;
  private isStart: boolean = true;
  private scroller: Scroller = new Scroller();
  private today: Date = new Date();
  private startDay: Date = new Date(this.today.getFullYear(), this.today.getMonth(), 1);

  aboutToAppear(): void {
    const monthDataWithWeekday = this.calcDatas(this.startDay);
    monthDataWithWeekday.forEach(item => {
      console.log("日期是》》》》》》》" + item.year, ' ', item.month, ' ', item.day, ' ', item.weekday);
    });
  }

  calcDatas(startDate: Date): Array<CalendaEntity> {
    const endDate = new Date(startDate.getFullYear(), startDate.getMonth() + 1, 0);
    let currentDate = new Date(startDate);
    while (currentDate <= endDate) {
      const weekday = currentDate.getDay();
      const weekdayStr = weekday.toString();
      let year = currentDate.getFullYear();
      let month = currentDate.getMonth() + 1;
      let day = currentDate.getDate().toString();
      if (day === this.today.getDate().toString()) day = '';
      this.data.push({ year, month, day, weekday: parseInt(weekdayStr) });
      currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate() + 1);
    }
    if (this.data.length !== 0) {
      let firstWeek = this.data[0].weekday;
      let tempData: Array<CalendaEntity> = [];
      while (firstWeek > 0) {
        tempData.push(new CalendaEntity());
        firstWeek--;
      }
      this.data = tempData.concat(this.data);
      let lastWeek = this.data[this.data.length - 1].weekday;
      while (lastWeek < 6) {
        this.data.push(new CalendaEntity());
        lastWeek++;
      }
    }
    return this.data;
  }

  build() {
    Column() {
      Row() {
        ForEach(this.weekDatas, (item: string) => {
          Text(item)
            .textAlign(TextAlign.Center)
            .fontColor('#9E9E9E')
            .fontSize(14)
            .layoutWeight(1)
        })
      }
      .alignItems(VerticalAlign.Center)
      .height(60)

      Scroll(this.scroller) {
        Column() {
          Grid() {
            ForEach(this.data, (item: CalendaEntity) => {
              GridItem() {
                Stack() {
                  Image("")
                    .backgroundColor('#35cfba')
                    .borderRadius(100)
                    .width(34)
                    .height(34)
                    .visibility(item.day === "0" || item.day !== '' ? Visibility.Hidden : Visibility.Visible)
                  Column() {
                    Text(item.day === "0" ? " " : item.day)
                      .fontSize(14)
                      .fontColor(Color.Gray)
                      .width('14%')
                      .textAlign(TextAlign.Center)
                    Text()
                      .backgroundColor(Color.Red)
                      .width(4)
                      .height(4)
                      .borderRadius(50)
                      .visibility(item.day === "0" ? Visibility.Hidden : Visibility.Visible)
                  }
                }
              }.onClick(() => console.log("dddddddddd"))
            })
          }.rowsGap(20)
          .backgroundColor('#fff')
          .margin({ top: this.gridTop })
          .onAreaChange((oldValue: Area, newValue: Area) => console.log('h', newValue.height))

          Blank().layoutWeight(1).backgroundColor(Color.Gray)
            .margin({ top: this.blankTop })
        }.height('calc(100% + 560vp)')
      }
      .height('100%')
      .scrollBar(BarState.Off)
      .onReachStart(() => this.isStart = true)
      .onScrollFrameBegin((offset: number, state: ScrollState) => {
        if (this.isStart) return { offsetRemain: 0 };
        if (!this.scroller.currentOffset().yOffset) this.isStart = false;
        return { offsetRemain: offset };
      })
      .parallelGesture(
        PanGesture({ direction: PanDirection.Vertical, distance: 1 })
          .onActionStart(() => console.info('Pan start'))
          .onActionUpdate((event: GestureEvent) => {
            if (event && this.isStart) {
              if (this.blankTop === 0) {
                this.gridTop = this.endGridY + event.offsetY < -(304 / 6 * 2) ? -(304 / 6 * 2) :
                  Math.min(0, this.endGridY + event.offsetY);
              }
              if (this.gridTop === -(304 / 6 * 2)) {
                this.blankTop = this.endBlankY + event.offsetY + (304 / 6 * 2) < -(304 / 6 * 3) ? -(304 / 6 * 3) :
                  Math.min(0, this.endBlankY + event.offsetY + (304 / 6 * 2));
              }
            }
          })
          .onActionEnd((event: GestureEvent) => {
            this.endBlankY = this.blankTop;
            this.endGridY = this.gridTop;
          })
      )
    }.width('100%')
  }
}

export class CalendaEntity {
  year: number = 0;
  month: number = 0;
  day: string = "0";
  weekday: number = -1;
}

export enum OptMode {
  WEEK = "week",
  MONTH = "month",
}

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert