首页 文章 精选 留言 我的

精选列表

搜索[HarmonyOS],共1235篇文章
优秀的个人博客,低调大师

HarmonyOS 5.0应用开发——V2装饰器@once的使用

【高心星出品】 V2装饰器@once的使用 概念 在鸿蒙ArkTS开发中,@Once装饰器用于实现子组件仅接受父组件传递的初始值,后续父组件数据变化不再同步至子组件。以下是其核心要点: 一、核心作用与规则 初始化同步一次 @Once与@Param结合使用,子组件仅在初始化时接收父组件传递的值,后续父组件数据更新时不会触发同步。 强制依赖关系 @Once必须与@Param搭配使用,单独使用或与其他装饰器(如@Local)结合会编译失败。 装饰顺序不影响功能,@Param @Once或@Once @Param均有效。 本地修改支持 结合@Once后,子组件可以修改@Param变量值并触发UI刷新,此时行为类似@Local,但仍保留初始值传递能力。 二、适用场景 固定初始值:父组件传递配置参数(如主题色、默认尺寸),子组件仅需初始化时使用。 独立维护状态:子组件基于父组件初始值构建自身状态后,不再依赖外部更新。 案例 父组件(oncepage): 点击按钮时,@Local修饰的count自增,并通过child3({ count: this.count })传递最新值给子组件。但子组件仅在首次渲染时接收初始值(如10),后续父组件的count变化不会更新子组件。 子组件(child3): 点击按钮时,子组件内部count自增并更新UI,但父组件的count始终保持独立状态(例如父组件count为15时,子组件可能显示为12)。 @ComponentV2 struct child3 { // 强制父组件传参 并且只会初始化一次 @Require @Once @Param count:number build() { Column() { Button('child count: ' + this.count) .width('60%') .onClick(() => { //@once装饰的变量 这里可以更新count值 this.count+=1 }) } .width('100%') .padding(20) } } @Entry @ComponentV2 struct oncepage { @Local count: number = 10; build() { Column({ space: 20 }) { Button('page count: ' + this.count) .width('60%') .onClick(() => { this.count += 1 }) // child count与 父组件count单向绑定 child3({ count: this.count }) } .height('100%') .width('100%') } } 父子组件同步的数据为数组的时候,使用@once和@param修改数组中元素不会造成单向同步而是会形成双向同步效果,例如下面案例,父子组件数据会同时改变。 @ComponentV2 struct child4 { // 强制父组件传参 并且只会初始化一次 @Require @Once @Param arr:number[] build() { Column() { Button('child count: ' + this.arr[0]) .width('60%') .onClick(() => { //@once装饰的变量 这里可以更新count值 this.arr[0]+=1 }) } .width('100%') .padding(20) } } @Entry @ComponentV2 struct oncepage1 { @Local arr: number[] = [1,2,3]; build() { Column({ space: 20 }) { Button('page count: ' + this.arr[0]) .width('60%') .onClick(() => { this.arr[0] += 1 }) // child count与 父组件count单向绑定 child4({ arr: this.arr }) // 使用深度拷贝 就会造成隔离不会双向同步 // child4({arr:[...this.arr]}) } .height('100%') .width('100%') } } | 装饰器组合 | 同步方式 | 内存关系 | 适用场景 | | ----------------- | -------------- | --------- | ---------------------------------- | | @Param | 双向同步 | 共享引用 | 需要实时联动的组件(如协同编辑器) | | @Once @Param | 单次初始化同步 | 共享引用* | 基于初始值的独立运作组件 | | @Param + 深拷贝 | 完全隔离 | 独立内存 | 需要数据隔离的安全场景 |

优秀的个人博客,低调大师

HarmonyOS 5.0应用开发——V2装饰器@param的使用

【高心星出品】 V2装饰器@param的使用 概念 在鸿蒙ArkTS开发中,@Param装饰器是组件间状态管理的重要工具,主要用于父子组件间的单向数据传递,这一点与V1中的@prop类似。 @Param装饰的变量支持本地初始化,但不允许在组件内部直接修改。 被@Param装饰的变量能够在初始化自定义组件时从外部传入,当数据源也是状态变量时,数据源的修改会同步给@Param。 @Param可以接受任意类型的数据源,包括普通变量、状态变量、常量、函数返回值等。 @Param装饰的变量变化时,会刷新该变量关联的组件。 @Param支持对基本类型(如number、boolean、string、Object、class)、内嵌类型(如Array、Set、Map、Date),以及null、undefined和联合类型进行观测。 对于复杂类型如类对象,@Param会接受数据源的引用。在组件内可以修改类对象中的属性,该修改会同步到数据源。 @Param的观测能力仅限于被装饰的变量本身。详见观察变化。 使用方法 数据流向 单向同步(父组件 → 子组件),仅支持父组件数据源变化触发子组件更新。 适用条件 1 仅限@ComponentV2装饰的组件(API version 12+)。 子组件禁止直接修改@Param装饰的变量(需通过@Once或事件触发父组件修改)。 支持类型 基本类型(number/boolean/string等)、对象、数组、Date/Map/Set等内嵌类型,以及null/undefined和联合类型。 @Param变量装饰器 说明 装饰器参数 无。 能否本地修改 否。若需要修改值,可使用@Param搭配@Once修改子组件的本地值。或通过@Event装饰器,修改@Param数据源的值。 同步类型 由父到子单向同步。 允许装饰的变量类型 Object、class、string、number、boolean、enum等基本类型以及Array、Date、Map、Set等内嵌类型。支持null、undefined以及联合类型。 被装饰变量的初始值 允许本地初始化,若不在本地初始化,则需要和@Require装饰器一起使用,要求必须从外部传入初始化。 传递规则 说明 从父组件初始化 @Param装饰的变量允许本地初始化,若无本地初始化则必须从外部传入初始化。当同时存在本地初始值与外部传入值时,优先使用外部传入值进行初始化。 初始化子组件 @Param装饰的变量可以初始化子组件中@Param装饰的变量。 同步 @Param可以和父组件传入的状态变量数据源(即@Local或@Param装饰的变量)进行同步,当数据源发生变化时,会将修改同步给子组件的@Param。 案例 子组件中有一个按钮展示@param装饰的count,但是不能更新本地的count,父组件中有一个按钮展示@local装饰的count,并且与子组件的count绑定,形成单向通信效果。点击父组件按钮会更新父子两者的组件显示数据。@require配合@param会强制要求父组件初始化子组件数据。 @ComponentV2 struct child { // 强制父组件传参 @Require @Param count:number // 父组件可以传参也可以不传参 // @Param count: number = 10 build() { Column() { Button('child count: ' + this.count) .width('60%') .onClick(() => { // 这里会报错,因为@param装饰的数据不能自己更新,需要让父组件更新 同步进来 // this.count+=1 }) } .width('100%') .padding(20) } } @Entry @ComponentV2 struct Parampage { @Local count: number = 10; build() { Column({ space: 20 }) { Button('page count: ' + this.count) .width('60%') .onClick(() => { this.count += 1 }) // child count与 父组件count单向绑定 child({ count: this.count }) } .height('100%') .width('100%') } } 同步数据为数组的时候,子组件中可以修改数组元素,不能初始化整个数组,这里的修改会形成双向同步关系,下面案例里面点击两个按钮数据会同时变化。 @ComponentV2 struct child2 { @Require @Param arr:number[] build() { Column() { Button('child count: ' + this.arr[0]) .width('60%') .onClick(() => { // 不可以修改整个数组,但是可以修改数组元素 形成双向同步 this.arr[0]+=1 }) } .width('100%') .padding(20) } } @Entry @ComponentV2 struct Parampage1 { @Local arr:number[]=[1,2,3] build() { Column({ space: 20 }) { Button('page count: ' + this.arr[0]) .width('60%') .onClick(() => { this.arr[0]+=1 }) // child arr与 父组件arr单向绑定 child2({arr:this.arr }) } .height('100%') .width('100%') } } 如果单向同步的数据是复杂的结构info类,在子组件中@param装饰的数据可以进行修改其属性并且会同步给父组件形成局部双向通信, 在下面案例中点击子组件按钮的时候in的count值已经发生了修改,父组件中的count也会修改,只不过@param和@local没有观察到,当先点击子组件按钮再点击父组件按钮的时候就会观察到两者显示10--12--14 . class info{ count:number constructor(count:number) { this.count=count } } @ComponentV2 struct child2 { @Require @Param in:info=new info(10) build() { Column() { Button('child count: ' + this.in.count) .width('60%') .onClick(() => { // param不允许本地修改 // this.in=new info(11) // 允许修改对象的属性 并且会同步到父组件 形成有限制的双向同步 this.in.count+=1 }) } .width('100%') .padding(20) } } @Entry @ComponentV2 struct Parampage1 { @Local in:info=new info(10) build() { Column({ space: 20 }) { Button('page count: ' + this.in.count) .width('60%') .onClick(() => { this.in=new info(this.in.count+1) }) // child count与 父组件count单向绑定 child2({ in: this.in }) } .height('100%') .width('100%') } }

优秀的个人博客,低调大师

HarmonyOS CPU与I/O密集型任务开发指导

一、CPU密集型任务开发指导 CPU密集型任务是指需要占用系统资源处理大量计算能力的任务,需要长时间运行,这段时间会阻塞线程其它事件的处理,不适宜放在主线程进行。例如图像处理、视频编码、数据分析等。 基于多线程并发机制处理CPU密集型任务可以提高CPU利用率,提升应用程序响应速度。 当进行一系列同步任务时,推荐使用Worker;而进行大量或调度点较为分散的独立任务时,不方便使用8个Worker去做负载管理,推荐采用TaskPool。接下来将以图像直方图处理以及后台长时间的模型预测任务分别进行举例。 使用TaskPool进行图像直方图处理 ​ 1. 实现图像处理的业务逻辑。 ​ 2. 数据分段,将各段数据通过不同任务的执行完成图像处理。 创建Task,通过execute()执行任务,在当前任务结束后,会将直方图处理结果同时返回。 ​ 3. 结果数组汇总处理。 import taskpool from '@ohos.taskpool'; @Concurrent function imageProcessing(dataSlice: ArrayBuffer) { // 步骤1: 具体的图像处理操作及其他耗时操作 return dataSlice; } function histogramStatistic(pixelBuffer: ArrayBuffer) { // 步骤2: 分成三段并发调度 let number = pixelBuffer.byteLength / 3; let buffer1 = pixelBuffer.slice(0, number); let buffer2 = pixelBuffer.slice(number, number * 2); let buffer3 = pixelBuffer.slice(number * 2); let task1 = new taskpool.Task(imageProcessing, buffer1); let task2 = new taskpool.Task(imageProcessing, buffer2); let task3 = new taskpool.Task(imageProcessing, buffer3); taskpool.execute(task1).then((ret: ArrayBuffer[]) => { // 步骤3: 结果处理 }); taskpool.execute(task2).then((ret: ArrayBuffer[]) => { // 步骤3: 结果处理 }); taskpool.execute(task3).then((ret: ArrayBuffer[]) => { // 步骤3: 结果处理 }); } @Entry @Component struct Index { @State message: string = 'Hello World' build() { Row() { Column() { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { let data: ArrayBuffer; histogramStatistic(data); }) } .width('100%') } .height('100%') } } 使用Worker进行长时间数据分析 本文通过某地区提供的房价数据训练一个简易的房价预测模型,该模型支持通过输入房屋面积和房间数量去预测该区域的房价,模型需要长时间运行,房价预测需要使用前面的模型运行结果,因此需要使用Worker。 ​ 1. DevEco Studio提供了Worker创建的模板,新建一个Worker线程,例如命名为“MyWorker”。 ​ 2. 在主线程中通过调用ThreadWorker的constructor()方法创建Worker对象,当前线程为宿主线程。 import worker from '@ohos.worker'; const workerInstance = new worker.ThreadWorker('entry/ets/workers/MyWorker.ts'); ​ 3. 在宿主线程中通过调用onmessage()方法接收Worker线程发送过来的消息,并通过调用postMessage()方法向Worker线程发送消息。 例如向Worker线程发送训练和预测的消息,同时接收Worker线程发送回来的消息。 // 接收Worker子线程的结果 workerInstance.onmessage = function(e) { // data:主线程发送的信息 let data = e.data; console.info('MyWorker.ts onmessage'); // 在Worker线程中进行耗时操作 } workerInstance.onerror = function (d) { // 接收Worker子线程的错误信息 } // 向Worker子线程发送训练消息 workerInstance.postMessage({ 'type': 0 }); // 向Worker子线程发送预测消息 workerInstance.postMessage({ 'type': 1, 'value': [90, 5] }); ​ 4. 在MyWorker.ts文件中绑定Worker对象,当前线程为Worker线程。 import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker'; let workerPort: ThreadWorkerGlobalScope = worker.workerPort; ​ 5. 在Worker线程中通过调用onmessage()方法接收宿主线程发送的消息内容,并通过调用postMessage()方法向宿主线程发送消息。 如在Worker线程中定义预测模型及其训练过程,同时与主线程进行信息交互。 import worker, { ThreadWorkerGlobalScope, MessageEvents, ErrorEvent } from '@ohos.worker'; let workerPort: ThreadWorkerGlobalScope = worker.workerPort; // 定义训练模型及结果 let result; // 定义预测函数 function predict(x) { return result[x]; } // 定义优化器训练过程 function optimize() { result = {}; } // Worker线程的onmessage逻辑 workerPort.onmessage = function (e: MessageEvents) { let data = e.data // 根据传输的数据的type选择进行操作 switch (data.type) { case 0: // 进行训练 optimize(); // 训练之后发送主线程训练成功的消息 workerPort.postMessage({ type: 'message', value: 'train success.' }); break; case 1: // 执行预测 const output = predict(data.value); // 发送主线程预测的结果 workerPort.postMessage({ type: 'predict', value: output }); break; default: workerPort.postMessage({ type: 'message', value: 'send message is invalid' }); break; } } ​ 6. 在Worker线程中完成任务之后,执行Worker线程销毁操作。销毁线程的方式主要有两种:根据需要可以在宿主线程中对Worker线程进行销毁;也可以在Worker线程中主动销毁Worker线程。 在宿主线程中通过调用onexit()方法定义Worker线程销毁后的处理逻辑。 // Worker线程销毁后,执行onexit回调方法 workerInstance.onexit = function() { console.info("main thread terminate"); } 方式一:在宿主线程中通过调用terminate()方法销毁Worker线程,并终止Worker接收消息。 // 销毁Worker线程 workerInstance.terminate(); 方式二:在Worker线程中通过调用close()方法主动销毁Worker线程,并终止Worker接收消息。 // 销毁线程 workerPort.close(); 二、 I/O密集型任务开发指导 使用异步并发可以解决单次I/O任务阻塞的问题,但是如果遇到I/O密集型任务,同样会阻塞线程中其它任务的执行,这时需要使用多线程并发能力来进行解决。 I/O密集型任务的性能重点通常不在于CPU的处理能力,而在于I/O操作的速度和效率。这种任务通常需要频繁地进行磁盘读写、网络通信等操作。此处以频繁读写系统文件来模拟I/O密集型并发任务的处理。 ​ 1. 定义并发函数,内部密集调用I/O能力。 import fs from '@ohos.file.fs'; // 定义并发函数,内部密集调用I/O能力 @Concurrent async function concurrentTest(fileList: string[]) { // 写入文件的实现 async function write(data, filePath) { let file = await fs.open(filePath, fs.OpenMode.READ_WRITE); await fs.write(file.fd, data); fs.close(file); } // 循环写文件操作 for (let i = 0; i < fileList.length; i++) { write('Hello World!', fileList[i]).then(() => { console.info(`Succeeded in writing the file. FileList: ${fileList[i]}`); }).catch((err) => { console.error(`Failed to write the file. Code is ${err.code}, message is ${err.message}`) return false; }) } return true; } ​ 2. 使用TaskPool执行包含密集I/O的并发函数:通过调用execute()方法执行任务,并在回调中进行调度结果处理。示例中的filePath1和filePath2的获取方式请参见获取应用文件路径。 import taskpool from '@ohos.taskpool'; let filePath1 = ...; // 应用文件路径 let filePath2 = ...; // 使用TaskPool执行包含密集I/O的并发函数 // 数组较大时,I/O密集型任务任务分发也会抢占主线程,需要使用多线程能力 taskpool.execute(concurrentTest, [filePath1, filePath2]).then((ret) => { // 调度结果处理 console.info(`The result: ${ret}`); }) 本文由博客一文多发平台 OpenWrite 发布!

优秀的个人博客,低调大师

HarmonyOS应用开发:鸿蒙Js实战,计算器功能开发!

几天没有更新了,最近上班有点忙,没有及时更新一些常用知识点鉴于之前整理的都是一些原理知识点,大部分描述比较多,突然想到做一个小项目,看还没有鸿蒙js实现计算器的项目,就用半个小时考虑做了一个计算器。 由于时间有限,目前是基本的计算功能,后续会优化成连续计算和功能更全面。 每天学习一点点。 场景: 通过动态设置按钮组件button实现计算器的键盘,通过文本text显示计算的表达书,可以计算+,-,*,/,可以一个一个移除,可以重置 等。 ​ 下面我们开始今天的文章,还是老规矩,通过如下几点来说: 1,实现思路 2,代码解析 3,实现效果 3,总结 一,实现思路 计算器的键盘,本来是想使用grid的 但是有一些默认属性不好控制,等后续组件完善了在做优化,目前grid适合一些均衡布局,通过监听计算符号添加判断逻辑,计算结果也是通过添加的计算类型进行计算,目前支持一级计算,后面做连续计算。 二,代码解析 子组件: 1,hml文件 实用了四个for循环实现了键盘效果,后面想了一下其实一个就能搞定,动态换行就行,时间有限后续优化(总感觉计算器挺简单,其实做起来还需要点时间) <div class="container"> <text class="input-content">{{inputcontent}}</text> <div class="menu-content"> <div class="caluater" for="{{ caluater }}" > <button class="content" onclick="calculatorClick({{ $item.id }})">{{ $item.id }}</button> </div> </div> <div class="menu-content"> <div class="caluater" for="{{ caluater1 }}"> <button class="content2" on:click="calculatorClick({{ $item.id }})">{{ $item.id }}</button> </div> </div> <div class="menu-content"> <div class="caluater" for="{{ caluater2 }}"> <button class="content2" on:click="calculatorClick({{ $item.id }})">{{ $item.id }}</button> </div> </div> <div class="list2-content"> <div class="list3-content"> <div class="list2-content"> <div class="caluater" for="{{ caluater3 }}"> <button class="content2" on:click="calculatorClick({{ $item.id }})">{{ $item.id }}</button> </div> </div> <div class="list2-content"> <div class="caluater" for="{{ caluater4 }}"> <button class="content2" on:click="calculatorClick({{ $item.id }})">{{ $item.id }}</button> </div> </div> </div> <button class="equals" onclick="calculatorResult">=</button> </div> </div> 2,css文件 样式比较简单,主要控制键盘和表达式文本的颜色和大小 .container { flex-direction: column; justify-content: flex-end; align-items: flex-end; width: 100%; } .input-content{ background-color: #00ffffff; text-align: right; font-size: 25px; padding: 10px; font-weight: bold; } .menu-content{ width: 100%; background-color: brown; allow-scale: true; } .caluater { flex-direction: row; width: 100%; height: 70px; background-color: #00ffffff; margin-left: 5px; margin-right: 5px; margin-top: 10px; } .content{ font-size: 30px; font-weight: bold; radius: 10px; width: 100%; height: 100%; text-color: #007EFF; background-color: #A8CCFB; } .content2{ font-size: 30px; font-weight: bold; radius: 10px; width: 100%; height: 100%; text-color: #000000; background-color: #dddcdc; } .list2-content{ flex-direction: row; justify-content: center; align-items: center; background-color: brown; } .list3-content{ flex-direction: column; margin-bottom: 10px; } .equals{ width: 30%; height: 150px; font-size: 30px; font-weight: bold; radius: 10px; margin-left: 5px; margin-right: 5px; } 3,js文件 js中主要实现逻辑,请看具体计算的判断。 import prompt from '@system.prompt'; export default { data: { title: "", inputcontent: "", number1: "", number2: "", type: "", caluater: [ { 'id': "C", }, { 'id': "÷", }, { 'id': "×", }, { 'id': "←", } ], caluater1:[ { 'id': "7", }, { 'id': "8", }, { 'id': "9", }, { 'id': "+", } ], caluater2:[ { 'id': "4", }, { 'id': "5", }, { 'id': "6", }, { 'id': "-", } ], caluater3:[ { 'id': "1", }, { 'id': "2", }, { 'id': "3", } ], caluater4:[ { 'id': "%", }, { 'id': "0", }, { 'id': ".", } ] }, onInit() { }, calculatorClick(result){ this.inputcontent = this.inputcontent+""; let str = this.inputcontent .substring(this.inputcontent.length-1, this.inputcontent.length); switch(result) { case "←": if(this.inputcontent.length > 0) { let str = this.inputcontent .substring(0, this.inputcontent.length - 1); this.inputcontent = str; } break; case "C": this.inputcontent = ""; break; case "+": this.calcula(str,result,"+"); break; case "-": this.calcula(str,result,"-"); break; case "×": this.calcula(str,result,"×"); break; case "÷": this.calcula(str,result,"÷"); break; case ".": if(this.inputcontent.length > 0 && str != ".") { this.inputcontent += result; } break; default: this.inputcontent += result; break; } }, calcula(str,result,cla){ if(this.inputcontent.length > 0 && str != "+" && str != "-" && str != "×" && str != "÷") { this.type = cla; this.inputcontent += result; } else { this.calculatorResult(); } }, calculatorResult(){// 计算结果 var temp = this.inputcontent.split(this.type); console.log("this.inputcontent = "+this.inputcontent) let str = this.inputcontent .substring(this.inputcontent.length-1, this.inputcontent.length); console.log("this.type = "+this.type) if (this.type == "+") { // 加法计算 if(temp.length > 1) { console.log("temp = "+temp) var num1 = parseFloat(temp[0]); var num2 = parseFloat(temp[1]); console.log("num1 = "+num1) console.log("num2 = "+num2) console.log("str = "+str) if(str != "+") { this.inputcontent = num1 + num2; this.type = ""; } } } else if(this.type == "-") { // 减法计算 if(temp.length > 1) { var num1 = parseFloat(temp[0]); var num2 = parseFloat(temp[1]); if(str != "-") { this.inputcontent = num1 - num2; this.type = ""; } } } else if(this.type == "×") { // 乘法计算 if(temp.length > 1) { var num1 = parseFloat(temp[0]); var num2 = parseFloat(temp[1]); if(str != "×") { this.inputcontent = num1 * num2; this.type = ""; } } } else if(this.type == "÷") { // 除法计算 if(temp.length > 1) { var num1 = parseFloat(temp[0]); var num2 = parseFloat(temp[1]); if(str != "÷") { this.inputcontent = num1 / num2; this.type = ""; } } } } } 为了目前的单一计算,现在做了不少的判断,后续做连续计算的时候会有改动,但是目前正常计算没有问题,期待后续更新。 三,实现效果 四,总结 开发计算器最主要的是连续计算,连续计算需要添加计算优先级逻辑,后续考虑通过遍历来判断里面的计算。 计算器界面开发通过常用组件就能实现,实现方式可以自己定。 在开发中验证了NaN,这个空的判断很多方式无效的,他是针对Number做的判断。 功能简单,不喜勿喷。 原创不易,有用就关注一下。要是帮到了你 就给个点赞吧,多谢支持。 觉得不错的小伙伴,记得帮我 点个赞和关注哟,笔芯笔芯~** 有问题请留言或者私信。

优秀的个人博客,低调大师

HarmonyOS实战—实现抖音点赞和取消点赞效果

1. 双击点赞 和 双击取消点赞 如:在抖音中双击屏幕之后就可以点赞,小红心就会变亮 把白色和红色的心形图片复制到 media 下 需要图片的可以自取,下面白色图片由于没有背景,所以显示的是白色的,下载后鼠标点击就能看见了 因为要双击屏幕才能点赞,所以还要给布局组件id 代码实现: ability_main <?xml version="1.0" encoding="utf-8"?> <DirectionalLayout ohos:id="$+id:dl" xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:alignment="center" ohos:orientation="vertical"> <Image ohos:id="$+id:img" ohos:height="match_content" ohos:width="match_content" ohos:image_src="$media:white" ohos:background_element="cyan" > </Image> </DirectionalLayout> MainAbilitySlice package com.xdr630.listenerapplication6.slice; import com.xdr630.listenerapplication6.ResourceTable; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.content.Intent; import ohos.agp.components.Component; import ohos.agp.components.DirectionalLayout; import ohos.agp.components.Image; public class MainAbilitySlice extends AbilitySlice implements Component.DoubleClickedListener { Image image; @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_main); //1.找到图片组件 image = (Image) findComponentById(ResourceTable.Id_img); //找到铺满屏幕布局的对象 DirectionalLayout dl = (DirectionalLayout) findComponentById(ResourceTable.Id_dl); //2.给布局添加双击事件 dl.setDoubleClickedListener(this); } @Override public void onActive() { super.onActive(); } @Override public void onForeground(Intent intent) { super.onForeground(intent); } //如果标记为false,表示没有点赞,此时把白色变为红色 //如果标记为true,表示已经点赞,再次双击后,会把红色变回白色 boolean flag = false; @Override public void onDoubleClick(Component component) { //修改图片的红星就可以了,只需要用到image就行了,所以把image定为成员变量 if (flag){ image.setImageAndDecodeBounds(ResourceTable.Media_white); //取消点赞变成白色,也要把flag设置为false flag = false; }else{ image.setImageAndDecodeBounds(ResourceTable.Media_red); //当启动项目的时候,flag初始值是false,就会走下面的else的代码,变成红色后就要把flag变成true了 flag = true; } } } 运行: 双击屏幕点赞: 双击屏幕后取消点赞: 2. 能否按照抖音的业务去实现呢? 业务分析: 双击屏幕之后点赞。(上面已实现),再次双击屏幕之后,不会取消点赞,只有点击后红心之后才能取消点赞。 单击红心也可以点赞,再次单击红心就会取消点赞 实现思路: 给最外层的布局添加双击事件,双击之后点赞,变成红色心。 如果已经被点赞,那么还是修改为红色心,相当于不做任何处理。 给图片添加单击事件。 如果没有点赞,单击之后,白色心变成红色心。 如果已经点赞了,单击之后,红色心变成白色心。 代码实现: 上面布局文件不变,MainAbilitySlice 如下: 给布局添加双击事件,因为再次双击不会取消点赞,所以把else代码里设置为红色后就把 flag 取反去掉,就不会出现再次双击取消点赞了。 给图片添加单击事件,因为涉及到点赞后为红色,再取消就变为白色,所以要把 flag 变为相反的操作 package com.xdr630.listenerapplication6.slice; import com.xdr630.listenerapplication6.ResourceTable; import ohos.aafwk.ability.AbilitySlice; import ohos.aafwk.content.Intent; import ohos.agp.components.Component; import ohos.agp.components.DirectionalLayout; import ohos.agp.components.Image; public class MainAbilitySlice extends AbilitySlice implements Component.DoubleClickedListener, Component.ClickedListener { Image image; @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_main); //1.找到图片组件 image = (Image) findComponentById(ResourceTable.Id_img); //找到铺满屏幕布局的对象 DirectionalLayout dl = (DirectionalLayout) findComponentById(ResourceTable.Id_dl); //2.给布局添加双击事件 dl.setDoubleClickedListener(this); //3.给图片添加单击事件 image.setClickedListener(this); } @Override public void onActive() { super.onActive(); } @Override public void onForeground(Intent intent) { super.onForeground(intent); } //如果标记为false,表示没有点赞,此时把白色变为红色 //如果标记为true,表示已经点赞,再次双击后,会把红色变回白色 boolean flag = false; @Override public void onDoubleClick(Component component) { //修改图片的红星就可以了,只需要用到image就行了,所以把image定为成员变量 if (flag){ image.setImageAndDecodeBounds(ResourceTable.Media_white); //取消点赞变成白色,也要把flag设置为false flag = false; }else{ image.setImageAndDecodeBounds(ResourceTable.Media_red); //当启动项目的时候,flag初始值是false,就会走下面的else的代码,此时设置为红色,把flag去掉,再次双击后就还是红色了 } } @Override public void onClick(Component component) { if (flag){ image.setImageAndDecodeBounds(ResourceTable.Media_white); flag = false; }else{ image.setImageAndDecodeBounds(ResourceTable.Media_red); flag = true; } } } 运行: 单击红心后: 再次单击红心: 双击屏幕后效果如下,再次双击屏幕就不会取消点赞了,只有点击小红心才能取消点赞

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。