DEV Community

Cover image for Foldable Screen Status Judgment, H5 Image Selection, Component Refresh, Click-to-Zoom Images, Interface Update Issues
kouwei qing
kouwei qing

Posted on

Foldable Screen Status Judgment, H5 Image Selection, Component Refresh, Click-to-Zoom Images, Interface Update Issues

[Daily HarmonyOS Next Knowledge] Foldable Screen Status Judgment, H5 Image Selection, Component Refresh, Click-to-Zoom Images, Interface Update Issues

1. How to determine if a HarmonyOS foldable phone is currently unfolded or folded?

Refer to the API to get the fold status of the current foldable device: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-display-V5#foldstatus10

FoldStatus

Enumeration of the fold status of the current foldable device.

Name Value Description
FOLD_STATUS_UNKNOWN 0 The device's current fold status is unknown.
FOLD_STATUS_EXPANDED 1 The device is currently in a fully unfolded state.
FOLD_STATUS_FOLDED 2 The device is currently in a folded state.
FOLD_STATUS_HALF_FOLDED 3 The device is currently in a half-folded state (between fully unfolded and folded).

2. In HarmonyOS H5 pages, image selection can be listened to via onShowFileSelector, but video selection cannot. Are there other methods for video selection?

Video selection in H5 pages can also be listened to via onShowFileSelector. Refer to the following documentation and example:

Example:

@Entry
@Component
export struct WebComponent {
  controller: webview.WebviewController = new webview.WebviewController()

  build() {
    Column() {
      Web({ src: $rawfile('index.html'), controller: this.controller })
        .onShowFileSelector((event) => {
          if (event) {
            let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
            let photoPicker = new photoAccessHelper.PhotoViewPicker();
            // Filter media file types to IMAGE_VIDEO_TYPE
            photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_VIDEO_TYPE;
            // Set maximum selection count
            photoSelectOptions.maxSelectNumber = 5;
            photoPicker.select(photoSelectOptions).then((res) => {
              console.log('asd===', JSON.stringify(res.photoUris))
            }).catch((err: Error) => {
            })
            console.log('res ===', JSON.stringify(event))
          }
          return true;
        })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

3. How to implement click-to-zoom images in HarmonyOS?

Is there a component for large-image browsing that supports double-click zooming?

Currently, no dedicated component exists, but you can refer to the source code for large-image viewing and left/right image browsing in the gallery: https://gitee.com/openharmony/applications_photos/tree/master

Alternatively, use the following code:

  1. Page component:
// xxx.ets
import router from '@ohos.router'

@Entry
@Component
struct SharedTransitionExample {
  @State active: boolean = false
  @State imageNames: Resource[] = [$r('app.media.image1'), $r('app.media.image2'), $r('app.media.image3')]
  @StorageLink('currentIndex') currentIndex: number = 0

  build() {
    Row() {
      ForEach(this.imageNames, (res: Resource, index: number) => {
        Column() {
          Image(res)
            .width('100%')
            .height('100%')
            .objectFit(ImageFit.Contain)
        }
        .width(100)
        .height(100)
        .clip(true)
        .sharedTransition('sharedImage' + res.id, {
          duration: 200,
          curve: Curve.Linear,
          zIndex: this.currentIndex === index ? 10 : -10
        })
        .onClick(() => {
          this.currentIndex = index
          router.pushUrl({ url: 'pages/Index', params: {
            data: this.imageNames,
          } })
        })
      })
    }.width('100%')
    .height('100%')
  }

  pageTransition() {
    PageTransitionEnter({ duration: 0, curve: Curve.Linear })
      .onEnter((type?: RouteType, progress?: number) => {
      })
    PageTransitionExit({ duration: 0, curve: Curve.Ease })
      .onExit((type?: RouteType, progress?: number) => {
      })
  }
}
Enter fullscreen mode Exit fullscreen mode
  1. Index page:
import window from '@ohos.window';
import router from '@ohos.router';

interface Data {
  data: Resource[];
}

enum Direction {
  None,
  Left,
  Right,
}

@Entry
@Component
struct Index {
  private swiperController: SwiperController = new SwiperController();
  @State imageNames: Resource[] = []
  @StorageLink('currentIndex') currentIndex: number = 0
  @State screenWidth: number = 0;
  @State op: number = 0

  aboutToAppear() {
    const data = (router.getParams() as Data)
    this.imageNames = data.data
    window.getLastWindow(getContext(this)).then(currentWindow => {
      let property = currentWindow.getWindowProperties();
      this.screenWidth = property.windowRect.width;
    })
  }

  build() {
    Stack({ alignContent: Alignment.Center }) {
      Swiper(this.swiperController) {
        ForEach(this.imageNames, (name: Resource, index: number) => {
          Column() {
            ImageComponent({
              image: name,
              viewWidth: this.screenWidth,
              isCurrent: this.currentIndex === index,
              onNeedGoNext: (dire: Direction) => {
                if (dire === Direction.Right) {
                  this.swiperController.showNext()
                } else if (dire === Direction.Left) {
                  this.swiperController.showPrevious()
                }
              }
            }).zIndex(index == this.currentIndex ? 2 : 1)
          }.width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
        })
      }
      .index(this.currentIndex)
      .indicator(false)
      .disableSwipe(true)
      .itemSpace(10)
      .onChange((index: number) => {
        this.currentIndex = index
      })
    }.width('100%').height('100%')
    .backgroundColor(`rgba(0,0,0,${this.op})`)
  }

  pageTransition() {
    PageTransitionEnter({ duration: 200, curve: Curve.Linear })
      .onEnter((type?: RouteType, progress?: number) => {
        if (progress) {
          this.op = progress
        }
      })
  }
}

@Component
struct ImageComponent {
  private image: Resource = $r('app.media.icon')
  private preGeometryScale: number = 1
  @State geometryScale: number = 1
  private preOffsetX: number = 0
  private preOffsetY: number = 0
  @State geometryOffsetX: number = 0
  @State geometryOffsetY: number = 0
  @State imageWidth: number = 0
  @State imageHeight: number = 0
  @Prop viewWidth: number = 0
  @Prop isCurrent: boolean = false
  private dire: Direction = Direction.None
  private goNext: boolean = true
  private pinching: boolean = false
  private onNeedGoNext: (dire: Direction) => void = () => {
  }

  reset(): Promise<void> | undefined {
    this.preGeometryScale = 1
    this.preOffsetX = 0
    this.preOffsetY = 0
    this.dire = Direction.None
    this.goNext = true
    if (this.geometryScale === 1) return
    return new Promise<void>(res => {
      animateTo({ duration: 200, onFinish: res }, () => {
        this.geometryScale = 1
        this.geometryOffsetX = 0
        this.geometryOffsetY = 0
      })
    })
  }

  build() {
    Column() {
      Image(this.image)
        .onComplete((e) => {
          this.imageWidth = (e?.width || 0)
          this.imageHeight = (e?.height || 0)
        })
        .objectFit(ImageFit.Cover)
        .width(this.imageWidth + 'px')
        .height(this.imageHeight + 'px')
        .scale({
          x: this.geometryScale,
          y: this.geometryScale
        })
        .offset({
          x: this.geometryOffsetX,
          y: this.geometryOffsetY
        })
        .focusable(true)
        .objectFit(ImageFit.Cover)
        .autoResize(false)
        .sharedTransition('sharedImage' + this.image.id, {
          duration: 200,
          curve: Curve.Linear,
          zIndex: this.isCurrent ? 10 : -10
        })
    }
    .clip(true)
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .hitTestBehavior(HitTestMode.Default)
    .parallelGesture(
      GestureGroup(GestureMode.Parallel,
        PinchGesture({ fingers: 2 })
          .onActionStart((event: GestureEvent) => {
            this.pinching = true
            this.goNext = false
          })
          .onActionUpdate((event: GestureEvent) => {
            const s = this.preGeometryScale * event.scale;
            this.geometryScale = Math.max(0.6, Math.min(2, s))
          })
          .onActionEnd(async () => {
            this.preGeometryScale = this.geometryScale
            if (this.preGeometryScale < 1) {
              await this.reset()
            }
            this.pinching = false
          }),
        PanGesture()
          .onActionStart((event?: GestureEvent) => {
          })
          .onActionUpdate((event?: GestureEvent) => {
            let offsetX = this.preOffsetX + (event?.offsetX || 0)
            let offsetY = this.preOffsetY + (event?.offsetY || 0)
            if (((this.imageWidth * this.geometryScale - this.viewWidth) / 2 - Math.abs(vp2px(offsetX))) <= 0) {
              if (!this.pinching) {
                this.dire = offsetX < 0 ? Direction.Right : Direction.Left
              }
              return;
            }
            this.goNext = false
            this.geometryOffsetX = offsetX
            this.geometryOffsetY = offsetY
          })
          .onActionEnd((event?: GestureEvent) => {
            if ((this.dire !== Direction.None)) {
              if (this.goNext) {
                this.onNeedGoNext(this.dire)
                this.reset()
              }
              this.goNext = true
            }
            this.preOffsetX = this.geometryOffsetX
            this.preOffsetY = this.geometryOffsetY
          }),
      )
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

4. HarmonyOS component refresh issue: Modifying item properties in an array does not trigger list update

struct MyFoldPage {
  @State filePaths: FolderModel[] = []
   build() {
      Column(){
            Grid() {
            ForEach(this.filePaths, (item: FolderModel, index: number) => {
              GridItem() {
                this.ItemBuilder(index)
              }
            }, (item: FolderModel) => item.fileName)
          }
         }
     }
}
export class FolderModel {
  filePath: string = ''
  fileName: string = ''
  isScreenshot: boolean = true
  thumbnailPath?: string = ''   // Thumbnail for videos
  isSelect?: boolean = false
}
Enter fullscreen mode Exit fullscreen mode

Issue: Modifying isSelect in array items does not trigger list refresh. The list only updates when filePaths is reassigned with new items. What's a better solution?

Use the @Observed and @ObjectLink decorators. Refer to the documentation: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-observed-and-objectlink-V5

5. HarmonyOS custom component: Interface does not re-render when data updates

@Component
export struct CardView {
  @State title: string | undefined = undefined
  @State moreText: string = '更多'

  @Builder
  tempBuilder() {
  };

  @BuilderParam contentBuilder: () => void = this.tempBuilder

  build() {
    this.contentBuilder()
  }
}

// Component usage
@Component
export struct NoticeCardView {
  @State noticeList: Notice[] = []

  aboutToAppear(): void {
    new NoticeRepository().getNoticeList().then((result) => {
      this.noticeList = result
    })
  }

  @Builder
  ContentBuilder() {
    ForEach(this.noticeList, (notice: Notice) => {
      Row() {
        Image($r('app.media.wb_ic_notice_tip')).height(11).width(9)
        Text(notice.title)
          .margin({ left: 11 })
          .fontColor($r('app.color.cm_text_primary'))
          .fontSize('14fp')
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
      }.padding(8)
    })
  }

  build() {
    CardView({
      title: '',
      contentBuilder: this.ContentBuilder
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Issue: When noticeList updates, the interface does not re-render.

Reference code:

@Component
struct CardView  {
  @Prop header: string = '';
  @Builder tempBuilder(){}

  @BuilderParam contentBuilder: () => void = this.tempBuilder
  build() {
    Column() {
      this.contentBuilder()
    }
  }
}
@Entry
@Component
struct CustomContainerUser {
  @State text: string = 'header';
  @State list: string[] = []
  aboutToAppear(): void {
    this.list.push("1");
    this.list.push("2");
    this.list.push("3");
    console.log('Data loaded', this.list)
  }
  build() {
    Column() {
      Text(this.text)
      CardView ({ header: this.text }) {
        Column() {
          this.ContentBuilder('testA', 'testB')
        }.backgroundColor(Color.Yellow)
      }
    }
  }
  @Builder  ContentBuilder(label1: string, label2: string) {
    Column() {
      Text('test')
      ForEach(this.list, (text: string) => {
        Text(text)
      })
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)