文章目录
项目源码地址: 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 的返回值

函数签名是:
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;
}
这段做了四件事:
- 创建一个
Column节点 - 设置
Column属性 - 遍历子节点
- 把子节点追加到
Column里
关键是这一段:
let child = FrameNodeFactory(kid, context);
node.appendChild(child);
函数调用了自己。
这就是递归。
递归到底在干什么
JSON 页面是树形结构:
Column
Swiper
Image
Image
Row
Column
Text
你不可能只解析一层。解析 Column 时,还要解析它下面的 Swiper、Row、Text。
所以 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。
所以 Column、Row、Swiper 都属于“能装孩子”的节点。
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 不是 Column、Row、Swiper、Image、Text 里的任何一个,就会走到这里。
这其实是一个简单的白名单机制。
小白做扩展时不要上来就支持所有组件。先支持几个常用节点,把流程跑通,再慢慢加。
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,创建节点,设置属性,递归处理子节点。
真正难的是后续扩展。比如支持更多组件、更多属性、更多事件时,这个函数会越来越大。小白练习时别一口吃成胖子,先把 Column、Row、Text、Image 四类节点吃透。
下一篇我们看样式设置函数,比如 setColumnNodeAttr() 和 setRowNodeAttr(),因为 JSON 里的 css 能不能生效,全靠这些属性解析代码。

70

被折叠的 条评论
为什么被折叠?



