ArkUI 底层自定义与 NDK:FrameNode、RenderNode 与 C++ 构建 UI

什么时候需要底层自定义

前面讲的 @Component@BuilderModifier 都是声明式层面的复用。但有些场景声明式够不着:

  • 需要精确控制单个组件节点的增删改查
  • 需要和底层渲染管线交互,做特殊绘制
  • 已有 C/C++ 图形库,想在鸿蒙里直接用
  • 对性能有极高要求,需要绕开声明式框架的开销

这些场景要往下走:自定义节点(FrameNode/RenderNode/BuilderNode)、自定义渲染,以及 NDK 构建 UI。

自定义节点三兄弟

ArkUI 提供了三个节点 API,从不同角度操作底层:

API定位用途
FrameNode声明式组件的底层节点在声明式外获取、操作组件节点,实现动态增删
RenderNode渲染节点自定义绘制,绕过组件直接操作渲染属性
BuilderNode构建节点把 Builder 内容挂到任意位置,动态插入 UI

FrameNode:动态操作组件节点

FrameNode 让你在声明式结构之外,获取并操控组件节点:

import { FrameNode, NodeController } from '@kit.ArkUI';

// 一个控制节点增删的控制器
class MyNodeController extends NodeController {
  makeNode(uiContext: UIContext): FrameNode | null {
    // 创建 FrameNode,挂载一个 Text 组件
    let node = new FrameNode(uiContext);
    let textNode = new FrameNode(uiContext);
    textNode.initialize('Text');  // 初始化为 Text 组件
    // 设置组件属性
    textNode.attribute.text('动态创建的文本');
    node.appendChild(textNode);
    return node;
  }
}

@Entry
@Component
struct FrameNodeDemo {
  private controller: MyNodeController = new MyNodeController();

  build() {
    // 用 NodeContainer 承载动态节点
    NodeContainer(this.controller)
      .width(200)
      .height(60)
  }
}

FrameNode 的核心价值是命令式地创建和操作组件,在声明式框架之外做更精细的控制。典型场景:动态创建组件树、精确控制某个节点的层级和属性。

RenderNode:自定义绘制

RenderNode 绕开组件,直接操作渲染属性,做自定义绘制:

import { RenderNode } from '@kit.ArkUI';

@Entry
@Component
struct RenderNodeDemo {
  private rootNode: RenderNode = new RenderNode();

  aboutToAppear() {
    let renderNode = this.rootNode;
    renderNode.frame = { x: 0, y: 0, width: 200, height: 200 };
    renderNode.backgroundColor = 0xFFFF6B35;
    // 渲染命令:绘制一个圆形
    renderNode.drawCircle({
      x: 100, y: 100, radius: 80, fill: 0xFF4A90E2
    });
  }

  build() {
    // RenderNode 挂载到组件上显示
    Stack() {}
      .width('100%')
      .height('100%')
      .onAreaChange(() => {})
  }
}

RenderNode 直接写渲染属性(frame、backgroundColor)和绘制命令(drawCircle 等),绕过了组件层。适合需要自定义绘制、性能敏感的场景,但门槛也更高——你需要理解渲染管线。

BuilderNode:动态插入 UI

BuilderNode 把一段 Builder 构建的 UI 动态挂到任意位置:

import { BuilderNode } from '@kit.ArkUI';

@Entry
@Component
struct BuilderNodeDemo {
  build() {
    Column() {
      Text('BuilderNode 会在下方插入内容')
    }
  }
}

// 动态创建 BuilderNode 并插入某种 UI
function insertDynamicContent(uiContext: UIContext) {
  let builderNode = new BuilderNode(uiContext);
  builderNode.build((ui: UIContext) => {
    // 构建要插入的 UI
    Row() {
      Text('动态插入的内容')
    }
  });
}

BuilderNode 结合了 Builder 的声明式写法和节点的动态挂载能力,比 FrameNode 更贴近声明式习惯。

XComponent:画布与自定义渲染

XComponent 提供自定义渲染的画布,可以对接 C/C++ 或 OpenGL:

@Entry
@Component
struct XComponentDemo {
  build() {
    XComponent({ id: 'canvas', type: XComponentType.SURFACE })
      .onLoad((xcomponent) => {
        // 拿到 XComponent 的 surface,对接渲染
        let surfaceId = xcomponent.surfaceId;
        // 后续通过 native 接口在 surfaceId 上绘制
      })
      .width('100%')
      .height(300)
  }
}

XComponent 是自定义渲染的入口,拿到 surface 后可以用 native 代码(C/C++)绘制,适合游戏、图形应用、视频渲染等自绘场景。

NDK 构建 UI

NDK 构建 UI 是更彻底的底层方案——用 C/C++ 代码直接创建组件、操作 UI 树:

#include "native_capi.h"

// C++ 侧创建并挂载 UI 组件
void BuildNativeUI(OH_NativeXComponent* component) {
    // 创建 ArkUI 节点树
    ArkUI_NodeHandle root = OH_ArkUI_Node_Create(...);
    
    // 创建文本组件节点
    ArkUI_NodeHandle textNode = OH_ArkUI_Node_Create(...);
    OH_ArkUI_Node_SetAttribute(textNode, ARKUI_NODE_ATTRIBUTE_CONTENT, "Native 文本");
    OH_ArkUI_Node_SetAttribute(textNode, ARKUI_NODE_ATTRIBUTE_FONT_SIZE, 24);
    
    // 挂载到根节点
    OH_ArkUI_Node_AppendChild(root, textNode);
    
    // 注册到组件
    OH_ArkUI_Node_RegisterToComponent(component, root);
}

NDK 构建 UI 的适用场景:

  • 业务已有 C/C++ 代码库,想复用
  • 需要精细化控制组件创建和挂载
  • 对性能有极致要求

设备上的能力差异

底层自定义能力在模拟器和真机上有差异,涉及这些的能力模拟器可能不支持:

  • Image 的 enableAnalyzer(图像 AI 分析)
  • @Preview 装饰器
  • EmbeddedComponent 嵌入组件
  • toolbar 属性
  • restoreId 属性

做底层自定义时,要在真机上验证,别在模拟器上白调试。

底层自定义的选型小建议哦

需求                                → 选型
────────────────────────────────────
复用结构 + 状态                    → @Component
复用布局片段                       → @Builder
复用样式属性                       → Modifier
动态创建/操作组件节点              → FrameNode
自定义绘制、绕开组件               → RenderNode
动态插入声明式 UI                  → BuilderNode
自绘图形、游戏、视频               → XComponent
已有 C/C++ 库、极致性能            → NDK 构建 UI

@Component 到 NDK,抽象层级越来越低、控制力越来越强、门槛也越来越高。大部分业务停在 @Component + Modifier 就够了。

总结一波

  1. 别为了"高级"而底层。底层自定义门槛高、调试难、维护成本大,只有声明式确实解决不了时才往下走。能声明式解决的别碰 FrameNode/RenderNode。

  2. FrameNode 用于"声明式不够灵活"的场景。需要运行时动态增删组件、精确控制层级时,FrameNode 比声明式更直接。

  3. RenderNode 用于自绘和性能敏感场景。理解渲染管线再动 RenderNode,否则容易画出问题。

  4. NDK 用于复用 C/C++ 库。如果单纯为了性能,先优化声明式代码,NDK 是最后的手段。

  5. 底层能力真机验证。涉及底层节点、渲染、嵌入组件的功能,模拟器可能不支持,务必真机测试。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐