HarmonyOS笔记

30 阅读4分钟

image

image()
.aspectRatio(1)         // 保持1:1宽高比(根据实际需求调整比例)
.objectFit(ImageFit.Contain)  // 保持比例完整显示

装饰器(V2)

当装饰的变量是嵌套类或对象数组时,@Local无法观察深层对象属性的变化。对深层对象属性的观测依赖@ObservedV2与@Trace装饰器。


@ObservedV2
class Region {
  @Trace x: number;
  @Trace y: number;
  constructor(x: numbe  r, y: number) {
    this.x = x;
    this.y = y;
  }
}
@ObservedV2
class Info {
  @Trace region: Region;
  @Trace name: string;
  constructor(name: string, x: number, y: number) {
    this.name = name;
    this.region = new Region(x, y);
  }
}
@Entry
@ComponentV2
struct Index {
  @Local infoArr: Info[] = [new Info("Ocean", 28, 120), new Info("Mountain", 26, 20)];
  @Local originInfo: Info = new Info("Origin", 0, 0);
  build() {
    Column() {
      ForEach(this.infoArr, (info: Info) => {
        Row() {
          Text(`name: ${info.name}`)
          Text(`region: ${info.region.x}-${info.region.y}`)
        }
      })
      Row() {
          Text(`Origin name: ${this.originInfo.name}`)
          Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`)
      }
      Button("change infoArr item")
        .onClick(() => {
          // 由于属性name被@Trace装饰,所以能够观察到
          this.infoArr[0].name = "Win";
        })
      Button("change originInfo")
        .onClick(() => {
          // 由于变量originInfo被@Local装饰,所以能够观察到
          this.originInfo = new Info("Origin", 100, 100);
        })
      Button("change originInfo region")
        .onClick(() => {
          // 由于属性x、y被@Trace装饰,所以能够观察到
          this.originInfo.region.x = 25;
          this.originInfo.region.y = 25;
        })
    }
  }
}

父子单向(v2)

被@Param修饰的taskName属性从父组件TodoList传入任务名称,使TaskItem组件灵活且可复用,能接收并渲染不同的任务名称。被@Param @Once装饰的isFinish属性在接收初始值后,可以在子组件内更新。

@ComponentV2
struct TaskItem {
  @Param taskName: string = '';
  @Param @Once isFinish: boolean = false;
​
  build() {
    Row() {
      // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错
      Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
        .width(28)
        .height(28)
      Text(this.taskName)
        .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
    }
    .onClick(() => this.isFinish = !this.isFinish)
  }
}
​
@Entry
@ComponentV2
struct TodoList {
  build() {
    Column() {
      Text('待办')
        .fontSize(40)
        .margin({ bottom: 10 })
      TaskItem({ taskName: 'Task 1', isFinish: false })
      TaskItem({ taskName: 'Task 2', isFinish: false })
      TaskItem({ taskName: 'Task 3', isFinish: false })
    }
  }
}

父子双向(v2)

通过使用@Param和@Event,子组件不仅能接收父组件的数据,还能够将事件传递回父组件,实现数据的双向同步。


//父组件 需要先用@param传过来 通过@event修改Child({
        title: this.title,
        fontColor: this.fontColor,
        changeFactory: (type: number) => {
          if (type == 1) {
            this.title = "Title One";
            this.fontColor = Color.Red;
          } else if (type == 2) {
            this.title = "Title Two";
            this.fontColor = Color.Green;
          }
        }
      })
          
          
//子组件
 @Event changeFactory: (x: number) => void = (x: number) => {};
  Button("change to Title One")
        .onClick(() => {
          this.changeFactory(1);
        })

子组件复用(v2)

使用Repeat方法迭代数组中的每一项,动态生成并复用TaskItem组件。在任务增删时,这种方式能高效复用已有组件,避免重复渲染,从而提高界面响应速度和性能


// src/main/ets/pages/5-Repeat.ets@ComponentV2
struct TaskItem {
  @Param taskName: string = '';
  @Param @Once isFinish: boolean = false;
  @Event deleteTask: () => void = () => {};
​
  build() {
    Row() {
      // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错
      Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
        .width(28)
        .height(28)
      Text(this.taskName)
        .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
      Button('删除')
        .onClick(() => this.deleteTask())
    }
    .onClick(() => this.isFinish = !this.isFinish)
  }
}
​
@Entry
@ComponentV2
struct TodoList {
  @Local tasks: string[] = ['task1','task2','task3'];
  @Local newTaskName: string = '';
  build() {
    Column() {
      Text('待办')
        .fontSize(40)
        .margin({ bottom: 10 })
      Repeat<string>(this.tasks)
        .each((obj: RepeatItem<string>) => {
          TaskItem({
            taskName: obj.item,
            isFinish: false,
            deleteTask: () => this.tasks.splice(this.tasks.indexOf(obj.item), 1)
          })
        })
      Row() {
        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
          .onChange((value) => this.newTaskName = value)
          .width('70%')
        Button('增加事项')
          .onClick(() => {
            this.tasks.push(this.newTaskName);
            this.newTaskName = '';
          })
      }
    }
  }
}

帧动画实现步骤

1.初始化 绑定上下文


aboutToAppear() {
  this.uiContext = this.getUIContext?.(); 
}

2.组件绑定缩放比例


Column(){}
.scale({ x: this.classLiveScale[i], y: this.classLiveScale[i] })
​

2.事件内完成动画效果


.onAppear(()=>{
                        if (!this.uiContext) {
                          console.info("no uiContext, keyframe failed");
                          return;
                        }
                        this.classLiveScale[i] = 1;
                        // 设置关键帧动画整体播放3次
                        this.uiContext?.keyframeAnimateTo({ iterations: -1}, [
                          {
                            // 第一段关键帧动画时长为800ms,scale属性做从1到1.5的动画
                            duration: 500,
                            event: () => {
                              this.classLiveScale[i] = 1.2;
                            }
                          },
                          {
                            // 第二段关键帧动画时长为500ms,scale属性做从1.5到1的动画
                            duration: 500,
                            event: () => {
                              this.classLiveScale[i] = 1;
                            }
                          }
                        ]);
                      })

使用LazyForEach后界面闪动


aboutToAppear(): void {
    app.setImageRawDataCacheSize(1024*1024*100); // 缓存 解码前数据上限,单位为字节;约等于9.53M
    app.setImageCacheCount(100); // 设置内存中缓存解码后图片的数量上限
  }

键盘避让问题

设置KeyboardAvoidMode的RESIZE模式时,expandSafeArea([SafeAreaType.KEYBOARD],[SafeAreaEdge.BOTTOM])不生效。

事件穿透问题

是用于设置组件的点击事件穿透行为。具体作用如下:HitTestMode.Transparent:该组件不会拦截点击事件,点击事件会穿透当前组件,传递给下层的组件或页面。也就是说,即使点击了这个组件,也不会触发它的 onClick 事件(如果有的话),而是让更下层的组件响应点击事件。


hitTestBehavior(HitTestMode.Transparent) 

AppStorageV2

@Local liveBlessEndTimeState: BlessCountDownState = AppStorageV2.connect(BlessCountDownState, () => new BlessCountDownState(false))!;
  //响应式强依赖new
this.liveBlessEndTimeState = new BlessCountDownState(true)
​
​

组件挂在生命周期


onAttach(组件渲染之前)=>onAppear(组件挂载显示后,可能发生在组件布局渲染后)=>onDetach(组件从组件树卸载时)=>onDisAppear(组件卸载消失时)
​
注意!!! onAttach回调在组件布局渲染之前调用。
​
不允许在回调中对组件树进行变更,例如启动动画或使用if-else变更组件树结构。
​