• [技术干货] 【HarmonyOS】选择列表数量限制
    // 使用 DevEco Studio 3.1.1 Release 及以上版本,API 版本为 api 9 及以上。// 主要功能及注意事项:// 该组件展示了一个乘客选择列表。列表中的每个项目包含一个复选框和对应的乘客姓名,// 用户点击任意一项即可切换其选中状态。组件通过限制最多只能选择5名乘客,// 并在超过限制时通过promptAction模块弹出 toast 提示用户。// 注意,代码中的Checkbox组件目前设置为不可更改(enabled(false)),// 在实际应用中可以根据需求决定是否允许用户手动改变复选框状态。// 导入提示操作模块import promptAction from '@ohos.promptAction';// 定义数据模型类ItemDataclass ItemData { // 名字属性 name: string; // 是否选中属性 isSelect: boolean; // 构造函数初始化数据 constructor(name: string, isSelect: boolean) { this.name = name; this.isSelect = isSelect; }}// 标记为入口文件并创建组件@Entry@Componentstruct test { // 状态变量arr用于存储ItemData对象数组 @State arr: Array<ItemData> = [ new ItemData('赵大', false), new ItemData('钱二', false), new ItemData('张三', false), new ItemData('李四', false), new ItemData('王五', false), new ItemData('周六', false), new ItemData('李七', false), new ItemData('朱八', false) ]; // 构建UI组件的方法 build() { // 创建垂直方向布局 Column() { // 显示提示文本 Text('请选择乘客,最多限五人') .margin({ top: '60lpx', left: '50lpx', bottom: '10lpx' }); // 遍历存储乘客信息的数据数组 ForEach(this.arr, (item: ItemData, index: number) => { // 创建水平方向布局 Row() { // 创建复选框组件,禁用修改(此处可能是样式演示,实际应用中可去除.enabled(false)) Checkbox() .enabled(false) .select(item.isSelect) .width('41lpx') .height('41lpx') .selectedColor("#FF53B175"); // 显示乘客姓名文本 Text(item.name) .fontSize('27lpx') .margin({ left: '10lpx' }) .fontWeight(400) .fontColor(item.isSelect ? "#FF53B175" : "#FF181725") // 当行组件点击事件处理 } .onClick(() => { // 反转当前项的选中状态 item.isSelect = !item.isSelect; // 计算已选中乘客数量 let isSelectCount = 0; for (let i = 0; i < this.arr.length; i++) { if (this.arr[i].isSelect) { isSelectCount++; } } // 如果已选中超过5人,则恢复当前项未选中状态并弹出提示 if (isSelectCount > 5) { item.isSelect = !item.isSelect; try { // 使用promptAction模块显示toast消息 promptAction.showToast({ message: '最多限五人', duration: 2000, bottom: '375lpx' }); } catch (error) { // 忽略错误 } return; } // 更新数组中对应项的状态 this.arr[index] = new ItemData(item.name, item.isSelect); }) // 设置行组件的边距 .margin({ left: '40lpx', top: '10lpx' }) }) } // 设置Column组件的整体样式 .width('100%') .height('100%') .backgroundColor("#FFF2F3F2") .justifyContent(FlexAlign.Start) .alignItems(HorizontalAlign.Start); }}转载自https://www.cnblogs.com/zhongcx/articles/18433333
  • [技术干货] 【HarmonyOS】搜索历史记录
    // 注:当前代码基于宽度为720的设计稿进行适配,使用lpx单位。// 整段代码描述的功能:// 该代码实现了一个简单的搜索功能组件,其中包括:// 1. 输入框:用户可以在此输入要搜索的内容;// 2. 搜索按钮:点击后,将当前输入内容添加到搜索历史记录的首位,若有重复则移除重复项,并保持历史记录不超过10条;// 3. 搜索历史标题和清空记录按钮:展示搜索历史记录列表,并提供清空全部历史记录的功能;// 4. 搜索历史记录列表:按照时间最近的顺序显示搜索历史记录,最多显示10条。@Entry@Componentstruct test { // 定义状态变量,用于存储输入框的当前值 @State inputValue: string = '' // 定义状态变量,用于存储搜索历史记录的数组 @State historyValueArr: Array<string> = [ '张三', '李四', '举头望明月', '低头思故乡', 'HarmonyOs', '不可能,绝对不可能' ] // 构建UI组件 build() { // 主体内容使用Column布局,垂直堆叠组件 Column() { // 输入框和搜索按钮组合,使用Row布局,水平排列 Row() { // 创建一个TextInput输入框 TextInput({ placeholder: '请输入内容', text: this.inputValue }) .width('524.31lpx') // 设置宽度 .height('70lpx') // 设置高度 .fontSize('27lpx') // 设置字体大小 .backgroundColor("#ffffff") // 设置背景颜色 // 输入框内容改变时,同步更新状态变量inputValue .onChange((value) => { this.inputValue = value }) // 创建一个搜索按钮 Button('搜索') // 按钮点击事件,处理搜索逻辑 .onClick(() => { // 遍历历史记录数组,若找到与输入框内容相同的记录,从数组中移除 for (let i = 0; i < this.historyValueArr.length; i++) { if (this.historyValueArr[i] === this.inputValue) { this.historyValueArr.splice(i, 1); break; } } // 将输入框内容添加到历史记录数组的首位 this.historyValueArr.unshift(this.inputValue); // 若历史记录超过10条,则移除最后一项 if (this.historyValueArr.length > 10) { this.historyValueArr.splice(this.historyValueArr.length - 1); } }) } // 设置Row组件的宽度、对齐方式和内外边距 .width('100%') .justifyContent(FlexAlign.SpaceBetween) .padding({ left: '37lpx', top: '11lpx', bottom: '11lpx', right: '15lpx' }) // 搜索历史标题和清除记录按钮组合,同样使用Row布局 Row() { // 搜索历史标题 Text('搜索历史').fontSize('31lpx').fontColor("#000000") // 清空记录按钮 Text('清空记录') .fontSize('27lpx').fontColor("#828385") // 清空记录按钮点击事件,清空历史记录数组 .onClick(() => { this.historyValueArr.length = 0; }) } // 设置Row组件的宽度、对齐方式和内外边距 .width('100%') .justifyContent(FlexAlign.SpaceBetween) .padding({ left: '37lpx', top: '11lpx', bottom: '11lpx', right: '37lpx' }) // 使用Flex布局,按行(FlexDirection.Row)包裹搜索历史记录 Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, }) { // 遍历历史记录数组,创建Text组件展示每一条历史记录 ForEach(this.historyValueArr, (item: string, value: number) => { Text(item) .padding({ left: '15lpx', right: '15lpx', top: '7lpx', bottom: '7lpx' }) // 设置背景颜色、圆角和间距 .backgroundColor("#EFEFEF") .borderRadius(10) .margin('11lpx') }) } // 设置Flex容器的宽度和内外边距 .width('100%') .padding({ left: '26lpx', top: '11lpx', bottom: '11lpx', right: '26lpx' }) } // 设置Column容器的宽度、高度和背景颜色 .width('100%') .height('100%') .backgroundColor("#F8F8F8") }}转载自https://www.cnblogs.com/zhongcx/articles/18433330
  • [年度盛典] 2024年度盛典分享|《星星之火,可以燎原——从鸿蒙小白到校园布道师的故事》张昊阳
    星星之火,可以燎原——从鸿蒙小白到校园布道师的故事亲爱的开发者朋友们:大家好!今天想和大家分享一段关于梦想、挑战和传承的故事——也就是我从鸿蒙小白到校园布道师的旅程。接过火种今年年初,我从前辈的手中,接过了鸿蒙的火种。那时,我只是一个对科技充满好奇的普通学生,但在华为校园公开课上,李林峰老师的生动讲解点燃了我心中的火苗。HarmonyOS NEXT的全新架构、分布式软总线、超级终端...这些前沿技术让我看到了一个全场景的万物互联的智慧世界。我决定投身于鸿蒙开发,开始了我的技术探险。微光成炬这条从0到1的开发之路却并不简单。当时正处于鸿蒙NEXT生态建设初期,开发文档还不够完善,网络传输、滑动卡顿、卡片数据同步等问题分分钟让我想要放弃。但是,就像火苗在风中摇曳,却始终不灭,我在广大开发者朋友们的帮助和自身努力下,逐渐克服了这些困难。今年4月份,仅用一个月的时间,我和小伙伴们不仅开发上架了应用,还获了奖。我们的项目,采用HarmonyOS NEXT的最新技术特性,实现了跨设备接续,支持多设备协同和同步的全新体验。如此丰富和全面的鸿蒙开放能力,如此简单的开发语言和配套工具,帮助我们迅速将脑海中的想象变为现实。在校园调查中,逸校园的满意度高达95%,同学们对鸿蒙原生应用带来的原生精致、易用、流畅、安全、智能、互联的使用体验一致地好评。造炬成阳在开发过程中,我们受到了徐彬老师的极大帮助。徐老师是一位华为开发者布道师,他通过深入浅出的课堂帮助我们快速入门。这让我意识到,作为开发者,我们不仅要做技术的探索者,也要做技术的传播者。今年8月份,我也成为了一名华为开发者布道师,立志将鸿蒙技术的力量带给更多同学。我在校内筹办了华为创新俱乐部,开始推广鸿蒙技术。我通过实际操作和案例演示,展示鸿蒙技术的独特魅力。半年的时间里,我已经将手中的火苗传递给了200多名同学。星星之火,可以燎原。星星之火,可以燎原作为一名校园开发者,我深知技术创新的力量。我相信,在华为技术的支持下,未来会有更多像我一样的开发者涌现出来。作为华为开发者布道师的一员,无论是老师、学生,还是企业布道师,我们在逐渐成长为照亮全国乃至全球的火炬。朋友们,让我们一起,用千万火苗,照亮鸿蒙新世界的每一个角落。谢谢大家!
  • [技术干货] 华为布道师中国人民大学站ICT及华为云技术布道
    2024年11月22日,华为开发者布道师于人民大学站开展布道师华为ICT创新赛和编程赛技术案例分享技术布道案例分享,并在分享中对相关疑问分析解答。首先介绍了基于鸿蒙生态的低光照增强安全辅助驾驶系统:低光照增强安全辅助驾驶系统以华为云作为训练的平台,以MindSpore作为框架,通过整合红外相机与可见光相机的特征,利用特征互补模型在软件平台控制下进行数据处理。系统采用Hispark IPC通讯协议实现各设备间的通信,包括红外相机、可见光相机、深度相机以及车辆显示屏等,这些设备采集的数据会上传到华为云进行进一步的处理和存储。在华为云的平台上,智能决策单元和传感器单元协同工作,收集并分析环境信息,同时应用云端算力对算法模型进行部署以优化系统性能。系统还具备低光照工作能力,能够实时显示感知数据,并在必要时进行决策预警,确保驾驶安全。此外,系统还展示了与Harmony OS生态的兼容,如手表等设备的联动提醒,以及红外点阵投影等高级功能,为驾驶者提供全面的安全辅助。并介绍了技术方案所涉及的关键技术:(1)传感器标定与图像配准技术:应用传感器标定与图像配准技术,对传感器采集到的图像数据进行进一步的配准以及标定。其中,本技术包括了双目视觉系统、交通目标识别方法、深度迭代配准算法来实现图像标定以及配准。其中,双目视觉系统基于深度迭代的配准方法主要使用CNN进行特征的提取或者代替传统配准算法中相似性度量的计算函数。使用深度学习网络对输入的图像对进行特征提取与相似性测度,能够实现配准图像的生成与图像配准精度的判别。在实际检测中,大大降低了系统的计算时间,提高了图像配准以及标定的精度。(2)图像特征融合感知技术在图像处理过程中还应用到了图像特征融合感知技术。在对标定以及配准好的图像进行特征融合的过程中,项目团队通过Mindspore训练的AI神经运算专用芯片,运用多源数据融合感知技术与交通安全隐患的智能检测从而识别不同模型,实现多源数据的精确融合。最终,在这些技术的应用下,通过不同类型相机采集到的图像特征进行融合感知,在低光照等不同恶劣环境(3)辅助决策预警与风险评估技术在辅助决策以及预警层面应用HiSpark-WiFi-IoT套件为主的智能决策模块来进行实现。通过紧急避障系统中搭建的感知层、规划层、决策层实现安全隐患智能评定与决策预警的相关功能,为显示交互单元提供安全数据信息。在辅助决策预警与风险评估的功能中,终端显示给用户也是十分关键的。所以在显示与交互的单元中开发了搭载Harmony OS的显示终端、多频报警音响等显示报警装置,运行配套开发的鸿蒙APP,实时显示感知数据与决策预警判定结果,可以在低光照、雨雪雾霾等全天候全场景工况下辅助驾驶人员进行判断,提升驾驶安全性与可靠性。接着介绍了基于华为云的制造业咨询服务微调问答助手案例:构建了私有的工业中文知识库问答系统,旨在为用户提供自定义友好、离线可运行的全面解决方案。​我们的系统的亮点包括:多模态交互。支持文本输入、语音输入和文件上传等多种输入方式,实现了多模态交互。用户可以根据实际需求选择最方便的交互方式,提高了系统的灵活性和适用性。同时,多模态交互也考虑到了用户的个体差异,满足了不同用户的使用习惯和需求。多功能对话界面。对话界面不仅支持基本的文本对话,还提供了多会话管理、对话模式切换和语音输入等功能。用户可以方便地切换不同对话会话,选择不同对话模式,以及通过语音输入进行自然交互。这种多功能对话界面提升了用户体验,使得系统更易于使用和操作。权限管理与安全性。系统引入了权限管理功能,通过登录注册和权限认证,确保只有经过授权的用户才能访问和操作知识库。这一功能提高了系统的安全性,防止未经授权的访问和操作,保护敏感信息免受泄露和篡改。知识库管理。系统提供了完整的知识库管理功能,包括知识库创建、文件上传、向量数据库构建、文件检索等。用户可以方便地管理知识库中的信息,通过文件对话和知识库问答等方式进行信息检索,满足不同场景和需求。检索增强的大模型。系统整合了检索增强的大模型,包括文件对话、知识库问答、搜索引擎问答和自定义Agent问答等功能。通过这些功能,系统可以更全面、准确地回答用户的问题,提供更丰富的信息服务。同时,用户可以选择不同的检索方式,根据实际需求获取最合适的答案。基于华为云ModelArts、企业级华为云主机的详细解决方案如下所示:首先介绍使用智能问答助手连接企业知识库实时获取信息。在权限管理方面,智能问答助手上运行着基于用户认证机制,利用安全框架,对用户进行身份验证,确保只有授权用户才能访问知识库;使用加密协议上传用户信息到云端服务中;使用专用SDK与API能够方便地对用户权限进行控制与管理。在系统实现方面,我们使用了微服务架构通过streamlit-authenticator模块对用户进行身份验证;并将认证结果通过后端服务传输到前端界面进行展示;前端界面使用HTML、CSS和JavaScript技术,通过发送请求获得认证结果,使用WebSocket技术实时更新用户界面。其次,介绍整合多模态对话输入技术,实现了与用户的高效交互。它支持传统的文本输入方式,同时引入了先进的语音输入识别技术,能够准确捕捉并实时转换用户的语音指令为文字,以便进行后续处理。这一语音输入功能的实现,依托于Streamlit和Bokeh库,通过在用户界面上设置一个专门的按钮,用户点击后即可激活语音识别。系统内部,js_on_event事件监听器与webkitSpeechRecognition对象协同工作,确保语音识别的准确性和实时性。此外,Omniind还提供了多种对话模板供用户选择,以适应不同咨询场景的需求,从而提升企业咨询服务的质量和效率。最后说明问答助手在知识库构建方面,预先构建了一个事实对应的知识库,用于存储和组织信息。当用户提出问题时,系统会进行问题解析,然后通过知识检索在知识库中寻找相关信息,最终生成答案。在实现过程中,助手采用了知识库构建、问题解析、知识检索和答案生成等一系列步骤,避免推理跳跃问题,确保了回答的准确性和相关性。为了验证系统的有效性,进行了功能测试。测试中,基于自定义数据集创建了Digital Twin领域的知识库,并提出了一系列问题,观察系统是否能够正确检索知识库并给出相关答案。此外,系统还提供了一个用户界面,用户可以通过这个界面与知识库进行交互,提出问题并获取答案。这个界面设计直观,易于使用,使得用户能够方便地获取所需的信息。通过这种方式,Omniind智能问答助手不仅提高了信息检索的效率,也提升了用户获取知识的体验。本次项目展示是华为开发者布道师​首次对ICT实战技术案例及华为云企业级边云部署行业解决方案进行高校技术布道,希望后续能够带给大家更多具有行业价值和实践意义的布道案例 。欢迎大家加入华为开发者布道师的大家庭,成为优秀的华为云开发者!
  • [课程学习] 【云学堂专家直播课】手把手教你玩转鸿蒙短视频应用案例
    直播主题:手把手教你玩转鸿蒙短视频应用案例直播时间:2024.12.09 16:00-18:00直播链接:cid:link_1直播老师:Cindy 华为云学堂技术讲师 直播简介:本期直播带你了解鸿蒙应用的基础组件和容器组件,通过短视频应用案例进行UI界面的搭建,熟悉组件的核心属性和事件,助力开发者高效开发!活动信息:鸿蒙应用入门级开发者认证训练营旨在助力开发者从零基础学习到实现鸿蒙应用构建的快速入门!我们将由华为云专家团队打造的免费精品认证课程,并安排7场专家直播授课,以及超过2000份总价值50万的认证代金券与云资源代金券,600份认证激励奖品考证就送!活动报名链接:云学堂鸿蒙应用入门级开发者认证训练营_开发者学堂-华为云往期直播回顾:【第一期】深度解析鸿蒙应用入门级开发者认证cid:link_2【第二期】鸿蒙应用入门:轻松掌握ArkTS开发语言cid:link_3
  • [热门活动] 云学堂鸿蒙应用入门级开发者认证训练营邀请好友报名&考证通过公示(名单持续更新中)
    【活动简介】鸿蒙应用入门级开发者认证训练营旨在助力开发者从零基础学习到实现鸿蒙应用构建的快速入门!我们将由华为云专家团队打造的免费精品认证课程,并安排7场专家直播授课,以及超过2000份总价值50万的认证代金券与云资源代金券,600份认证激励奖品考证就送!【活动报名链接】cid:link_3【邀请好友奖励】云资源代金券申请链接:cid:link_2【邀请好友】进度公示(统计数据截止到2024/12/31 8:43)-具体名单可看附件查看链接:https://doc.weixin.qq.com/sheet/e3_AX0AjQaEALkSZt249ZISNKeLGAzNm?scode=ANEAbQegAA8Ei7NYfJAX0AjQaEALk&tab=BB08J2【考证通过】进度公示(统计数据截止到2024/12/31 11:00)-具体名单可看附件查看链接:  https://doc.weixin.qq.com/sheet/e3_AX0AjQaEALk7sWNpVt1R0OPROU7XO?scode=ANEAbQegAA8l6ml7CwAX0AjQaEALk&tab=BB08J2【关于邀请有礼】    点击活动页面右上角“分享有礼”按钮生成邀请链接,邀请好友即可获得云资源代金券。(可用于免费购买云服务器资源)邀请人数云资源代金券350元5100元15300元25500元501000元
  • [技术干货] 【HarmonyOS】抖动动画方案
    HarmonyOs-demo-抖动动画实现方案import curves from "@ohos.curves"/** * 1. 手机号输入框: * - 提供一个用于输入手机号码的文本框。 * - 输入类型限制为电话号码,仅接受数字输入。 * - 最大允许输入长度为13位,符合中国手机号码标准。 * - 显示占位提示文字“请输入手机号”,当输入框为空时可见。 * - 自定义样式包括背景色、字体颜色、大小、边框圆角、光标颜色等。 * - 用户输入时,实时更新`textPhone`状态变量存储输入的手机号码。 * * 2. 动画测试按钮: * - 点击此按钮触发`startAnimation`方法,启动输入框的动画效果。 * * 3. 动画实现: * - `startAnimation`方法使用`animateTo`函数创建一个动画。 * - 动画参数说明: * - `duration`: 动画持续时间设为800毫秒。 * - `curve`: 应用`curves.springCurve`生成的Spring Curve动画曲线,模拟物理弹簧效果。 * - `iterations`: 动画只执行一次。 * - `onEnded`: 动画结束后,通过回调函数更新`doScale`状态,将输入框永久放大10%(从1到1.1)。 * - 在启动动画之前,先将`doScale`重置为初始值(1, 1),确保动画从原尺寸开始计算。 * * 注意事项: * * - **动画逻辑**: * - 动画结束后,输入框的缩放状态将永久保留放大后的尺寸。这是通过`onEnded`回调函数实现的。 * - 为了保证动画正确起始,先将`doScale`恢复到初始尺寸(1, 1)再启动动画。 * * - **输入框行为**: * - 用户目前只能在输入框末尾插入或删除字符,无法在中间位置插入光标。这可能是由于`onChange`事件处理方式导致的限制。 */@Entry@Componentexport struct test { @State textPhone: string = '' @State doScale: ScaleOptions = { x: 1, y: 1 } startAnimation() { animateTo({ duration: 800, curve: curves.springCurve(0, 10, 80, 10), iterations: 1, }, () => { this.doScale = { x: 1.1, y: 1.1 }; }) this.doScale = { x: 1, y: 1 }; } build() { Column() { TextInput({ text: this.textPhone, placeholder: '请输入手机号' }) .margin({ top: 30 }) // .padding({ left: 0 }) .width('658lpx') .height('96lpx') .scale(this.doScale) .backgroundColor(Color.White) .type(InputType.PhoneNumber) .maxLength(13) .placeholderColor("#CBCBCB") .fontColor("#2E2E2E") .fontSize('36lpx') .caretColor('#FF1919') //设置输入框光标颜色。 // .stateStyles({ //设置按下背景颜色 ,一旦设置上,会导致点击空白输入框区域就清空内容 // pressed: this.txtClcik, // focused: this.txtClcik, // }) .onChange((value: string) => { //解决了会导致点击空白输入框区域就清空内容的问题,但有新问题,用户没办法在中间插入光标 this.textPhone = value }) .borderRadius(40) Button('动画测试').margin({ top: 30 }).onClick(() => { this.startAnimation() }) }.width('100%').height('100%') .backgroundColor("#f5f5f5") }}转载自https://www.cnblogs.com/zhongcx/articles/18433328
  • [技术干货] 【HarmonyOS】九宫格拼图游戏
    构建一个简易九宫格拼图游戏应用程序,利用picker从相册选择图片、使用fs拷贝路径、使用PixelMap切分图片import picker from '@ohos.file.picker';import fs from '@ohos.file.fs';import image from '@ohos.multimedia.image';import { common } from '@kit.AbilityKit';import { promptAction } from '@kit.ArkUI';// 定义拼图组件接口interface PuzzlePiece { // 拼图块的像素地图 pixelMap: image.PixelMap; // 原始图片中的索引位置 originalIndex: number;}// 使用装饰器定义页面组件@Entry@Componentstruct Page30 { // 状态变量:选中图片的URI @State imgUri: string = ''; // 状态变量:原始图片的URI @State imgOriginal: string = ''; // 状态变量:存储拼图块的数组 @State puzzlePieces: Array<PuzzlePiece> = []; // 状态变量:记录当前选中的拼图块索引 @State selectedPieceIndex: number = -1; // 弹出图片选择器方法 async openPicker() { try { // 设置图片选择器选项 const photoSelectOptions = new picker.PhotoSelectOptions(); // 限制只能选择一张图片 photoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE; photoSelectOptions.maxSelectNumber = 1; // 创建并实例化图片选择器 const photoViewPicker = new picker.PhotoViewPicker(); // 选择图片并获取图片URI let uris: picker.PhotoSelectResult = await photoViewPicker.select(photoSelectOptions); if (!uris || uris.photoUris.length === 0) return; // 获取选中图片的第一张URI let uri: string = uris.photoUris[0]; // 打开文件读取流 let file = fs.openSync(uri, fs.OpenMode.READ_ONLY); // 获取当前上下文 let context = getContext(this) as common.UIAbilityContext; // 新建一个保存裁剪后图片的路径 let newUrl = context.filesDir + '/test' + new Date().getTime() + '.jpg'; // 复制图片到新的路径 fs.copyFileSync(file.fd, newUrl); // 关闭文件读取流 fs.closeSync(file); // 更新状态变量:设置显示图片的URI this.imgUri = newUrl; // 更新状态变量:保存原始图片的URI this.imgOriginal = uri; // 图片更改时触发的方法 this.imgChange(); } catch (e) { console.error('openPicker', JSON.stringify(e)); } } // 图片更改处理方法 async imgChange() { try { // 创建图片源对象 const imageSource: image.ImageSource = image.createImageSource(this.imgUri); // 图片解码选项 let decodingOptions: image.DecodingOptions = { editable: true, desiredPixelFormat: 3, }; // 创建像素地图 let mPixelMap: image.PixelMap = await imageSource.createPixelMap(decodingOptions); // 获取图片信息 let mImageInfo: image.ImageInfo = await mPixelMap.getImageInfo(); // 计算每个拼图块的大小 const cropSize: image.Size = { width: mImageInfo.size.width / 3, height: mImageInfo.size.height / 3, }; // 清空已有拼图块数据 this.puzzlePieces.splice(0); // 遍历图片生成9个拼图块 let count = 0; for (let row = 0; row < 3; row++) { for (let col = 0; col < 3; col++) { // 创建基于原图的新图片源 const imageSource = image.createImageSource(this.imgUri); // 创建新像素地图 let mPixelMap = await imageSource.createPixelMap(decodingOptions); // 计算裁剪区域 const cropRect: image.Region = { x: col * cropSize.width, y: row * cropSize.height, size: cropSize, }; // 裁剪像素地图 await mPixelMap.crop(cropRect); // 创建并添加拼图块至数组 const piece: PuzzlePiece = { pixelMap: mPixelMap, originalIndex: count++, }; this.puzzlePieces.push(piece); } } // 打乱拼图块顺序 for (let i = this.puzzlePieces.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); let temp: PuzzlePiece = this.puzzlePieces[i]; this.puzzlePieces[i] = this.puzzlePieces[j]; this.puzzlePieces[j] = temp; } } catch (e) { console.error('imgChange', JSON.stringify(e)); } } // 构建UI界面 build() { Column() { // 添加选择图片按钮,点击后调用打开图片选择器方法 Button('选择图片→').onClick(() => { this.openPicker(); }); // 显示原始图片(如果已选择) if (this.imgOriginal) { Text('原始图片↓'); Image(this.imgOriginal) .width('180lpx') .height('180lpx') .objectFit(ImageFit.Contain); } // 如果有拼图块,则显示游戏区 if (this.puzzlePieces.length > 0) { Text('游戏图片↓'); // 游戏区域采用网格布局 Grid() { // 遍历所有拼图块并创建网格项 ForEach(this.puzzlePieces, (item: PuzzlePiece, index: number) => { GridItem() { // 显示拼图块图像 Image(item.pixelMap) .width('200lpx') .height('200lpx') .margin('5lpx') // 根据是否选中调整缩放比例 .scale(this.selectedPieceIndex == index ? { x: 0.5, y: 0.5 } : { x: 1, y: 1 }) // 添加点击事件处理 .onClick(() => { // 处理拼图交换逻辑 if (this.selectedPieceIndex == -1) { this.selectedPieceIndex = index; } else if (this.selectedPieceIndex == index) { this.selectedPieceIndex = -1; } else { let temp: PuzzlePiece = this.puzzlePieces[this.selectedPieceIndex]; this.puzzlePieces[this.selectedPieceIndex] = this.puzzlePieces[index]; this.puzzlePieces[index] = temp; this.selectedPieceIndex = -1; // 检查拼图是否完成 let isSucc: boolean = true; for (let i = 0; i < this.puzzlePieces.length; i++) { console.info('====item', this.puzzlePieces[i].originalIndex, i); if (this.puzzlePieces[i].originalIndex !== i) { isSucc = false; break; } } // 如果拼图完成,弹出提示对话框 if (isSucc) { promptAction.showDialog({ message: '拼图完成!', }); } } }); } }) // End of ForEach } // End of Grid .backgroundColor("#fafafa"); // 设置网格背景色 } } // End of Column .width('100%'); // 设置列宽度为100% }}转载自https://www.cnblogs.com/zhongcx/articles/18433327
  • [技术干货] 【HarmonyOS】Web组件同步与异步数据获取
    Web组件交互同步与异步获取数据的方式示例【html测试文件】src/main/resources/rawfile/Page04.html<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <script> let isEnvSupported = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)')); document.write(`<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0${isEnvSupported ? ', viewport-fit=cover' : ''}">`); </script> <title>Page Title</title> <link rel="stylesheet" href="mycss.css"> <link rel="icon" href="./static/favicon.ico"></head><body><button onclick="fetchSyncData()">获取同步数据</button><button onclick="fetchAsyncData()">获取异步数据</button><p id="dataDisplay"></p></body><script> function fetchSyncData() { console.log('开始获取同步数据'); const result = window.hm.getTestData("测试数据"); document.getElementById("dataDisplay").textContent = result; console.log('完成获取同步数据'); } function fetchAsyncData() { console.log('开始获取异步数据'); window.hm.getTestDataAsync("测试数据").then(value => { document.getElementById("dataDisplay").textContent = value; console.log('完成获取异步数据'); }); }</script></html>【使用示例】src/main/ets/pages/Page04.etsimport web_webview from '@ohos.web.webview';import dataPreferences from '@ohos.data.preferences';class WebService { context: Context constructor(context: Context) { this.context = context } getTestData = (input: string): string => { console.info('输入数据:', input); const resultMap = new Map<string, string>(); resultMap[input] = "我是value"; return JSON.stringify(resultMap); } getTestDataAsync = async (input: string): Promise<string> => { console.info('输入数据:', input); const preferences = await dataPreferences.getPreferences(this.context, 'DATA_STORE'); const value = await preferences.get('KEY', '默认值'); console.info('读取到的值:', value); const resultMap = new Map<string, string>(); resultMap[input] = value; return JSON.stringify(resultMap); }}@Entry@Componentstruct Page04 { controller: web_webview.WebviewController = new web_webview.WebviewController(); webService: WebService = new WebService(getContext(this)); methodList: Array<string> = [] aboutToAppear(): void { this.methodList.splice(0) //清空原数组 console.info('====this.testObjtest', JSON.stringify(this.webService)) Object.keys(this.webService).forEach((key) => { this.methodList.push(key) console.info('====key', key) }); } build() { Column() { Web({ src: $rawfile('Page04.html'), // src: 'https://xxx', controller: this.controller }) .width('100%') .height('100%') .domStorageAccess(true)//设置是否开启文档对象模型存储接口(DOM Storage API)权限。 .javaScriptAccess(true)//设置是否允许执行JavaScript脚本,默认允许执行。 .databaseAccess(true)//设置是否开启数据库存储API权限,默认不开启。 .mixedMode(MixedMode.All)//HTTP和HTTPS混合 .javaScriptProxy({ name: "hm", object: this.webService, methodList: this.methodList, controller: this.controller, }) } .width('100%') .height('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18433324
  • [技术干货] 【HarmonyOS】图片下载加载方案
    【harmonyOS】如果有些图片url用Image组件加载不显示,可以request下载后利用PixelMap加载。需要网络权限:src/main/module.json5 "requestPermissions": [ { "name": "ohos.permission.INTERNET" },src/main/ets/pages/Page010.etsimport { http } from '@kit.NetworkKit'import { image } from '@kit.ImageKit'@Entry@Componentstruct Page010 { @State pixelMap: PixelMap | undefined = undefined @State imgUrl: string = 'https://img0.baidu.com/it/u=3129379276,3231297819&fm=253&fmt=auto&app=120&f=JPEG?w=500&h=500' build() { Column() { Text('加载url显示') Image(this.imgUrl).width('200lpx').height('200lpx') .onError((error: ImageError) => { console.info('error', JSON.stringify(error)) }) .onComplete((event) => { console.info('event', JSON.stringify(event)) }) Button('下载图片').onClick(() => { http.createHttp().request( this.imgUrl, { expectDataType: http.HttpDataType.ARRAY_BUFFER } ).then(async (res) => { console.info('res', JSON.stringify(res)) // 将图片资源转为像素图(PixelMap) this.pixelMap = await image.createImageSource(res.result as ArrayBuffer).createPixelMap() }).catch(() => { console.info('catch') }) }) Text('下载图片后显示') Image(this.pixelMap).width('200lpx') } .width('100%') .height('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18433322
  • [技术干货] 【HarmonyOS】动画旋转方式比较
    【HarmonyOS】学习笔记 比较animation动画三种rotate旋转方式的不同import { curves } from '@kit.ArkUI'@Entry@Componentstruct Page030 { @State rotateValue_1: number = 0 @State rotateValue_2: number = 0 build() { Column({ space: 10 }) { Image($r("app.media.app_icon")) .width('100lpx') .height('100lpx') .rotate({ angle: this.rotateValue_1 }) .animation({ duration: 2000, curve: curves.springMotion(), playMode: PlayMode.Normal, }) Button('切换旋转 (往返)').onClick(() => { //先正向转,再转回来 this.rotateValue_1 = this.rotateValue_1 == 360 ? 0 : 360 }) Button('持续加速旋转').onClick(() => { //持续正转,动画未执行完成就继续加速旋转 this.rotateValue_1 += 360 }) Image($r("app.media.app_icon")) .width('100lpx') .height('100lpx') .rotate({ angle: this.rotateValue_2 }) .animation(this.rotateValue_2 != 0 ? { duration: 2000, curve: curves.springMotion(), playMode: PlayMode.Normal, onFinish: (() => { console.info('----onFinish this.rotateValue', this.rotateValue_2) if (this.rotateValue_2 == 360) { this.rotateValue_2 = 0 } }) } : undefined) Button('条件旋转 (一次性)').onClick(() => { //持续正转,动画未执行完成点击无效 this.rotateValue_2 = 360 }) } .width('100%') .height('100%') }}1. 切换旋转 (往返):• 这种方式允许图像在用户点击时进行一个完整的360度旋转,然后回到初始位置。它适用于需要展示一个“完整循环”动画效果的场景。2. 持续加速旋转:• 在这种方式下,每次用户点击按钮时,图像都会额外旋转360度。这模拟了一种连续加速的效果,适合于创建动态的、无限旋转的视觉效果。3.条件旋转 (一次性):• 这种方式只有在图像没有正在执行动画时才响应用户的点击,从而进行一次360度旋转。它防止了动画的叠加,确保每个动画周期都是独立的,适用于需要精确控制动画触发的情况。转载自https://www.cnblogs.com/zhongcx/articles/18433319
  • [技术干货] 【HarmonyOS】Flex布局文本位置
    【HarmonyOS】使用Flex布局和onAreaChange事件计算并记录多行文本位置的实现方案class PosItem { x: number y: number constructor(x: number, y: number) { this.x = x this.y = y }}@Entry@Componentstruct Page021 { // 原始数据 @State historyValueArr: Array<string> = ['张三', '李四', '举头望明月', '低头思故乡', 'HarmonyOS', '不可能,绝对不可能', '张三和李四', 'city不city'] @State result: string[][] | undefined = undefined private map: Map<string, PosItem> = new Map<string, PosItem>() processPositions(key: string, value: PosItem) { this.map.set(key, value) if (this.map.size == this.historyValueArr.length) { this.convertTo2DArray() } } convertTo2DArray() { console.info('创建一个空的对象来存储行数据'); const rows: ESObject = {}; this.map.forEach((value, key) => { const rowKey = Math.floor(value.y / 26.923076923076923).toString(); if (!rows[rowKey]) { rows[rowKey] = []; } rows[rowKey].push(key); }); // 对每一行中的元素按x值排序 Object.keys(rows).forEach(rowKey => { rows[rowKey].sort((a: string, b: string) => { const posA = this.map.get(a); const posB = this.map.get(b); return posA!.x - posB!.x; }); }); this.result = Object.values(rows); } build() { Column() { Flex({ direction: FlexDirection.Row, wrap: FlexWrap.Wrap, }) { ForEach(this.historyValueArr, (item: string, value: number) => { Text(item) .padding({ left: '15lpx', right: '15lpx', top: '7lpx', bottom: '7lpx' }) .backgroundColor("#EFEFEF") .borderRadius(10) .margin('11lpx') .onAreaChange((previousArea: Area, currentArea: Area) => { console.info(`child currentArea item ${item}`); console.info(`child currentArea ${JSON.stringify(currentArea)}`); this.processPositions(item, new PosItem(currentArea.position.x as number, currentArea.position.y as number)); }) }) } .width('100%') .padding({ left: '26lpx', top: '11lpx', bottom: '11lpx', right: '26lpx' }) .backgroundColor("#F8F8F8") ForEach(this.result, (item: Object, index: number) => { Text(`第${index}组:${JSON.stringify(item)}`).backgroundColor(Color.Pink) }) }.width('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18433315
  • [技术干货] 【HarmonyOS】横向List高度适配
    【HarmonyOS】当list设置横向布局时,list高度默认撑满没有达到预期的高度自适应,可以通过onAreaChange动态修改高度。@Entry@Componentstruct Page148 { build() { Column() { List() { ForEach(['北京', '杭州', '上海'], (item: string, index: number) => { ListItem() { Text(item).fontSize(24) .height(100 * (Math.floor(Math.random() * 3) + 1))//生成一个1到3 随机数,然后+100高度 测试 .backgroundColor(Color.Pink) .margin(10) } }) } .listDirection(Axis.Horizontal) .backgroundColor('#FFF1F3F5') }.width('100%') .height('100%') }}@Entry@Componentstruct Page148 { @State maxItemHeight: number = -1 build() { Column() { List() { ForEach(['北京', '杭州', '上海'], (item: string, index: number) => { ListItem() { Text(item).fontSize(24) .height(100 * (Math.floor(Math.random() * 3) + 1))//生成一个1到3 随机数,然后+100高度 测试 .backgroundColor(Color.Pink) .margin(10) }.onAreaChange((oldArea: Area, newArea: Area) => { if (this.maxItemHeight < newArea.height) { this.maxItemHeight = newArea.height as number } }) }) } .listDirection(Axis.Horizontal) .backgroundColor('#FFF1F3F5') .height(this.maxItemHeight == -1 ? undefined : this.maxItemHeight) }.width('100%') .height('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18433314
  • [技术干货] 【HarmonyOS】TaskPool非阻塞UI
    【HarmonyOS】TaskPool方法不会阻塞UI,如果做上传图片的功能加载Loading记得使用TaskPool,Promise、Async/Await都会阻塞UI【引言】 源于一个论坛帖子:https://developer.huawei.com/consumer/cn/forum/topic/0209156179937828001?fid=0109140870620153026&pid=0308156182059545349发现Promise可能会阻塞UI,尝试使用async或await,但发现它们仍然会导致阻塞。后来看到chaoxiaoshu回复的TaskPool方法,发现使用该方法后UI不再阻塞。因此,我特意编写了一个加载弹窗进行测试,结果同样显示,只有TaskPool方法不会阻塞UI。【代码示例】import { taskpool } from '@kit.ArkTS';@Componentexport struct MyDialog_1 { @Prop dialogID: string @State title: string = '加载中...' build() { Stack() { Column() { LoadingProgress() .color(Color.White).width(100).height(100) Text(this.title) .fontSize(18).fontColor(0xffffff).margin({ top: 8 }) .visibility(this.title ? Visibility.Visible : Visibility.None) } } .onClick(() => { getContext(this).eventHub.emit(this.dialogID, "关闭弹窗") }) .width(180) .height(180) .backgroundColor(0x88000000) .borderRadius(10) .shadow({ radius: 10, color: Color.Gray, offsetX: 3, offsetY: 3 }) }}@Entry@Componentstruct Page28 { @State time3: string = "" @State isShowLoading: boolean = false build() { Stack() { Column({ space: 20 }) { Button("【方案一】测试Promise") .type(ButtonType.Capsule) .onClick(() => { this.isShowLoading = true this.time3 = 'loading...' console.log("start call promise") testPromise(100000000).then((time) => { this.time3 = `耗时:${time}` console.log("promise then") this.isShowLoading = false }) console.log("end call promise") }) Button("【方案二】测试async await") .type(ButtonType.Capsule) .onClick(() => { this.isShowLoading = true this.time3 = 'loading...' console.log("start call promise") this.testPromise() console.log("end call promise") }) Button("【方案三】测试taskpool") .type(ButtonType.Capsule) .onClick(() => { this.isShowLoading = true this.time3 = 'loading...' let task: taskpool.Task = new taskpool.Task(concurrentFunc, 100000000); taskpool.execute(task); task.onReceiveData((time: number) => { this.time3 = `耗时:${time}`; console.log("====end") this.isShowLoading = false }) }) Text(this.time3) }.alignItems(HorizontalAlign.Start) MyDialog_1().visibility(this.isShowLoading ? Visibility.Visible : Visibility.None) }.width('100%').height('100%') } //耗时操作 async testPromise() { let time = await testPromise(100000000) time = new Date().getTime() - time this.time3 = `耗时:${time}毫秒` console.log("promise then") this.isShowLoading = false }}function testPromise(count: number): Promise<number> { return new Promise<number>((resolve) => { let time = Date.now().valueOf() let num = 0 for (let i = 0; i < count; i++) { +num } time = Date.now().valueOf() - time resolve(time) })}@Concurrentfunction concurrentFunc(count: number): void { let time = Date.now().valueOf() let num = 0 for (let i = 0; i < count; i++) { +num } time = Date.now().valueOf() - time taskpool.Task.sendData(time);}【方案一:Promise】优点:易于理解:Promise的语法简单,易于理解和使用。链式调用:可以通过.then进行链式调用,处理多个异步操作。缺点:阻塞UI:在执行耗时任务时,Promise会阻塞UI线程,导致Loading弹窗不能及时显示。【方案二:Async/Await】优点:同步写法:Async/Await 使异步代码看起来像同步代码,更加直观。错误处理:可以使用try/catch块处理错误,使代码更加清晰。缺点:阻塞UI:与Promise类似,Async/Await在执行耗时任务时仍会阻塞UI线程,导致Loading弹窗不能及时显示。【方案三:TaskPool】优点:真正的异步:TaskPool可以将耗时任务放到独立的线程中执行,不会阻塞UI线程,保证了UI的流畅性。数据通信:通过task.onReceiveData可以方便地接收任务结果。缺点:复杂度增加:引入了多线程处理,增加了代码的复杂度和维护成本。【使用注意事项】任务复杂度:如果任务较为简单且不会长时间阻塞UI,可以考虑使用Promise或Async/Await。如果任务较为复杂且耗时较长,建议使用TaskPool以保证UI的流畅性(例如,上传图片时显示加载中)。代码可读性:Promise和Async/Await的语法较为简单,适合初学者使用。TaskPool需要对多线程有一定了解,适合有经验的开发者。性能考虑:TaskPool在处理大量或耗时任务时表现更优,可以显著提升应用性能。Promise和Async/Await在小任务场景下更简洁高效。【总结】选择合适的异步操作方案至关重要。Promise和Async/Await适合处理简单的异步任务,而TaskPool则在处理复杂耗时任务时表现出色。根据实际需求,选择最适合的方案,能有效提升开发效率和用户体验。希望本文对您在异步操作的选择和使用上有所帮助。转载自https://www.cnblogs.com/zhongcx/articles/18433309
  • [技术干货] 【HarmonyOS】TextPicker日期选择
    【HarmonyOS】利用TextPicker实现日期选择框只有【年】或者【年月】或【月日】@Entry@Componentstruct Page39 { @State generateYearMonth: TextCascadePickerRangeContent [] = [] @State generateMonthDay: TextCascadePickerRangeContent [] = [] @State generateYear: TextCascadePickerRangeContent [] = [] generateYearMonthRange(startYear: number, endYear: number): TextCascadePickerRangeContent[] { const range: TextCascadePickerRangeContent[] = []; for (let year = startYear; year <= endYear; year++) { const months: TextCascadePickerRangeContent[] = []; for (let month = 1; month <= 12; month++) { months.push({ text: `${month.toString().padStart(2, '0')}月` // 确保月份是两位数 }); } // 只有当月份数组不为空时,才添加到range中 if (months.length > 0) { range.push({ text: `${year}年`, // 使用年份作为文本 children: months // 只有当月份不为空时,才设置children属性 }); } } return range; // 返回一维数组 } generateMonthDayRange(year: number): TextCascadePickerRangeContent[] { const range: TextCascadePickerRangeContent[] = []; // 生成月份 for (let month = 1; month <= 12; month++) { const days: TextCascadePickerRangeContent[] = []; // 计算每个月的天数 let daysInMonth = new Date(year, month, 0).getDate(); for (let day = 1; day <= daysInMonth; day++) { days.push({ text: `${day.toString().padStart(2, '0')}日` // 确保天数是两位数 }); } range.push({ text: `${month.toString().padStart(2, '0')}月`, // 使用月份作为文本 children: days }); } return range; } generateYearRange(startYear: number, endYear: number): TextCascadePickerRangeContent[] { const range: TextCascadePickerRangeContent[] = []; for (let year = startYear; year <= endYear; year++) { range.push({ text: `${year}年` }); } return range; } aboutToAppear(): void { this.generateYear = this.generateYearRange(2000, 2024); this.generateYearMonth = this.generateYearMonthRange(2000, 2024); this.generateMonthDay = this.generateMonthDayRange(2024); } build() { Column() { Button('指定【年】区间列表') TextPicker({ range: this.generateYear }) .onChange((value: string | string[], index: number | number[]) => { console.info('TextPicker 多列联动:onChange ' + JSON.stringify(value) + ', ' + 'index: ' + JSON.stringify(index)) }) Button('指定【年】【月】区间列表') TextPicker({ range: this.generateYearMonth }) .onChange((value: string | string[], index: number | number[]) => { console.info('TextPicker 多列联动:onChange ' + JSON.stringify(value) + ', ' + 'index: ' + JSON.stringify(index)) }) Button('【月】【日】区间列表') TextPicker({ range: this.generateMonthDay }) .onChange((value: string | string[], index: number | number[]) => { console.info('TextPicker 多列联动:onChange ' + JSON.stringify(value) + ', ' + 'index: ' + JSON.stringify(index)) }) } .height('100%') .width('100%') }}转载自https://www.cnblogs.com/zhongcx/articles/18433308
总条数:109 到第
上滑加载中