HarmonyOS7 FrameNodeFactory 如何把 JSON 变成页面?递归建树讲明白


项目源码地址: https://gitcode.com/HarmonyOS_Samples/DynamicComponent

上一篇看懂了 structure.json

这篇看真正把 JSON 变成页面的函数:FrameNodeFactory()。它在 entry/src/main/ets/view/ImperativeView.ets 里,是整个动态页面案例的核心。

小白看到递归别慌,我们一点点拆。

VM 类对应 JSON 节点

文件里先定义了一个 VM

class VM {
  type?: string;
  content?: string;
  css?: ESObject;
  children?: VM[];
  id?: string;
}

它和 JSON 结构是一一对应的。

type 对应节点类型。

content 对应文字或图片资源。

css 对应样式配置。

children 对应子节点。

id 对应特殊节点标识。

所以 FrameNodeFactory(vm, context) 接收到的 vm,本质上就是一个 JSON 节点对象。

FrameNodeFactory 的返回值

A hand-drawn doodle illustration on pure white pap

函数签名是:

function FrameNodeFactory(vm: VM, context: UIContext): FrameNode | null

它接收两个参数:

  • vm:当前要解析的 JSON 节点
  • context:创建节点需要的 UI 上下文

它返回:

  • FrameNode:创建成功的节点
  • null:不认识或创建失败

这和前面 NodeController.makeNode() 的返回思路有点像,最终都要交出能渲染的节点。

遇到 Column 怎么处理

先看 Column 分支:

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;
}

这段做了四件事:

  1. 创建一个 Column 节点
  2. 设置 Column 属性
  3. 遍历子节点
  4. 把子节点追加到 Column

关键是这一段:

let child = FrameNodeFactory(kid, context);
node.appendChild(child);

函数调用了自己。

这就是递归。

递归到底在干什么

JSON 页面是树形结构:

Column
  Swiper
    Image
    Image
  Row
    Column
      Text

你不可能只解析一层。解析 Column 时,还要解析它下面的 SwiperRowText

所以 FrameNodeFactory() 的策略是:

先创建当前节点
再让每个 children 继续调用 FrameNodeFactory
最后把 child append 到当前节点

这就是把 JSON 树变成 UI 节点树。

Row 和 Column 思路一样

Row 分支也差不多:

} 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;
}

区别是创建的是 Row,属性设置调用 setRowNodeAttr()

这说明容器类节点的通用逻辑是一样的:创建自己,设置属性,递归创建孩子。

Swiper 也是容器节点

Swiper 也是容器,只是属性少一点:

} 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;
}

它同样会遍历 children

所以 ColumnRowSwiper 都属于“能装孩子”的节点。

Image 和 Text 是叶子节点

Image 分支没有递归 children

} else if (vm.type === "Image") {
  let node = typeNode.createNode(context, "Image");
  node.attribute.width(vm.css.width);
  node.attribute.height(vm.css.height);
  node.initialize($r(vm.content));
  carouselNodes.push(node);
  return node;
}

Text 分支也没有递归:

} else if (vm.type === "Text") {
  let node = typeNode.createNode(context, "Text");
  node.attribute.fontSize(vm.css.fontSize);
  node.initialize(vm.content);
  return node;
}

因为它们是叶子节点,不需要再挂子节点。

typeNode.createNode 是节点工厂

这个函数多次出现:

typeNode.createNode(context, "Column")
typeNode.createNode(context, "Image")
typeNode.createNode(context, "Text")

它根据字符串创建对应类型的节点。

这就是 JSON 动态页面能成立的关键:JSON 里写 "Column",代码就创建 Column;JSON 里写 "Image",代码就创建 Image

不过要注意:不是 JSON 里随便写什么都能创建。FrameNodeFactory() 里支持了哪些分支,页面就只能创建哪些节点。

不认识的 type 会返回 null

函数最后是:

return null;

如果 vm.type 不是 ColumnRowSwiperImageText 里的任何一个,就会走到这里。

这其实是一个简单的白名单机制。

小白做扩展时不要上来就支持所有组件。先支持几个常用节点,把流程跑通,再慢慢加。

ImperativeController 怎么调用它

FrameNodeFactory() 最后是被 ImperativeController 调用的:

class ImperativeController extends NodeController {
  makeNode(uiContext: UIContext): FrameNode | null {
    carouselNodes = [];
    if (i18n.System.getSystemLanguage() == 'zh-Hans') {
      return FrameNodeFactory(data, uiContext);
    } else {
      return FrameNodeFactory(dataEn, uiContext);
    }
  }
}

这里又回到了前面学过的 NodeController

makeNode() 里直接把 JSON 根节点传给 FrameNodeFactory(),让它返回整棵页面节点树。

整体流程串起来

JSON 动态页面的完整流程是:

NodeContainer 持有 ImperativeController
NodeContainer 调用 makeNode(uiContext)
makeNode 选择 structure.json 或 structure_en.json
FrameNodeFactory 解析 JSON 根节点
遇到容器节点就递归解析 children
最终返回整棵 FrameNode 树
页面显示出来

这和广告案例很像,只是广告案例动态创建的是局部组件,而这里动态创建的是整页内容。

收个尾

FrameNodeFactory() 的核心并不复杂:判断 type,创建节点,设置属性,递归处理子节点。

真正难的是后续扩展。比如支持更多组件、更多属性、更多事件时,这个函数会越来越大。小白练习时别一口吃成胖子,先把 ColumnRowTextImage 四类节点吃透。

下一篇我们看样式设置函数,比如 setColumnNodeAttr()setRowNodeAttr(),因为 JSON 里的 css 能不能生效,全靠这些属性解析代码。

内容概要:本文介绍了一项创新性未发表的研究,即利用多元宇宙优化算法(Multiverse Optimizer, MVO)对分时电价下的需求响应与综合能源系统调度问题进行建模与求解,旨在实现能源系统的经济性、高效性与可持续性运行。该研究构建了包含多种能源设备(如光伏、风机、燃气轮机、储能系统等)及可调节负荷的综合能源系统模型,充分考虑了用户侧的需求响应行为在分时电价机制下的响应特性,通过MVO算法对系统运行成本、能源利用率、碳排放等多目标进行协同优化,实现了日前调度计划的智能决策。研究还提供了完整的MATLAB代码实现,便于研究人员复现实验、验证算法性能,并为进一步研究提供可靠的仿真基础。; 适合人群:具备一定电力系统、优化算法及MATLAB编程基础的科研人员、研究生以及从事能源互联网、综合能源系统规划与运行的技术工程师。; 使用场景及目标:① 学习并掌握多元宇宙优化算法在复杂能源系统调度中的具体应用方法;② 研究分时电价机制如何通过需求响应引导用户参与电网互动,实现削峰填谷;③ 实现综合能源系统(IES)中冷、热、电、气等多种能源的协同优化调度,以降低运行成本、提高新能源消纳能力和系统可靠性;④ 为相关领域的学术研究提供可复现的代码实例和仿真平台。; 阅读建议:此资源以MATLAB代码为核心载体,深入剖析了算法应用与系统建模的全过程。建议读者在学习时,不仅应关注代码的实现细节,更要理解其背后的数学模型、优化目标设定和约束条件的物理意义。建议结合文档中的模型描述,逐步调试代码,观察不同参数和场景下的优化结果,从而深刻掌握综合能源系统优化调度的设计思想与关键技术。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值