[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:
- Documentation: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-basic-components-web-V5#onshowfileselector9
-
onShowFileSelector(callback: Callback<OnShowFileSelectorEvent, boolean>)
: Processes HTML forms with "file" input types. If not called or returns false, the Web component provides a default "select file" interface. If returns true, the app can customize the response behavior.
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;
})
}
}
}
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:
- 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) => {
})
}
}
- 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
}),
)
)
}
}
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
}
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
})
}
}
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)
})
}
}
}
Top comments (0)