1# RenderNode 2 3## 概述 4 5对于不具备自己的渲染环境的三方框架,虽然实现了前端的解析以及布局、事件等处理,但需要依赖系统提供的基础渲染、动画的能力。[FrameNode](./arkts-user-defined-arktsNode-frameNode.md)上的通用属性、通用事件对这一类框架是多余的,会进行多次冗余的操作,包括布局、事件等处理逻辑。 6 7[RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md)是更加轻量级的渲染节点,仅包含渲染相关的能力。在该节点上暴露了设置基础的渲染属性的能力,并提供节点的动态增加、删除能力以及自定义绘制的能力。可以向三方框架提供基础的渲染、动画能力。 8 9## 创建和删除节点 10 11RenderNode提供了节点创建和删除的能力。可以通过RenderNode的构造函数创建自定义的RenderNode节点。通过构造函数创建的节点对应一个实体的节点。同时,可以通过RenderNode中的dispose接口来实现与实体节点的绑定关系的解除。 12 13## 操作节点树 14 15RenderNode提供了节点的增、删、查、改的能力,能够修改节点的子树结构;可以对所有RenderNode的节点的父子节点做出查询操作,并返回查询结果。 16 17> **说明:** 18> 19> - RenderNode中查询获取得到的子树结构按照开发通过RenderNode的接口传递的参数构建。 20> 21> - RenderNode如果要与原生组件结合显示,使用需要依赖FrameNode中获取的RenderNode进行挂载上树。 22 23## 设置和获取渲染相关属性 24 25RenderNode中可以设置渲染相关的属性,包括:backgroundColor,clipToFrame,opacity,size,position,frame,pivot,scale,translation,rotation,transform,shadowColor,shadowOffset,shadowAlpha,shadowElevation,shadowRadius,borderStyle,borderWidth,borderColor,borderRadius,shapeMask,shapeClip,markNodeGroup等。具体属性支持范围参考[RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md#rendernode)接口说明。 26 27> **说明:** 28> 29> - RenderNode中查询获取得到的属性为设置的属性值。 30> 31> - 若未传入参数或者传入参数为非法值则查询获得的为默认值。 32> 33> - 不建议对BuilderNode中的RenderNode进行修改操作。 34 35```ts 36import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI'; 37 38const renderNode = new RenderNode(); 39renderNode.frame = { x: 0, y: 0, width: 200, height: 350 }; 40renderNode.backgroundColor = 0xffff0000; 41for (let i = 0; i < 5; i++) { 42 const node = new RenderNode(); 43 // 设置node节点的Frame大小 44 node.frame = { x: 10, y: 10 + 60 * i, width: 50, height: 50 }; 45 // 设置node节点的背景颜色 46 node.backgroundColor = 0xff00ff00; 47 // 将新增节点挂载在renderNode上 48 renderNode.appendChild(node); 49} 50 51class MyNodeController extends NodeController { 52 private rootNode: FrameNode | null = null; 53 54 makeNode(uiContext: UIContext): FrameNode | null { 55 this.rootNode = new FrameNode(uiContext); 56 57 const rootRenderNode = this.rootNode?.getRenderNode(); 58 if (rootRenderNode) { 59 rootRenderNode.appendChild(renderNode); 60 } 61 return this.rootNode; 62 } 63} 64 65@Entry 66@Component 67struct Index { 68 private myNodeController: MyNodeController = new MyNodeController(); 69 70 build() { 71 Row() { 72 NodeContainer(this.myNodeController) 73 .width(200) 74 .height(350) 75 Button('getNextSibling') 76 .onClick(() => { 77 const child = renderNode.getChild(1); 78 const nextSibling = child!.getNextSibling() 79 if (child === null || nextSibling === null) { 80 console.log('the child or nextChild is null'); 81 } else { 82 // 获取子节点的位置信息 83 console.log(`the position of child is x: ${child.position.x}, y: ${child.position.y}, ` + 84 `the position of nextSibling is x: ${nextSibling.position.x}, y: ${nextSibling.position.y}`); 85 } 86 }) 87 } 88 } 89} 90``` 91 92## 自定义绘制 93 94通过重写RenderNode中的[draw](../reference/apis-arkui/js-apis-arkui-renderNode.md#draw)方法,可以自定义RenderNode的绘制内容,通过[invalidate](../reference/apis-arkui/js-apis-arkui-renderNode.md#invalidate)接口可以主动触发节点的重新绘制。 95 96> **说明:** 97> 98> - 同时同步触发多个invalidate仅会触发一次重新绘制。 99> 100> - 自定义绘制有两种绘制方式:通过ArkTS接口进行调用和通过Node-API进行调用。 101 102**ArkTS接口调用示例:** 103 104```ts 105import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI'; 106import { drawing } from '@kit.ArkGraphics2D'; 107 108class MyRenderNode extends RenderNode { 109 draw(context: DrawContext) { 110 // 获取canvas对象 111 const canvas = context.canvas; 112 // 创建笔刷 113 const brush = new drawing.Brush(); 114 // 设置笔刷颜色 115 brush.setColor({ alpha: 255, red: 255, green: 0, blue: 0 }); 116 canvas.attachBrush(brush); 117 // 绘制矩阵 118 canvas.drawRect({ left: 0, right: 200, top: 0, bottom: 200 }); 119 canvas.detachBrush(); 120 } 121} 122 123const renderNode = new MyRenderNode(); 124renderNode.frame = { x: 0, y: 0, width: 300, height: 300 }; 125renderNode.backgroundColor = 0xff0000ff; 126renderNode.opacity = 0.5; 127 128class MyNodeController extends NodeController { 129 private rootNode: FrameNode | null = null; 130 131 makeNode(uiContext: UIContext): FrameNode | null { 132 this.rootNode = new FrameNode(uiContext); 133 134 const rootRenderNode = this.rootNode?.getRenderNode(); 135 if (rootRenderNode !== null) { 136 rootRenderNode.frame = { x: 0, y: 0, width: 500, height: 500 } 137 rootRenderNode.appendChild(renderNode); 138 } 139 140 return this.rootNode; 141 } 142} 143 144@Entry 145@Component 146struct Index { 147 private myNodeController: MyNodeController = new MyNodeController(); 148 149 build() { 150 Column() { 151 NodeContainer(this.myNodeController) 152 .width('100%') 153 Button('Invalidate') 154 .onClick(() => { 155 // 同步调用多次,仅触发一次重绘 156 renderNode.invalidate(); 157 renderNode.invalidate(); 158 }) 159 } 160 } 161} 162``` 163 164**Node-API调用示例:** 165 166C++侧可通过Node-API来获取Canvas,并进行后续的自定义绘制操作。 167 168```c++ 169// native_bridge.cpp 170#include "napi/native_api.h" 171#include <native_drawing/drawing_canvas.h> 172#include <native_drawing/drawing_color.h> 173#include <native_drawing/drawing_path.h> 174#include <native_drawing/drawing_pen.h> 175 176static napi_value OnDraw(napi_env env, napi_callback_info info) 177{ 178 size_t argc = 4; 179 napi_value args[4] = { nullptr }; 180 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 181 182 int32_t id; 183 napi_get_value_int32(env, args[0], &id); 184 185 // 获取 Canvas 指针 186 void* temp = nullptr; 187 napi_unwrap(env, args[1], &temp); 188 OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas*>(temp); 189 190 // 获取 Canvas 宽度 191 int32_t width; 192 napi_get_value_int32(env, args[2], &width); 193 194 // 获取 Canvas 高度 195 int32_t height; 196 napi_get_value_int32(env, args[3], &height); 197 198 // 传入canvas、height、width等信息至绘制函数中进行自定义绘制 199 auto path = OH_Drawing_PathCreate(); 200 OH_Drawing_PathMoveTo(path, width / 4, height / 4); 201 OH_Drawing_PathLineTo(path, width * 3 / 4, height / 4); 202 OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4); 203 OH_Drawing_PathLineTo(path, width / 4, height * 3 / 4); 204 OH_Drawing_PathLineTo(path, width / 4, height / 4); 205 OH_Drawing_PathClose(path); 206 207 auto pen = OH_Drawing_PenCreate(); 208 OH_Drawing_PenSetWidth(pen, 10); 209 OH_Drawing_PenSetColor(pen, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0x00, 0x00)); 210 OH_Drawing_CanvasAttachPen(canvas, pen); 211 212 OH_Drawing_CanvasDrawPath(canvas, path); 213 214 return nullptr; 215} 216 217EXTERN_C_START 218static napi_value Init(napi_env env, napi_value exports) 219{ 220 napi_property_descriptor desc[] = { 221 { "nativeOnDraw", nullptr, OnDraw, nullptr, nullptr, nullptr, napi_default, nullptr } 222 }; 223 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 224 return exports; 225} 226EXTERN_C_END 227 228static napi_module demoModule = { 229 .nm_version =1, 230 .nm_flags = 0, 231 .nm_filename = nullptr, 232 .nm_register_func = Init, 233 .nm_modname = "entry", 234 .nm_priv = ((void*)0), 235 .reserved = { 0 }, 236}; 237 238extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 239{ 240 napi_module_register(&demoModule); 241} 242``` 243 244修改工程中的`src/main/cpp/CMakeLists.txt`文件,添加如下内容: 245```cmake 246# the minimum version of CMake. 247cmake_minimum_required(VERSION 3.4.1) 248project(NapiTest) 249 250set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 251 252include_directories(${NATIVERENDER_ROOT_PATH} 253 ${NATIVERENDER_ROOT_PATH}/include) 254 255add_library(entry SHARED native_bridge.cpp) 256target_link_libraries(entry PUBLIC libace_napi.z.so) 257target_link_libraries(entry PUBLIC libace_ndk.z.so) 258target_link_libraries(entry PUBLIC libnative_drawing.so) 259``` 260 261同时在工程中的`src/main/cpp/types/libentry/index.d.ts`文件中,添加自定义绘制函数在ArkTS侧的定义,如: 262```ts 263import { DrawContext } from '@kit.ArkUI' 264 265export const nativeOnDraw: (id: number, context: DrawContext, width: number, height: number) => number; 266``` 267 268ArkTS侧代码: 269 270```ts 271// Index.ets 272import bridge from "libentry.so" // 该 so 由 Node-API 编写并生成 273import { DrawContext, FrameNode, NodeController, RenderNode } from '@kit.ArkUI' 274 275class MyRenderNode extends RenderNode { 276 draw(context: DrawContext) { 277 // 需要将 context 中的宽度和高度从vp转换为px 278 bridge.nativeOnDraw(0, context, vp2px(context.size.height), vp2px(context.size.width)); 279 } 280} 281 282class MyNodeController extends NodeController { 283 private rootNode: FrameNode | null = null; 284 285 makeNode(uiContext: UIContext): FrameNode | null { 286 this.rootNode = new FrameNode(uiContext); 287 288 const rootRenderNode = this.rootNode.getRenderNode(); 289 if (rootRenderNode !== null) { 290 const renderNode = new MyRenderNode(); 291 renderNode.size = { width: 100, height: 100 } 292 rootRenderNode.appendChild(renderNode); 293 } 294 return this.rootNode; 295 } 296} 297 298@Entry 299@Component 300struct Index { 301 private myNodeController: MyNodeController = new MyNodeController(); 302 303 build() { 304 Row() { 305 NodeContainer(this.myNodeController) 306 } 307 } 308} 309``` 310 311## 设置标签 312 313开发者可以通过[label](../reference/apis-arkui/js-apis-arkui-renderNode.md#label12)接口向FrameNode中设置标签信息,便于在节点Inspector信息中较为方便对节点进行区分。 314 315```ts 316import { RenderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 317 318class MyNodeController extends NodeController { 319 private rootNode: FrameNode | null = null; 320 321 makeNode(uiContext: UIContext): FrameNode | null { 322 this.rootNode = new FrameNode(uiContext); 323 const renderNode: RenderNode | null = this.rootNode.getRenderNode(); 324 if (renderNode !== null) { 325 const renderChildNode: RenderNode = new RenderNode(); 326 renderChildNode.frame = { x: 0, y: 0, width: 100, height: 100 }; 327 renderChildNode.backgroundColor = 0xffff0000; 328 renderChildNode.label = 'customRenderChildNode'; 329 console.log('label:', renderChildNode.label); 330 renderNode.appendChild(renderChildNode); 331 } 332 333 return this.rootNode; 334 } 335} 336 337@Entry 338@Component 339struct Index { 340 private myNodeController: MyNodeController = new MyNodeController(); 341 342 build() { 343 Column() { 344 NodeContainer(this.myNodeController) 345 .width(300) 346 .height(700) 347 .backgroundColor(Color.Gray) 348 } 349 } 350} 351``` 352