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

news2024/11/15 7:31:25

鸿蒙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?: string
  content?: string
  css?: ESObject
  children?: 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.coloradmin.cn/o/2135180.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Unity新闻】Unity的产品命名变化

快速回顾一下Unity产品命名的调整。 在2023年 Unity就宣布版本命名的变化,将使用Unity 6作为最新版本的命名。 具体的规则,在论坛里进行了说明。 以后正式的LTS版本就是Unity 6,将在2024年末发布。 而不管是之前的Runtime费还是今天的费用…

短视频剪辑从简单到复杂,这四款很OK!

作为一个刚刚踏入视频剪辑世界的新手,我最近可是忙得不亦乐乎。我尝试了四款流行的视频剪辑软件,今天,就让我来和大家分享一下我的使用感受,看看哪款软件更适合我们这些初学者。这里先说一句,选择视频剪辑软件就像挑衣…

Python Module 模块详解:模块导入与项目管理的最佳实践

Python Module 模块详解:模块导入与项目管理的最佳实践 文章目录 Python Module 模块详解:模块导入与项目管理的最佳实践一 准备示例代码二 引用 module三 大型项目的模块管理四 完整文件示例五 源码地址 本文详细介绍了 Python 中模块(Modul…

yolo自动化项目实例解析(二)ui页面整理

我们在上一章整理main.py 的if __name__ __main__: 内容还留下面这一段, app QApplication(sys.argv) # 初始化Qt应用ratio screen_width / 2560 # 分辨率比例# 设置全局字体大小# 计算字体大小base_font_size 13# 基准字体大小,适合1920*1080分辨…

不要在这些场景中使用LLM或生成式AI

虽然但是,LLM并不是AI的全部,并不是所有的AI应用场景都适合生成式AI。 在某些用例中,应避免或极其谨慎地使用LLM和GenAI,二者可能并非最佳解决方案。 1. 高风险决策 LLM和生成式AI不适合做出可能对现实世界产生重大影响的高风险…

别人做谷歌seo为什么流量比你多?

如果你确认你的网站技术层面没有问题,那其实无非就是两方面,关键词没选好和用户体验不够好,不要妄想一步登天,选那些看起来搜索量很大的热门关键词,这种属于大家都在做,竞争是非常激烈的,在你的…

华宇TAS应用中间件斩获2024鲲鹏应用创新大赛北京赛区总决赛二等奖!

近日,以“数智未来 因你而来”为主题的创客北京2024鲲鹏应用创新大赛华鲲振宇北京赛区总决赛在北京鲲鹏联合创新中心圆满举办,华宇TAS应用中间件凭借产品竞争力、产品兼容性、技术领先性等优势脱颖而出,斩获鲲鹏原生开发赛道(泛政…

AI为云游戏带来的革新及解决方案:深度技术剖析与未来展望

近期,科技巨头埃隆马斯克与热门国产游戏《黑神话:悟空》的互动,再次引发了公众对AI技术在游戏产业中应用的关注。马斯克,作为特斯拉和SpaceX的掌门人,不仅在科技领域引领风骚,其个人兴趣也广泛涉猎&#xf…

关于使用Mybatis-Plus 自动填充功能失效问题

关于使用Mybatis-Plus 自动填充功能失效问题 关于使用Mybatis-Plus 自动填充功能失效 首先遇到的第一个问题 自动填充失败 或被填充为NULL 原因:字段类型 与 填充类型 不一致导致 解决方法:将类型替换成一致的类型 全部为Date 或 LocalDateTime 即可解…

828华为云征文 | 使用华为云X实例部署图数据库Virtuoso并存储6500万条大数据的完整过程与性能测评

前言 在大数据时代,图数据库以其强大的关系处理能力在复杂网络、社交媒体分析、知识图谱等领域得到了广泛应用。而在云计算的蓬勃发展下,使用云服务器进行图数据库的部署与管理变得更加方便高效。本篇文章将详细介绍如何在华为云X实例上部署开源图数据…

CANFD和CAN最主要的区别

随着汽车电子的高速发展,车内信息的急剧增多,传统的CAN总线的数据传输能力已经很难满足车辆ECU的数据传输需求了,此时CANFD就应运而生了。 CANFD和CAN最主要的区别就是CANFD的ID段和数据段能够以不同的速率传输数据,这就保证了即…

【大模型专栏—进阶篇】语言模型创新大总结——“后起之秀”

大模型专栏介绍 😊你好,我是小航,一个正在变秃、变强的文艺倾年。 🔔本文为大模型专栏子篇,大模型专栏将持续更新,主要讲解大模型从入门到实战打怪升级。如有兴趣,欢迎您的阅读。 &#x1f4…

Prompt最佳实践|指定任务步骤,让ChatGPT不懵逼

在OpenAI的官方文档中已经提供了[Prompt Enginerring]的最佳实践,目的就是帮助用户更好的使用ChatGPT 编写优秀的提示词我一共总结了9个分类,本文讲解第4个分类:指定任务步骤 提供更多的细节要求模型扮演角色使用分隔符指定任务步骤提供样例…

e冒泡排序---复杂度O(X^2)

排序原理: 1.比较相邻的元素。如果前一个元素比后一个元素大,就交换这两个元素的位置。 2.对每一对相邻元素做同样的工作,从开始第一对元素到结尾的最后一对元素。最终最后位置的元素就是最大值, public class 冒泡排序 {public static void main(String[] args) {I…

点亮第一盏LED灯 4): stm32CubeMX配置时钟

嵌入式入门,继续点亮第一盏LED灯,在Stm32CubeMX这个图形界面,一共是需要配置2个地方,1是GPIO引脚,2是时钟,上一篇文章已经将引脚PC13配置为输出引脚,这个引脚需要输出的是低电平,这篇…

这才几天,京东又又又又又又加薪了!

京东 今天的最新消息,京东又又又又又加薪了。 距离我们 京东宣布大幅上调校招薪资 的推文发布才一周多点的时间,京东又宣布加薪了。 好家伙,算上这次,光 2024 年京东就已经宣布了 6 次调薪了: 2024 年初,京…

多文件多子目录makefile

这里写目录标题 1 makefile原理2 MakeFile步骤3 多文件多子目录Makefile实例4 总结附录一:常用Bash指令附录二:常用批处理变量附录三:常用makefile指令 1 makefile原理 编译过程是将高级语言(如C、C等)源代码转换为可…

如何利用数字化智慧法务管理平台,提升企业合规与治理水平?

在当今这个日新月异的时代,企业管理正面临着前所未有的挑战与机遇。随着数字化浪潮的汹涌澎湃,企业治理水平的提升已不再是简单的管理升级,而是需要借助科技的力量,实现智慧化、精细化的管理。而智慧法务管理平台,正是…

电子电路产业园废水处理与资源回收的创新实践

随着电子产品的普及和技术革新步伐的加快,电子电路制造业已成为推动现代科技发展的关键力量之一。然而,随之而来的环保问题不容忽视。电镀工艺作为电子电路生产中的一个核心环节,其产生的含镍废水处理成为了企业必须面对的重要课题。本文将探…

【组件】前端js HEIC/HEIF 转换为JPEG、PNG或GIF格式 苹果格式

【组件】前端js HEIC/HEIF 转换为JPEG、PNG或GIF格式 Heic2any: Client-side conversion of HEIC/HEIF image files to JPEG,PNG, or GIF in the browser.https://alexcorvi.github.io/heic2any/#demo GitHub - alexcorvi/heic2any: Converting HEIF/HEIF image formats to PN…