DEV Community

Cover image for Image Pixels, State Binding, Slider Value Listening, Gesture Issues, Object-Map Conversion
kouwei qing
kouwei qing

Posted on

Image Pixels, State Binding, Slider Value Listening, Gesture Issues, Object-Map Conversion

【Daily HarmonyOS Next Knowledge】Image Pixels, State Binding, Slider Value Listening, Gesture Issues, Object-Map Conversion

1. How to create an ImageBitmap with empty content of a specified size in HarmonyOS?

Refer to the official ImageBitmap documentation: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-components-canvas-imagebitmap-V5

ImageBitmap(data: PixelMap, unit?: LengthMetricsUnit)

Create an ImageBitmap object from a PixelMap.

2. Why is the ObservedV2 decorator ineffective for state monitoring of models returned by interfaces?

The ObservedV2 decorator does not work for state monitoring of models returned by interfaces.

Objects obtained via JSON.parse are not instances created by the User constructor, so their data changes cannot be observed, preventing UI updates. You can use third-party libraries reflect-metadata and class-transformer. Refer to the following demo (similarly applicable to observedv2):

//test.ets
export let jsonString:Record<string, ESObject> = {
  "data" : [
    { "number" :1,
      "age": 20,
      "testA": {
        "str": "123"
      }
    },
    {
      "number" :2,
      "age": 21,
      "testA": {
        "str": "456"
      }
    }
  ]
}
import { Type, plainToClass } from 'class-transformer'
import 'reflect-metadata'
import { jsonString } from './test'
import { TestA, Person, ViewA} from './ViewA';
class ResponseObj {
  @Type (() => Person) data: Person[] = [];
}
@Entry @Component struct Index {
  @State list: Person[] = [];
  @State message: string = 'Click me';
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(40)
          .onClick(() => {
            let responseObj : ResponseObj = plainToClass(ResponseObj, jsonString);
            console.log(` ${responseObj.data[0] instanceof Person}`)
            this.list = this.list.concat(responseObj.data); })
        ForEach(this.list, (item : Person, index: number) => {
          ViewA({index:index, testA: item.testA})
        })
      }
      .width('100%')
    } .height('100%')
  } }
ViewA.ets
import { Type } from 'class-transformer'
@Observed export class TestA {
  public str : string
  constructor(str: string) { this.str = str; }
}
export class Person {
  name: string = ''
  age: number = 1
  @Type(()=>TestA) testA: TestA = new TestA('')
}
@Component export struct ViewA {
  @ObjectLink testA: TestA
  index: number = -1;
  build() {
    Row(){
      Button(`View A ${this.testA.str}`)
        .onClick(() =>{
          this.index += 1;
          this.testA.str = `${this.index} : Test A String` })
    }
    .margin({top : 10 })
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Why does the onChange event of the HarmonyOS Slider component return inaccurate values?

During sliding, the Slider's progress value is a repeating decimal. Use .toFixed().toString() to format the value.

4. Gesture handling issues in HarmonyOS

There are two sibling buttons, A and B, on the page. Button A has a long-press gesture and B has a touch event. Requirement: After a long-press on A, sliding to B should trigger B's touch event.

Current implementations may cause long-press events to malfunction or be interrupted. Try using combined gestures or binding events: https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-gesture-events-combined-gestures-V5

Combined gestures are composed of multiple single gestures. Declare the type of combined gesture using different GestureModes in GestureGroup, supporting sequential, parallel, and exclusive recognition:

GestureGroup(mode:GestureMode, gesture:GestureType[])
Enter fullscreen mode Exit fullscreen mode
  • mode: A GestureMode enum value defining the combination type.
  • gesture: An array of gestures composing this combined gesture.

Sequential Recognition:
Use GestureMode.Sequence. Gestures are recognized in registration order. If any gesture fails, subsequent gestures are skipped. Only the last gesture can respond to onActionEnd.

Example: A Column with a translate property for position movement, bound to a Sequence gesture combining LongPressGesture and PanGesture:

// xxx.ets
@Entry
@Component
struct Index {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  @State count: number = 0;
  @State positionX: number = 0;
  @State positionY: number = 0;
  @State borderStyles: BorderStyle = BorderStyle.Solid

  build() {
    Column() {
      Text('sequence gesture\n' + 'LongPress onAction:' + this.count + '\nPanGesture offset:\nX: ' + this.offsetX + '\n' + 'Y: ' + this.offsetY)
        .fontSize(28)
    }.margin(10)
    .borderWidth(1)
    // Bind translate property to enable component movement
    .translate({ x: this.offsetX, y: this.offsetY, z: 0 })
    .height(250)
    .width(300)
    // Sequential recognition: PanGesture triggers only after LongPressGesture succeeds
    .gesture(
      // Declare sequential gesture group
      GestureGroup(GestureMode.Sequence,
        // First gesture: LongPress with repeat enabled
        LongPressGesture({ repeat: true })
          // Increment count when long-press is recognized
          .onAction((event: GestureEvent|undefined) => {
            if(event){
              if (event.repeat) {
                this.count++;
              }
            }
            console.info('LongPress onAction');
          })
          .onActionEnd(() => {
            console.info('LongPress end');
          }),
        // Second gesture: PanGesture (triggers after long-press)
        PanGesture()
          .onActionStart(() => {
            this.borderStyles = BorderStyle.Dashed;
            console.info('pan start');
          })
          // Update position based on pan movement
          .onActionUpdate((event: GestureEvent|undefined) => {
            if(event){
              this.offsetX = (this.positionX + event.offsetX);
              this.offsetY = this.positionY + event.offsetY;
            }
            console.info('pan update');
          })
          .onActionEnd(() => {
            this.positionX = this.offsetX;
            this.positionY = this.offsetY;
            this.borderStyles = BorderStyle.Solid;
          })
      )
      .onCancel(() => {
        console.log("sequence gesture canceled")
      })
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

Parallel Recognition:
Use GestureMode.Parallel. All gestures are recognized simultaneously without interference.

Example: A Column with both single-tap and double-tap gestures:

// xxx.ets
@Entry
@Component
struct Index {
  @State count1: number = 0;
  @State count2: number = 0;

  build() {
    Column() {
      Text('Parallel gesture\n' + 'tapGesture count is 1:' + this.count1 + '\ntapGesture count is 2:' + this.count2 + '\n')
        .fontSize(28)
    }
    .height(200)
    .width('100%')
    // Parallel recognition: Both single-tap and double-tap can trigger
    .gesture(
      GestureGroup(GestureMode.Parallel,
        TapGesture({ count: 1 })
          .onAction(() => {
            this.count1++;
          }),
        TapGesture({ count: 2 })
          .onAction(() => {
            this.count2++;
          })
      )
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

Exclusive Recognition:
Use GestureMode.Exclusive. Gestures are recognized simultaneously. The first successful gesture stops recognition for others.

Example: A Column with conflicting single-tap and double-tap gestures:

// xxx.ets
@Entry
@Component
struct Index {
  @State count1: number = 0;
  @State count2: number = 0;

  build() {
    Column() {
      Text('Exclusive gesture\n' + 'tapGesture count is 1:' + this.count1 + '\ntapGesture count is 2:' + this.count2 + '\n')
        .fontSize(28)
    }
    .height(200)
    .width('100%')
    // Exclusive recognition: Only one gesture can succeed
    .gesture(
      GestureGroup(GestureMode.Exclusive,
        TapGesture({ count: 1 })
          .onAction(() => {
            this.count1++;
          }),
        TapGesture({ count: 2 })
          .onAction(() => {
            this.count2++;
          })
      )
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

5. How to convert a HarmonyOS Object to a Map?

After receiving data of type Object from an interface, you want to convert it to a Map to use Map methods. Here's how:

Reference demo:

@Entry
@Component
struct otj {

  @State obj: object = new Object();
  @State map: Map<string, string> = new Map();

  aboutToAppear(): void {
    this.obj['first'] = '1';
    this.obj['second'] = '2';
    // Object.entries(this.obj) converts to [["first","1"],["second","2"]]
    Object.entries(this.obj).forEach((item:string[])=>{
      // Method 1
      //this.map[item[0]] = item[1];
      // Method 2 (preferred)
      this.map.set(item[0],item[1]);
      // Access values: this.map['key'] or this.map.get('key')
    });
  }
  build() {}
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)