在这里插入图片描述

1 -> 概述

随着鸿蒙生态的蓬勃发展,应用对性能和灵活性的要求日益提高。在API version 20(鸿蒙6.0)中,ArkUI框架为NDK开发者带来了一项革命性的能力:直接构建和操作渲染节点的C API。这一更新彻底改变了在C++层进行UI自定义的方式,为游戏引擎、高性能图形应用、复杂自定义控件等场景打开了新的大门。

1.1 -> 核心价值:绕过测量布局,直达渲染核心

在传统的UI开发中,自定义绘制通常需要经历复杂的测量、布局过程。而全新的渲染节点C API允许开发者:

  1. 直接控制绘制:通过OH_ArkUI_RenderNodeUtils_SetContentModifierOnDraw等接口,直接在Canvas上进行绘制。
  2. 精细操作节点树:使用OH_ArkUI_RenderNodeUtils_AddRenderNodeOH_ArkUI_RenderNodeUtils_AddChild等API,自由构建渲染节点树,不受原有布局约束。
  3. 高效属性动画:将属性与ContentModifier绑定,实现仅更新绘制内容的属性动画,性能更优。

1.2 -> 关键概念与约束

渲染节点并非独立存在,它需要以子树的形式挂载在特定类型的ArkUI节点上:

  • 宿主节点:必须是类型为ARKUI_NODE_CUSTOM的自定义节点。
  • 约束条件:该CUSTOM节点不能有其他子节点(即为叶子节点),且最多挂载一个渲染节点作为其根。

2 -> 渲染节点的创建、挂载与基础属性设置

这部分将带你完成渲染节点从创建到显示的全过程,包括构建节点树和设置基础样式。

2.1 -> 环境准备与前置工程

在开始之前,你需要已完成一个基础的ArkTS页面创建流程,并能够获取到ArkUI_NativeNodeAPI_1实例。文档中的CreateNativeRoot函数展示了如何从NAPI接口获取节点API,这是我们操作的基础。

2.2 -> 逐步构建:从CUSTOM节点到渲染子树

以下示例展示了如何创建一个拥有三个子节点的渲染子树,并为其设置尺寸、位置、背景色、旋转和边框。

// NativeEntry.cpp 片段 - 创建并挂载渲染节点树
#include <arkui/native_node.h>
#include <arkui/native_render.h>

ArkUI_NodeHandle CreateRenderNodeSubtree(ArkUI_NativeNodeAPI_1 *nodeAPI) {
    // 1. 创建一个CUSTOM节点作为宿主
    ArkUI_NodeHandle customHost = nodeAPI->createNode(ARKUI_NODE_CUSTOM);
    ArkUI_NumberValue widthValue[] = {400};
    ArkUI_AttributeItem widthItem = {widthValue, 1};
    nodeAPI->setAttribute(customHost, NODE_WIDTH, &widthItem);
    nodeAPI->setAttribute(customHost, NODE_HEIGHT, &widthItem); // 设为400x400

    // 2. 创建渲染节点(根节点和三个子节点)
    auto rootRenderNode = OH_ArkUI_RenderNodeUtils_CreateNode();
    auto child1 = OH_ArkUI_RenderNodeUtils_CreateNode();
    auto child2 = OH_ArkUI_RenderNodeUtils_CreateNode();
    auto child3 = OH_ArkUI_RenderNodeUtils_CreateNode();

    // 3. 将渲染根节点挂载到CUSTOM节点上(关键步骤)
    int32_t result = OH_ArkUI_RenderNodeUtils_AddRenderNode(customHost, rootRenderNode);
    if (result != ARKUI_ERROR_CODE_NO_ERROR) {
        // 处理挂载失败
        return nullptr;
    }

    // 4. 构建渲染节点树
    OH_ArkUI_RenderNodeUtils_AddChild(rootRenderNode, child1);
    OH_ArkUI_RenderNodeUtils_AddChild(rootRenderNode, child2);
    OH_ArkUI_RenderNodeUtils_AddChild(rootRenderNode, child3);

    // 5. 设置节点基础属性:尺寸和位置
    OH_ArkUI_RenderNodeUtils_SetSize(rootRenderNode, 360.0f, 360.0f); // 根节点稍小,留出内边距
    OH_ArkUI_RenderNodeUtils_SetSize(child1, 100.0f, 100.0f);
    OH_ArkUI_RenderNodeUtils_SetSize(child2, 100.0f, 100.0f);
    OH_ArkUI_RenderNodeUtils_SetSize(child3, 100.0f, 100.0f);

    OH_ArkUI_RenderNodeUtils_SetPosition(rootRenderNode, 20.0f, 20.0f); // 相对于CUSTOM节点的偏移
    OH_ArkUI_RenderNodeUtils_SetPosition(child1, 0.0f, 0.0f);
    OH_ArkUI_RenderNodeUtils_SetPosition(child2, 130.0f, 130.0f);
    OH_ArkUI_RenderNodeUtils_SetPosition(child3, 260.0f, 260.0f);

    // 6. 设置背景颜色以便观察
    OH_ArkUI_RenderNodeUtils_SetBackgroundColor(rootRenderNode, 0xFFE0E0E0); // 浅灰色背景
    OH_ArkUI_RenderNodeUtils_SetBackgroundColor(child1, 0xFFFF0000); // 红
    OH_ArkUI_RenderNodeUtils_SetBackgroundColor(child2, 0xFF00FF00); // 绿
    OH_ArkUI_RenderNodeUtils_SetBackgroundColor(child3, 0xFF0000FF); // 蓝

    // 7. 应用变换和边框属性
    // 将第二个子节点旋转45度
    OH_ArkUI_RenderNodeUtils_SetRotation(child2, 0.0f, 0.0f, 45.0f); // 绕Z轴旋转45度

    // 为第一个子节点设置黑色实线边框,宽度5px
    auto borderStyle = OH_ArkUI_RenderNodeUtils_CreateNodeBorderStyleOption();
    OH_ArkUI_RenderNodeUtils_SetNodeBorderStyleOptionEdgeStyle(borderStyle, 
        ARKUI_BORDER_STYLE_SOLID, ARKUI_EDGE_DIRECTION_ALL);
    OH_ArkUI_RenderNodeUtils_SetBorderStyle(child1, borderStyle);
    OH_ArkUI_RenderNodeUtils_DisposeNodeBorderStyleOption(borderStyle);

    auto borderWidth = OH_ArkUI_RenderNodeUtils_CreateNodeBorderWidthOption();
    OH_ArkUI_RenderNodeUtils_SetNodeBorderWidthOptionEdgeWidth(borderWidth, 5.0f, 
        ARKUI_EDGE_DIRECTION_ALL);
    OH_ArkUI_RenderNodeUtils_SetBorderWidth(child1, borderWidth);
    OH_ArkUI_RenderNodeUtils_DisposeNodeBorderWidthOption(borderWidth);

    auto borderColor = OH_ArkUI_RenderNodeUtils_CreateNodeBorderColorOption();
    OH_ArkUI_RenderNodeUtils_SetNodeBorderColorOptionEdgeColor(borderColor, 0xFF000000, 
        ARKUI_EDGE_DIRECTION_ALL); // 黑色边框
    OH_ArkUI_RenderNodeUtils_SetBorderColor(child1, borderColor);
    OH_ArkUI_RenderNodeUtils_DisposeNodeBorderColorOption(borderColor);

    // ... 将customHost添加到你的UI树中(例如Scroll、Column等)
    return customHost;
}

要点解析

  • OH_ArkUI_RenderNodeUtils_AddRenderNode:这是将渲染子树挂载到UI树的关键接口,参数Custom节点和renderRootNode建立了联系。
  • 坐标系统:渲染节点的SetPosition是相对于其父渲染节点的偏移。最顶层的渲染根节点是相对于其宿主的CUSTOM节点内容区域。
  • 资源管理:所有通过Create方法创建的选项结构体(如borderStyle)都必须使用对应的Dispose方法释放,避免内存泄漏。

3 -> 深入自定义绘制与动画集成

渲染节点的真正威力在于其灵活的自定义绘制能力,并能将属性与动画无缝集成。这一部分将展示如何绘制一个动态变化的矩形,并使其颜色和大小随动画改变。

3.1 -> 核心概念:ContentModifier与AnimatableProperty

  • ContentModifier:通过OH_ArkUI_RenderNodeUtils_CreateContentModifier创建,它像一个“绘制插件”挂载到渲染节点上,负责定义具体的绘制内容。
  • AnimatableProperty:可动画的属性,如浮点型、颜色、二维向量等。它们与ContentModifier关联,其值的变化会自动触发ContentModifierOnDraw回调。

3.2 -> 实战:创建一个带动画的自定义绘制节点

// NativeEntry.cpp 片段 - 自定义绘制与动画
#include <arkui/native_animate.h>
#include <native_drawing/drawing_canvas.h>
#include <native_drawing/drawing_path.h>
#include <native_drawing/drawing_pen.h>

// 自定义数据结构,用于在回调中传递动画属性句柄
struct AnimatableData {
    ArkUI_FloatAnimatablePropertyHandle sizeProp;   // 控制绘制大小
    ArkUI_ColorAnimatablePropertyHandle colorProp;  // 控制绘制颜色
};

ArkUI_NodeHandle CreateAnimatedRenderNode(ArkUI_NativeNodeAPI_1 *nodeAPI, ArkUI_ContextHandle context) {
    // 1. 创建宿主CUSTOM节点
    ArkUI_NodeHandle customHost = nodeAPI->createNode(ARKUI_NODE_CUSTOM);
    ArkUI_NumberValue sizeVal[] = {400};
    ArkUI_AttributeItem sizeItem = {sizeVal, 1};
    nodeAPI->setAttribute(customHost, NODE_WIDTH, &sizeItem);
    nodeAPI->setAttribute(customHost, NODE_HEIGHT, &sizeItem);

    // 2. 创建渲染节点并挂载
    auto renderNode = OH_ArkUI_RenderNodeUtils_CreateNode();
    OH_ArkUI_RenderNodeUtils_AddRenderNode(customHost, renderNode);
    OH_ArkUI_RenderNodeUtils_SetSize(renderNode, 400.0f, 400.0f); // 铺满CUSTOM节点

    // 3. 创建可动画属性
    auto sizeProperty = OH_ArkUI_RenderNodeUtils_CreateFloatAnimatableProperty(50.0f);  // 初始大小50
    auto colorProperty = OH_ArkUI_RenderNodeUtils_CreateColorAnimatableProperty(0xFFFF0000); // 初始红色

    // 4. 创建ContentModifier并关联属性
    auto modifier = OH_ArkUI_RenderNodeUtils_CreateContentModifier();
    OH_ArkUI_RenderNodeUtils_AttachContentModifier(renderNode, modifier);
    OH_ArkUI_RenderNodeUtils_AttachFloatAnimatableProperty(modifier, sizeProperty);
    OH_ArkUI_RenderNodeUtils_AttachColorAnimatableProperty(modifier, colorProperty);

    // 5. 设置自定义绘制内容 (OnDraw回调)
    auto *userData = new AnimatableData{sizeProperty, colorProperty}; // 注意生命周期管理
    OH_ArkUI_RenderNodeUtils_SetContentModifierOnDraw(
        modifier, 
        userData,
        [](ArkUI_DrawContext* context, void* data) {
            auto* animData = static_cast<AnimatableData*>(data);
            
            // 从可动画属性中获取当前值
            float currentSize;
            OH_ArkUI_RenderNodeUtils_GetFloatAnimatablePropertyValue(animData->sizeProp, &currentSize);
            
            uint32_t currentColor;
            OH_ArkUI_RenderNodeUtils_GetColorAnimatablePropertyValue(animData->colorProp, &currentColor);

            // 获取Canvas并进行绘制
            OH_Drawing_Canvas* canvas = reinterpret_cast<OH_Drawing_Canvas*>(
                OH_ArkUI_DrawContext_GetCanvas(context));
            
            // 计算绘制位置(居中)
            float left = (400.0f - currentSize) / 2;
            float top = (400.0f - currentSize) / 2;
            float right = left + currentSize;
            float bottom = top + currentSize;

            // 创建画笔并设置颜色
            auto pen = OH_Drawing_PenCreate();
            OH_Drawing_PenSetColor(pen, currentColor);
            OH_Drawing_PenSetWidth(pen, 4.0f);
            OH_Drawing_CanvasAttachPen(canvas, pen);

            // 绘制一个矩形
            OH_Drawing_CanvasDrawRect(canvas, left, top, right, bottom);
            
            // 记得释放临时对象
            OH_Drawing_PenDestroy(pen);
        });

    // 6. 启动动画:改变属性值
    // 创建一个动画回调,用于更新属性值(实际应用中应结合动画曲线和持续时间)
    ArkUI_ContextCallback* animationCallback = new ArkUI_ContextCallback;
    animationCallback->userData = userData;
    animationCallback->callback = [](void* user) {
        auto* data = static_cast<AnimatableData*>(user);
        
        // 示例:简单循环改变大小和颜色
        static float size = 50.0f;
        static float step = 2.0f;
        size += step;
        if (size > 300.0f || size < 50.0f) {
            step = -step;
        }
        
        // 更新属性值,框架会自动触发OnDraw重绘
        OH_ArkUI_RenderNodeUtils_SetFloatAnimatablePropertyValue(data->sizeProp, size);
        
        // 颜色在红蓝之间渐变
        uint32_t color = (size > 175.0f) ? 0xFF0000FF : 0xFFFF0000;
        OH_ArkUI_RenderNodeUtils_SetColorAnimatablePropertyValue(data->colorProp, color);
    };

    // 需要使用OH_ArkUI_CreateContextCallback或者平台相关方式将animationCallback
    // 注册到一个定时器或动画驱动中。此处省略了注册部分,仅展示核心逻辑。

    return customHost;
}

要点解析

  • 属性驱动绘制OnDraw回调中不直接持有状态,而是通过Get...PropertyValue从绑定的可动画属性中获取当前值。这实现了绘制与数据的分离。
  • 动画触发:当你通过OH_ArkUI_RenderNodeUtils_Set...PropertyValue修改属性值时,框架会自动调度一次重绘,并调用OnDraw。你可以通过ContextCallback配合动画曲线在每一帧更新属性值,从而实现流畅动画。
  • 数据类型对应:注意绘制上下文ArkUI_DrawContext获取的Canvas指针需要转换为OH_Drawing_Canvas才能使用标准的Drawing API。

4 -> 总结与展望

鸿蒙6.0引入的渲染节点C API,标志着ArkUI框架在底层能力开放上迈出了关键一步。它不仅填补了NDK层精细控制UI绘制的空白,更通过“渲染节点树”和“可动画属性-修饰器”的设计模式,为高性能、高动态的UI实现提供了优雅的解决方案。

4.1 -> 主要优势回顾

  1. 性能:绕过ArkUI的声明式渲染管线中的测量布局阶段,直接操作渲染层,非常适合对绘制时机和方式有极致要求的场景。
  2. 灵活性:渲染节点树独立于原有的组件树,开发者可以自由组合、变换节点,实现复杂的图形结构。
  3. 融合能力:无缝集成鸿蒙的动画系统和Drawing库,既能享受底层绘制的强大,又能使用上层便捷的动画API。

4.2 -> 适用场景

  • 游戏开发:将游戏循环中的渲染直接对接渲染节点,减少UI框架带来的额外开销。
  • 自定义图形引擎:实现流程图、拓扑图、CAD预览等专业图形应用。
  • 复杂动画效果:对粒子系统、路径动画等需要逐帧精细控制的内容,通过修改可动画属性驱动绘制,效率极高。

未来,随着鸿蒙系统的不断迭代,我们可以期待这一能力与更多底层硬件能力(如GPU直接访问、色彩管理)结合,为我们开发者打开更大的创意空间。现在,正是探索和应用这项强大技术的最佳时机。

总之,鸿蒙6.0的渲染节点C API为NDK开发者提供了前所未有的UI控制力,它将绘制效率与灵活性提升到了新高度。


感谢各位大佬支持!!!

互三啦!!!
Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐