HarmonyOS开发实战( Beta5.0)使用ArkUI的FrameNode扩展实现动态布局类框架详解

ops/2024/10/21 6:05:06/

鸿蒙HarmonyOS开发往期必看:

最新版!“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线!(从零基础入门到精通)

HarmonyOS NEXT应用开发性能实践总结


简介

在特定的节假日或活动节点,应用通常需要推送相应主题或内容到首页,但又不希望通过程序更新方式来实现。因此,一般会采用动态布局类框架。动态布局类框架是一种动态生成原生组件树的轻量级框架,可以根据运营需求,在无需重新上架应用的情况下也可以动态地向用户推送新内容。该框架使用了类似于CSS的语法,通过设置不同的样式属性来控制视图的位置、大小、对齐方式等。本文将介绍如何使用ArkUI的FrameNode扩展来实现动态布局类框架,并探讨其带来的性能收益。

ArkUI的声明式扩展在动态框架对接场景下的优势

组件创建更快

在采用声明式前端开发模式时,若使用ArkUI的自定义组件对节点树中的每个节点进行定义,往往会遇到节点创建效率低下的问题。这主要是因为每个节点在JS引擎中都需要分配内存空间来存储应用程序的自定义组件和状态变量。此外,在节点创建过程中,还必须执行组件ID、组件闭包以及状态变量之间的依赖关系收集等操作。相比之下,使用ArkUI的FrameNode扩展,则可以避免创建自定义组件对象和状态变量对象,也无需进行依赖收集,从而显著提升组件创建的速度。

组件更新更快

在动态布局类框架的更新场景中,通常存在一个由树形数据结构ViewModelA创建的UI组件树TreeA。当需要使用新的数据结构ViewModelB来更新TreeA时,尽管声明式前端可以实现数据驱动的自动更新,但这一过程中却伴随着大量的diff操作,如图一所示。对于JS引擎而言,在对一个复杂组件树(深度超过30层,包含100至200个组件)执行diff算法时,几乎无法在120Hz的刷新率下保持满帧运行。然而,使用ArkUI的FrameNode扩展,框架能够自主掌控更新流程,实现高效的按需剪枝。特别是针对那些仅服务于少数特定业务的动态布局框架,利用这一扩展,可以实现极其迅速的更新操作。

图一 

图一

直接操作组件树

使用声明式前端还存在组件树结构更新操作困难的痛点,比如将组件树中的一个子树从当前子节点完整移到另一个子节点,如图二所示。使用声明式前端无法直接调整组件实例的结构关系,只能通过重新渲染整棵组件树的方式实现上述操作。而使用ArkUI的FrameNode扩展,则可以通过操作FrameNode来很方便的操控该子树,将其移植到另一个节点,这样只会进行局部渲染刷新,性能更优。

图二 

图二

场景示例

下面使用视频首页刷新图片资源作为场景,如图三所示,来介绍如何使用ArkUI的FrameNode扩展来实现。

图三

图三

ArkUI的声明式扩展使用

一个简化的动态布局类框架的DSL一般会使用JSON、XML等数据交换格式来描述UI,下面使用JSON为例进行说明。 本案例相关核心字段含义如下表所示:

标签含义
type描述UI组件的类型,通常与原生组件存在一一对应的关系,也可能是框架基于原生能力封装的某种组件
content文本,图片类组件的内容
css描述UI组件的布局特性
  1. 定义视频首页UI描述数据如下:
{"type": "Column","css": {"width": "100%"},"children": [{"type": "Row","css": {"width": "100%","padding": {"left": 15,"right": 15},"margin": {"top": 5,"bottom": 5},"justifyContent": "FlexAlign.SpaceBetween"},"children": [{"type": "Text","css": {"fontSize": 24,"fontColor": "#ffffff"},"content": "首页"},{"type": "Image","css": {"width": 24,"height": 24},"content": "app.media.search"}]},{"type": "Swiper","css": {"width": "100%"},"children": [{"type": "Image","css": {"height": "40%","width": "100%"},"content": "app.media.movie1"},{"type": "Image","css": {"height": "40%","width": "100%"},"content": "app.media.movie2"},{"type": "Image","css": {"height": "40%","width": "100%"},"content": "app.media.movie3"}]},{"type": "Row","css": {"width": "100%","padding": {"left": 15,"right": 15},"margin": {"top": 15,"bottom": 15},"justifyContent": "FlexAlign.SpaceBetween"},"children": [{"type": "Text","css": {"width": 75,"height": 40,"borderRadius": 60,"fontColor": "#000000","backgroundColor": "#ffffff"},"content": "精选"},{"type": "Text","css": {"width": 75,"height": 40,"borderRadius": 60,"fontColor": "#000000","backgroundColor": "#808080"},"content": "电视剧"},{"type": "Text","css": {"width": 75,"height": 40,"borderRadius": 60,"fontColor": "#000000","backgroundColor": "#808080"},"content": "电影"},{"type": "Text","css": {"width": 75,"height": 40,"borderRadius": 60,"fontColor": "#000000","backgroundColor": "#808080"},"content": "综艺"}]},{"type": "Row","css": {"width": "100%","padding": {"left": 15,"right": 15},"margin": {"top": 5,"bottom": 5},"justifyContent": "FlexAlign.SpaceBetween"},"children": [{"type": "Text","css": {"fontSize": 24,"fontColor": "#ffffff"},"content": "每日推荐"},{"type": "Text","css": {"fontSize": 20,"fontColor": "#ffffff","opacity": 0.5},"content": "更多"}]},{"type": "Row","css": {"width": "100%","padding": {"left": 15,"right": 15},"margin": {"top": 5,"bottom": 5},"justifyContent": "FlexAlign.SpaceBetween"},"children": [{"type": "Column","css": {"alignItems": "HorizontalAlign.Start"},"children": [{"type": "Image","css": {"height": 120,"width": 170,"borderRadius": 10},"content": "app.media.movie4"},{"type": "Text","css": {"fontColor": "#ffffff"},"content": "电影1"}]},{"type": "Column","css": {"alignItems": "HorizontalAlign.Start"},"children": [{"type": "Image","css": {"height": 120,"width": 170,"borderRadius": 10},"content": "app.media.movie5"},{"type": "Text","css": {"fontColor": "#ffffff"},"content": "电影2"}]}]},{"id": "refreshImage","type": "Text","css": {"width": 180,"height": 40,"borderRadius": 60,"fontColor": "#ffffff","backgroundColor": "#0000FF"},"content": "刷新"}]
}
  1. 定义相应数据结构用于接收UI描述数据,如下:
class VM {type?: stringcontent?: stringcss?: ESObjectchildren?: VM[]id?: string
}
  1. 自定义DSL解析逻辑,且使用carouselNodes保存轮播图节点,方便后续操作节点更新,如下:
// 存储图片节点,方便后续直接操作节点
let carouselNodes: typeNode.Image[] = [];/*** 自定义DSL解析逻辑,将UI描述数据解析为组件** @param vm* @param context* @returns*/
function FrameNodeFactory(vm: VM, context: UIContext): FrameNode | null {if (vm.type === "Column") {let node = typeNode.createNode(context, "Column");setColumnNodeAttr(node, vm.css);vm.children?.forEach(kid => {let child = FrameNodeFactory(kid, context);node.appendChild(child);});return node;} else if (vm.type === "Row") {let node = typeNode.createNode(context, "Row");setRowNodeAttr(node, vm.css);vm.children?.forEach(kid => {let child = FrameNodeFactory(kid, context);node.appendChild(child);});return node;} else if (vm.type === "Swiper") {let node = typeNode.createNode(context, "Swiper");node.attribute.width(vm.css.width);node.attribute.height(vm.css.height);vm.children?.forEach(kid => {let child = FrameNodeFactory(kid, context);node.appendChild(child);});return node;} else if (vm.type === "Image") {let node = typeNode.createNode(context, "Image");node.attribute.width(vm.css.width);node.attribute.height(vm.css.height);node.attribute.borderRadius(vm.css.borderRadius);node.attribute.objectFit(ImageFit.Fill)node.initialize($r(vm.content));carouselNodes.push(node);return node;} else if (vm.type === "Text") {let node = typeNode.createNode(context, "Text");node.attribute.fontSize(vm.css.fontSize);node.attribute.width(vm.css.width);node.attribute.height(vm.css.height);node.attribute.width(vm.css.width)node.attribute.borderRadius(vm.css.borderRadius)node.attribute.backgroundColor(vm.css.backgroundColor);node.attribute.fontColor(vm.css.fontColor);node.attribute.opacity(vm.css.opacity);node.attribute.textAlign(TextAlign.Center)// 使用id来标识特殊节点,方便抽出来单独操作if (vm.id === 'refreshImage') {// 因为frameNode暂时没有Button组件,因此使用Text代替,给该组件绑定点击事件node.attribute.onClick(() => {carouselNodes[1].initialize($r('app.media.movie6'))carouselNodes[2].initialize($r('app.media.movie7'))carouselNodes[3].initialize($r('app.media.movie8'))carouselNodes[4].initialize($r('app.media.movie9'))carouselNodes[5].initialize($r('app.media.movie10'))node.attribute.visibility(Visibility.Hidden);})}node.initialize(vm.content);return node;}return null;
}function setColumnNodeAttr(node: typeNode.Column, css: ESObject) {node.attribute.width(css.width);node.attribute.height(css.height);node.attribute.backgroundColor(css.backgroundColor);if (css.alignItems === "HorizontalAlign.Start") {node.attribute.alignItems(HorizontalAlign.Start)}
}function setRowNodeAttr(node: typeNode.Row, css: ESObject) {node.attribute.width(css.width);if (css.padding !== undefined) {node.attribute.padding(css.padding as Padding)}if (css.margin !== undefined) {node.attribute.margin(css.margin as Padding)}node.attribute.justifyContent(FlexAlign.SpaceBetween)
}
  1. 使用NodeContainer组件嵌套ArkUI的FrameNode扩展和ArkUI的声明式语法,如下:
/*** 继承NodeController,用于绘制组件树*/
class ImperativeController extends NodeController {makeNode(uiContext: UIContext): FrameNode | null {return FrameNodeFactory(data, uiContext);}
}@Component
struct ImperativeView {controller: ImperativeController = new ImperativeController();build() {Column() {NodeContainer(this.controller)}.height('100%').width('100%').backgroundColor(Color.Black)}
}

性能对比

下面以场景示例中的两种方案实现,通过DevEcho Studio的profile工具抓取Trace进行性能分析比对。

  1. 声明式前端开发模式下刷新图片资源场景的完成时延为9.8ms(根据设备和场景不同,数据会有差异,本数据仅供参考),如图四所示。

图四 

图四

  1. FrameNode扩展模式下刷新图片资源场景的完成时延为7.6ms(根据设备和场景不同,数据会有差异,本数据仅供参考),如图五所示。

图五 

图五

总结

综上所述,在动态布局类场景下,相对于声明式写法,使用ArkUI的FrameNode扩展更具有优势,能缩短响应时延,带来的性能收益更高。因此对于需要使用动态布局类框架的场景,建议优先使用ArkUI的FrameNode扩展来实现。

最后

小编在之前的鸿蒙系统扫盲中,有很多朋友给我留言,有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)路线图、学习视频、文档用来跟着学习是非常有必要的。 

如果你是一名有经验的资深Android移动开发、Java开发、前端开发、对鸿蒙感兴趣以及转行人员

鸿蒙 NEXT 全栈开发学习笔记  希望这一份鸿蒙学习文档能够给大家带来帮助~


鸿蒙(HarmonyOS NEXT)最新学习路线

该路线图包含基础技能、就业必备技能、多媒体技术、六大电商APP、进阶高级技能、实战就业级设备开发,不仅补充了华为官网未涉及的解决方案

路线图适合人群:

IT开发人员:想要拓展职业边界
零基础小白:鸿蒙爱好者,希望从0到1学习,增加一项技能。
技术提升/进阶跳槽:发展瓶颈期,提升职场竞争力,快速掌握鸿蒙技术

2.视频学习教程+学习PDF文档

HarmonyOS Next 最新全套视频教程

  纯血版鸿蒙全套学习文档(面试、文档、全套视频等)       

​​

总结

参与鸿蒙开发,你要先认清适合你的方向,如果是想从事鸿蒙应用开发方向的话,可以参考本文的学习路径,简单来说就是:为了确保高效学习,建议规划清晰的学习路线


http://www.ppmy.cn/ops/110127.html

相关文章

swagger-bootstrap-ui页面空白,也没报错

回想起来,代码层面没有进行什么大的调整,增加了配置文件,application.yml中的 spring:profiles:active: sms # dev --> smsname: sms-server swagger配置未调整导致空白 修改profile 问题解决

物联网控制箱

随着科技的飞速发展,物联网(Internet of Things, IoT)技术已经深入我们生活的方方面面,从智能家居到智慧城市,从工业制造到农业管理,物联网正以前所未有的方式改变着世界。唯众的物联网控制箱正是这一趋势下…

2024年语音识别转文字工具的崛起

无论是繁忙的会议记录、远程教学的即时笔记,还是日常生活的语音备忘,只需轻轻一说,便能瞬间转化为清晰可编辑的文字,这种便捷与高效无疑为现代生活增添了无限可能。本文将带你深入探索语音识别转文字工具的奥秘。 1.365在线转文字…

TCP全连接队列和tcpdump抓包

全连接队列 listen第二个参数 服务器在调用listen的时候,listen的第二个参数 1,就是TCP全连接队列的长度。 当客户端的连接进入established 状态后,如果服务器没有调用accept将连接取走,那么该连接就会待在TCP全连接队列中&a…

精准电商营销:基于京东商品详情API返回值的数据分析

精准电商营销是现代电商领域中的关键策略之一,它依赖于对大量数据的深入分析和有效应用。京东商品详情API(Application Programming Interface)为商家和开发者提供了一种获取商品详细信息的方式,这些详细信息包括但不限于商品价格…

Kafka 实战演练:创建、配置与测试 Kafka全面教程

文章目录 1.配置文件2.消费者1.注解方式2.KafkaConsumer 3.依赖1.注解依赖2.KafkaConsumer依赖 本文档只是为了留档方便以后工作运维,或者给同事分享文档内容比较简陋命令也不是特别全,不适合小白观看,如有不懂可以私信,上班期间都…

【Petri网导论学习笔记】Petri网导论入门学习(四)

Petri网导论入门学习(四) Petri 网导论学习笔记(4)1.2 标识网与网系统定义 1.8定义 1.9例 1.4存在空标识网的几种情况1.2 小结1.2学习完应达到的要求 Petri 网导论学习笔记(4) 如需学习转载请注明原作者并附…

开源模型应用落地-qwen2-7b-instruct-LoRA微调-unsloth(让微调起飞)-单机单卡-V100(十六)

一、前言 本篇文章将在v100单卡服务器上,使用unsloth去高效微调QWen2系列模型,通过阅读本文,您将能够更好地掌握这些关键技术,理解其中的关键技术要点,并应用于自己的项目中。 使用unsloth能够使模型的微调速度提高 2 - 5 倍。在处理大规模数据或对时间要求较高的场景下,…