
本节目标理解 ArkUI 三类自定义节点FrameNode、RenderNode、BuilderNode的定位与协作关系掌握FrameNode的创建、节点树操作增删查改与挂载显示方法掌握NodeController与NodeContainer的配合机制理解自定义节点的挂载流程掌握RenderNode的自定义渲染能力能够通过getRenderNode()获取渲染节点并进行视觉属性设置与自定义绘制掌握BuilderNode的组件预创建能力能够利用预创建减少首屏加载耗时理解全局自定义组件复用池的设计思路能够利用BuilderNodeNodePool实现跨页面组件复用能够根据场景选择正确的自定义节点方案并遵循节点唯一父节点等约束一、自定义节点体系概述1.1 三类自定义节点的定位ArkUI 提供三种自定义节点对象各自解决不同层面的问题FrameNode表示组件树的实体节点是自定义组件节点。支持节点树操作、自定义绘制与布局、位置查询、动画等能力。适用于需要通过代码动态创建和管理组件节点树的场景可实现声明式组件无法直接满足的灵活 UI 组合与自定义渲染需求。RenderNode自绘制渲染节点支持开发者完成自定义绘制需求。还支持渲染节点树管理添加、删除、查询子节点、背景色与不透明度等视觉属性设置、变换缩放、旋转、平移、变换矩阵、阴影、边框、遮罩与裁剪、模糊效果等能力适用于在 Stage 模型下进行自定义渲染与节点树管理的场景。BuilderNode赋予创建、更新系统组件及其组件树的能力使用Builder装饰的无状态 UI 方法生成组件树。其核心价值在于组件预创建能够在非 build 生命周期中提前创建组件节省组件创建时间。1.2 三者的协作关系NodeController用于管理自定义节点的创建、显示与更新负责将节点挂载到NodeContainer上。NodeContainer 作为自定义占位节点在组件树上为自定义节点提供挂载点。典型协作流程为创建自定义节点FrameNode 或 BuilderNode 持有的 FrameNode→ 实现 NodeController 的makeNode()方法返回节点 → 通过 NodeContainer 将节点挂载到组件树上显示。1.3 核心约束使用自定义节点时必须遵守以下约束节点唯一父节点同一时刻同一个节点有且仅有一个父节点。作为不同节点的子节点存在时可能出现事件响应异常、内容显示异常、位置显示异常、稳定性问题。避免网状或环形结构父子节点相互引用会导致节点树成环形一个子节点有多个父节点会导致节点树成网状。不在预览器中使用FrameNode 节点当前不支持在预览器中使用FrameNode 节点暂不支持拖拽。二、FrameNode组件实体节点2.1 FrameNode 的两类能力FrameNode 提供两类核心能力完全自定义节点提供完整的自定义能力包括自定义测量、布局以及绘制支持节点的动态增、删设置通用属性设置事件回调。通过new FrameNode(uiContext)或typeNode.createNode(Text)等方法创建出来的节点属于完全自定义节点。适用于不自带渲染引擎、需要依赖系统的布局、事件、动画、渲染等能力的场景。系统组件代理节点提供系统组件的代理能力提供遍历节点树的能力通过组件树上的 FrameNode 可以遍历整个组件树并通过节点访问组件的信息或者注册额外的事件监听回调。适用于结合无感监听的接口实现打点、广告 SDK 等业务。2.2 节点树操作FrameNode 提供了增、删、查、改的节点树操作能力。核心方法包括appendChild在最后一个子节点后添加新子节点、removeChild移除子节点、getChild获取指定索引的子节点、getChildrenCount获取子节点数量等。import{FrameNode,NodeController,UIContext}fromkit.ArkUI;classMyNodeControllerextendsNodeController{privaterootNode:FrameNode|nullnull;makeNode(uiContext:UIContext):FrameNode|null{this.rootNodenewFrameNode(uiContext);// 创建子节点并添加到根节点consttextNodetypeNode.createNode(uiContext,Text);textNode.initialize(动态创建的文本节点);textNode.attribute.fontSize(20);textNode.attribute.fontColor(#007DFF);this.rootNode.appendChild(textNode);constbuttonNodetypeNode.createNode(uiContext,Button);buttonNode.initialize(动态按钮);this.rootNode.appendChild(buttonNode);returnthis.rootNode;}// 动态添加节点的方法addNewNode(uiContext:UIContext):void{if(!this.rootNode)return;constnewNodetypeNode.createNode(uiContext,Text);newNode.initialize(新节点${this.rootNode.getChildrenCount()});newNode.attribute.fontSize(16);this.rootNode.appendChild(newNode);}// 移除最后一个子节点removeLastNode():void{if(!this.rootNode||this.rootNode.getChildrenCount()0)return;constlastIndexthis.rootNode.getChildrenCount()-1;constchildthis.rootNode.getChild(lastIndex);this.rootNode.removeChild(child);}}EntryComponentstruct FrameNodeDemo{privatecontroller:MyNodeControllernewMyNodeController();build(){Column({space:16}){NodeContainer(this.controller).width(100%).height(200).backgroundColor(#F5F7FA).borderRadius(12)Row({space:12}){Button(添加节点).onClick((){constuiContextthis.getUIContext();this.controller.addNewNode(uiContext);})Button(移除节点).onClick(()this.controller.removeLastNode())}}.width(100%).padding(16)}}2.3 FrameNode 的性能优势在动态布局场景下FrameNode 相比声明式自定义组件具有显著优势减少创建开销使用 FrameNode 可以避免创建自定义组件对象和状态变量对象无需进行依赖收集从而显著提升组件创建的速度。声明式开发中每个节点都需要在 ArkTS 引擎中分配内存空间来存储自定义组件和状态变量并执行组件 ID、组件闭包以及状态变量之间的依赖关系收集等操作。更新更快对复杂组件树深度超过 30 层包含 100 至 200 个组件执行 diff 算法时几乎无法在 120Hz 刷新率下保持满帧。使用 FrameNode 扩展框架能够自主掌控更新流程实现高效的按需剪枝。直接操作组件树将组件树中的一个子树从当前子节点完整移到另一个子节点时声明式开发范式只能通过重新渲染整棵组件树的方式实现。使用 FrameNode 扩展可以方便地操控该子树将其移植到另一个节点只进行局部渲染刷新。三、RenderNode自定义渲染节点3.1 获取 RenderNode通过 FrameNode 的getRenderNode()方法可以获取 FrameNode 中持有的 RenderNode。RenderNode 提供了appendChild方法在最后一个子节点后添加新的子节点。import{RenderNode,FrameNode,NodeController,UIContext}fromkit.ArkUI;classRenderNodeControllerextendsNodeController{privaterootNode:FrameNode|nullnull;makeNode(uiContext:UIContext):FrameNode|null{this.rootNodenewFrameNode(uiContext);constrootRenderNodethis.rootNode.getRenderNode();if(rootRenderNode!null){// 创建一个 RenderNode 并设置属性constrenderNodenewRenderNode();renderNode.frame{x:0,y:0,width:100,height:100};renderNode.backgroundColor0xFFFF5500;renderNode.borderRadius12;rootRenderNode.appendChild(renderNode);}returnthis.rootNode;}}3.2 视觉属性设置RenderNode 支持丰富的视觉属性设置包括背景色、不透明度、边框、阴影、圆角、变换矩阵缩放、旋转、平移、遮罩与裁剪、模糊效果等。开发者可以通过 RenderNode 的属性动态修改节点的渲染效果。3.3 自定义绘制RenderNode 允许通过重写draw方法自定义绘制内容。绘制时使用DrawContext提供的绘图能力完成自定义渲染import{RenderNode,DrawContext,FrameNode,NodeController,UIContext}fromkit.ArkUI;classCustomDrawControllerextendsNodeController{privaterootNode:FrameNode|nullnull;makeNode(uiContext:UIContext):FrameNode|null{this.rootNodenewFrameNode(uiContext);constrootRenderNodethis.rootNode.getRenderNode();if(rootRenderNode!null){constrenderNodenewRenderNode();renderNode.frame{x:0,y:0,width:200,height:200};renderNode.draw((context:DrawContext){// 使用 DrawContext 进行自定义绘制constcanvascontext.canvas;// 绘制内容...});rootRenderNode.appendChild(renderNode);}returnthis.rootNode;}}3.4 使用约束不建议对 BuilderNode 中的 RenderNode 进行修改操作。BuilderNode 中持有的 FrameNode 仅用于将该 BuilderNode 作为子节点挂载到其他 FrameNode 上对该 FrameNode 或对应的 RenderNode 进行属性设置与子节点操作可能会产生未定义行为包括但不限于显示异常、事件异常、稳定性问题。四、BuilderNode 与组件预创建4.1 预创建原理在声明式范式中组件仅在 build 环节中被创建开发者无法在其他生命周期阶段进行组件创建从而引起页面加载慢等问题。ArkUI 提供的 UI 动态操作支持组件的预创建可以在非 build 生命周期中进行组件创建创建后的组件可以进行属性设置、布局计算等操作之后在页面加载时进行使用从而极大提升页面响应速度。利用组件预创建机制可以在动画执行过程的空闲时间进行组件预创建和属性设置动画结束后再进行属性和布局的更新节省组件创建时间加快页面渲染。4.2 预创建的典型应用BuilderNode 预创建对于那些创建初始化耗时较长的声明式组件特别有效比如 Web、XComponent 等预创建可以有效减少组件初始化的耗时。小红书 APP 在冷启动优化中使用了这一方案在启动阶段先获取磁盘上的笔记内容然后在 BuilderNode 中预先创建多个 Image 组件。这样在等待网络或推荐接口响应时Image 组件已经创建完毕首页刷新时可以立即使用这些组件从而提升首刷速度。import{BuilderNode,FrameNode,NodeController,UIContext}fromkit.ArkUI;classPreCreateControllerextendsNodeController{privaterootNode:FrameNode|nullnull;makeNode(uiContext:UIContext):FrameNode|null{this.rootNodenewFrameNode(uiContext);constrootRenderNodethis.rootNode.getRenderNode();if(rootRenderNode!null){// 预创建 BuilderNodeconstbuilderNodenewBuilderNode(uiContext,{selfIdealSize:{width:200,height:100}});// 使用 WrappedBuilder 构建组件树builderNode.build(newWrappedBuilder(this.buildContent));// 将预创建的节点挂载到渲染节点constrenderNodebuilderNode.getFrameNode()?.getRenderNode();if(renderNode!null){rootRenderNode.appendChild(renderNode);}}returnthis.rootNode;}BuilderbuildContent(){Column({space:8}){Text(预创建的内容).fontSize(18)Button(操作按钮).fontSize(14)}.padding(12)}}五、全局自定义组件复用池5.1 为什么需要全局复用池在默认的组件复用模式下子组件被放置在父组件的缓存池中不同父组件中的相同子组件无法复用。将父组件改为 Builder 函数可以让子组件共享组件复用池但当父组件承载复杂业务逻辑和状态、而 Builder 是无状态的时候这种方案不可行。通过 BuilderNode 可以管理组件复用池实现跨页面的组件复用。5.2 实现路线使用NodeContainer作为自定义组件生成位置的占位符将NodeController存储在NodePool中按组件类型分类复用获取每次创建子组件时优先使用NodePool的getNode方法复用已有的NodeController组件。如果复现成功调用update方法更新组件数据。如果没有可复用的组件则调用makeNode方法创建新的子组件。回收归还销毁NodeController组件时将NodeItem回收到NodePool中供后续使用。页面退出时系统默认会销毁所有节点及其 NodeItem 实例应用侧可以主动保存 NodeItem 实例有效延长其生命周期避免不必要的重建开销。5.3 数据结构NodeItem继承NodeController并实现makeNode方法。NodePool使用 HashMap 管理NodeItem的复用与回收核心方法包括getNode获取可复用节点和recycleNode回收节点。// NodePool 核心结构示意classNodeItemextendsNodeController{privatebuilder:WrappedBuilder[ESObject];privatenode:BuilderNode[ESObject]|nullnull;privatedata:ESObject;privatetype:string;makeNode(uiContext:UIContext):FrameNode|null{this.nodenewBuilderNode(uiContext);this.node.build(this.builder);returnthis.node.getFrameNode();}update(data:ESObject):void{this.datadata;// 更新组件数据}aboutToDisappear():void{// 回收时清理}}classNodePool{privatestaticinstance:NodePool|nullnull;privatenodePool:Mapstring,NodeItem[]newMap();staticgetInstance():NodePool{if(!NodePool.instance){NodePool.instancenewNodePool();}returnNodePool.instance;}getNode(type:string,data:ESObject,builder:WrappedBuilder[ESObject]):NodeItem{constpoolthis.nodePool.get(type);if(poolpool.length0){constitempool.pop()!;item.update(data);returnitem;}returnnewNodeItem(builder,data,type);}recycleNode(type:string,node:NodeItem):void{if(!this.nodePool.has(type)){this.nodePool.set(type,[]);}this.nodePool.get(type)!.push(node);}}5.4 典型应用场景页面切换场景中首页的 Tabs 页面通过 List标题和 Swiper列表页实现。Swiper 中每个页面使用瀑布流加载视频列表各个瀑布流中的子组件可能具有相同布局。这些子组件期望跨页面复用但 ArkUI 提供的普通复用中复用池位于父组件内导致上一个页面瀑布流中的子组件无法跨页面复用。此时可以使用 BuilderNode 自定义全局组件复用池根据页面状态创建、回收和复用子组件。六、多元化习题习题 1判断题题目在 ArkUI 中同一个自定义节点可以同时作为多个父节点的子节点不会产生异常。答案错误解读使用自定义节点相关能力时需要保证同一时刻同一个节点有且仅有一个父节点。同一个节点作为不同节点的子节点存在时可能出现事件响应异常、内容显示异常、位置显示异常、稳定性问题等。习题 2单选题题目以下哪个自定义节点用于表示组件树的实体节点支持节点树操作与自定义布局A. RenderNodeB. BuilderNodeC. FrameNodeD. NodeContainer答案C解读FrameNode 表示组件树的实体节点支持节点树操作、自定义绘制与布局、位置查询、动画等能力。RenderNode 是自绘制渲染节点BuilderNode 用于创建系统组件树NodeContainer 是挂载自定义节点的占位容器。习题 3多选题题目关于 FrameNode 相比声明式自定义组件的性能优势以下说法正确的有多选A. FrameNode 可以避免创建自定义组件对象和状态变量对象无需进行依赖收集B. FrameNode 可以避免对复杂组件树的大量 diff 操作C. FrameNode 可以直接操作组件树将子树从当前子节点移植到另一个节点D. FrameNode 在预览器中也可以正常使用答案A、B、C解读使用 FrameNode 可以避免创建自定义组件对象和状态变量对象无需进行依赖收集选项 A 正确。使用 FrameNode 扩展框架能够自主掌控更新流程实现高效的按需剪枝避免大量 diff 操作选项 B 正确。使用 FrameNode 可以通过操作节点方便地操控子树将其移植到另一个节点只进行局部渲染刷新选项 C 正确。当前不支持在预览器中使用 FrameNode 节点选项 D 错误。习题 4代码填空题题目请补全以下代码获取 FrameNode 中持有的 RenderNode并将一个自定义 RenderNode 挂载到其上。classMyControllerextendsNodeController{privaterootNode:FrameNode|nullnull;makeNode(uiContext:UIContext):FrameNode|null{this.rootNodenewFrameNode(uiContext);// 在此处填写代码获取 RenderNodeconstrootRenderNodethis.rootNode.______________();if(rootRenderNode!null){constrenderNodenewRenderNode();renderNode.frame{x:0,y:0,width:100,height:100};renderNode.backgroundColor0xFFFF5500;rootRenderNode.______________(renderNode);}returnthis.rootNode;}}答案getRenderNode、appendChild解读通过getRenderNode()方法可以获取 FrameNode 中持有的 RenderNode。通过appendChild()方法在 RenderNode 的最后一个子节点后添加新的子节点。习题 5代码改错题题目以下代码存在节点树结构错误请指出问题并修正。constnodeAtypeNode.createNode(uiContext,Text);nodeA.initialize(A);constnodeBtypeNode.createNode(uiContext,Text);nodeB.initialize(B);constnodeCtypeNode.createNode(uiContext,Text);nodeC.initialize(C);// 节点树挂载nodeA.appendChild(nodeB);nodeA.appendChild(nodeC);nodeB.appendChild(nodeC);// 问题nodeC 同时作为 nodeA 和 nodeB 的子节点答案nodeC同时作为nodeA和nodeB的子节点违反了“节点唯一父节点”的约束会导致事件响应异常、内容显示异常等问题。修正方案是移除重复的挂载nodeA.appendChild(nodeB);nodeA.appendChild(nodeC);// 删除 nodeB.appendChild(nodeC);解读使用自定义节点相关能力时需要保证同一时刻同一个节点有且仅有一个父节点。同一个节点作为不同节点的子节点存在时可能出现事件响应异常、内容显示异常、位置显示异常、稳定性问题。习题 6简答题题目简述 FrameNode、RenderNode、BuilderNode 三类自定义节点的区别与协作关系。答案FrameNode 是组件树的实体节点支持节点树操作、自定义绘制与布局适用于动态创建和管理组件节点树。RenderNode 是更轻量级的渲染节点专注于自定义渲染支持背景色、变换、阴影、模糊等视觉属性设置。BuilderNode 赋予创建、更新系统组件及其组件树的能力使用Builder装饰的无状态 UI 方法生成组件树核心价值在于组件预创建和全局组件复用池管理。协作关系为NodeController 管理节点的创建与更新通过 makeNode 返回 FrameNode 或 BuilderNode 持有的 FrameNode再通过 NodeContainer 将节点挂载到组件树上显示。FrameNode 可以通过 getRenderNode 获取对应的 RenderNode 进行渲染层面的操作。解读三类节点分别对应“组件树操作”、“渲染控制”和“组件创建复用”三个层面。FrameNode 是最基础的实体节点RenderNode 在其基础上提供渲染能力BuilderNode 则封装了组件树的创建过程。习题 7简答题题目简述如何利用 BuilderNode 和 NodePool 实现全局自定义组件复用池以及其解决的核心问题。答案实现方式为使用 NodeContainer 作为占位符将 NodeController 存储在 NodePool 中按类型分类。每次创建子组件时优先使用 NodePool 的 getNode 方法复用已有组件复用成功则调用 update 更新数据无可用组件时调用 makeNode 创建。销毁时调用 recycleNode 将 NodeItem 归还到 NodePool。NodeItem 继承 NodeController 并实现 makeNode 方法NodePool 使用 HashMap 管理 NodeItem 的复用与回收。核心解决的问题是在默认的组件复用模式下子组件被放置在父组件的缓存池中不同父组件中的相同子组件无法复用。通过 BuilderNode 管理全局复用池可以实现跨页面的组件复用避免页面退出时节点被全部销毁导致的重复创建开销。解读全局复用池的关键在于将复用池的管理权从父组件提升到全局层面通过 NodePool 统一管理 NodeItem 的创建、更新、回收和复用从而突破父组件缓存池的局限。七、本节知识点总结自定义节点体系ArkUI 提供三类自定义节点FrameNode组件实体节点、RenderNode自绘制渲染节点、BuilderNode组件树创建与预创建节点。通过 NodeController 和 NodeContainer 完成节点的创建与挂载。FrameNode 节点树操作支持 appendChild、removeChild、getChild、getChildrenCount 等增删查改能力。完全自定义节点支持自定义测量、布局和绘制系统组件代理节点支持遍历组件树和访问组件信息。FrameNode 性能优势避免创建自定义组件对象和状态变量对象无需依赖收集框架自主掌控更新流程实现按需剪枝可直接操作组件树移植子树只进行局部渲染刷新。RenderNode 自定义渲染通过 FrameNode 的 getRenderNode() 获取。支持丰富的视觉属性设置背景色、变换、阴影、模糊等支持自定义绘制。不建议对 BuilderNode 中的 RenderNode 进行修改操作。BuilderNode 组件预创建在非 build 生命周期中提前创建组件适用于 Web、XComponent 等初始化耗时较长的组件。小红书 APP 利用预创建 Image 组件优化冷启动首刷速度。全局组件复用池通过 BuilderNode NodePool 实现跨页面组件复用突破父组件缓存池的局限。NodeItem 继承 NodeControllerNodePool 使用 HashMap 管理复用与回收。核心约束节点唯一父节点避免网状或环形结构不在预览器中使用FrameNode 节点暂不支持拖拽。下节预告第4课将进入 ArkUI 的 NDK 开发与 C 混合编程的学习涵盖 Node-API 的 C 接口封装、Native 侧组件节点的创建与操作、以及 ArkTS 与 C 的高效数据交互方案。