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变更组件树结构。