1# Custom Render Node (RenderNode) 2 3## Overview 4 5For third-party frameworks that lack an inherent rendering environment, leveraging the system's basic rendering and animation capabilities is crucial. Such frameworks often have their own mechanisms for front-end parsing, layout management, and event handling. As a result, the universal attributes and events associated with [FrameNode](./arkts-user-defined-arktsNode-frameNode.md) may be redundant for these frameworks, potentially causing duplicate work in managing layout and event logic. 6 7This is where [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md) becomes beneficial. **RenderNode** is a streamlined render node designed to offer rendering-specific features. It allows for setting of basic rendering properties and provides the capability to dynamically add and remove nodes and to implement custom drawing. These can be used to provide third-party frameworks with essential rendering and animation capabilities. 8 9## Creating and Removing Nodes 10 11You can create and remove nodes with **RenderNode**. You can create a custom instance of **RenderNode** using its constructor, and the instance thereby created corresponds to an entity node. You can use the [dispose](../reference/apis-arkui/js-apis-arkui-renderNode.md#dispose12) API in **RenderNode** to break the binding with the entity node. 12 13## Operating the Node Tree 14 15With **RenderNode**, you can add, delete, query, and modify nodes, thereby changing the subtree structure of nodes; you can also query the parent-child relationships to obtain the results. 16 17> **NOTE** 18> 19> - The subtree structure obtained through queries in **RenderNode** is constructed based on the parameters passed through the APIs of **RenderNode**. 20> 21> - To display a RenderNode in conjunction with built-in components, you need to mount the RenderNode obtained from a FrameNode onto the component tree. 22 23```ts 24import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI'; 25 26const renderNode = new RenderNode(); 27renderNode.frame = { x: 0, y: 0, width: 200, height: 350 }; 28renderNode.backgroundColor = 0xffff0000; 29for (let i = 0; i < 5; i++) { 30 const node = new RenderNode(); 31 // Set the frame size of the node. 32 node.frame = { x: 10, y: 10 + 60 * i, width: 50, height: 50 }; 33 // Set the background color of the node. 34 node.backgroundColor = 0xff00ff00; 35 // Mount the new node to the RenderNode. 36 renderNode.appendChild(node); 37} 38 39class MyNodeController extends NodeController { 40 private rootNode: FrameNode | null = null; 41 42 makeNode(uiContext: UIContext): FrameNode | null { 43 this.rootNode = new FrameNode(uiContext); 44 45 const rootRenderNode = this.rootNode?.getRenderNode(); 46 if (rootRenderNode) { 47 rootRenderNode.appendChild(renderNode); 48 } 49 return this.rootNode; 50 } 51} 52 53@Entry 54@Component 55struct Index { 56 private myNodeController: MyNodeController = new MyNodeController(); 57 58 build() { 59 Row() { 60 NodeContainer(this.myNodeController) 61 .width(200) 62 .height(350) 63 Button('getNextSibling') 64 .onClick(() => { 65 const child = renderNode.getChild(1); 66 const nextSibling = child!.getNextSibling() 67 if (child === null || nextSibling === null) { 68 console.log('the child or nextChild is null'); 69 } else { 70 // Obtain the position of the child node. 71 console.log(`the position of child is x: ${child.position.x}, y: ${child.position.y}, ` + 72 `the position of nextSibling is x: ${nextSibling.position.x}, y: ${nextSibling.position.y}`); 73 } 74 }) 75 } 76 } 77} 78``` 79 80## Setting and Obtaining Rendering-related Properties 81 82In **RenderNode**, you can set rendering-related properties, including the following: [backgroundColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#backgroundcolor), [clipToFrame](../reference/apis-arkui/js-apis-arkui-renderNode.md#cliptoframe), [opacity](../reference/apis-arkui/js-apis-arkui-renderNode.md#opacity), [size](../reference/apis-arkui/js-apis-arkui-renderNode.md#size), [position](../reference/apis-arkui/js-apis-arkui-renderNode.md#position), [frame](../reference/apis-arkui/js-apis-arkui-renderNode.md#frame), [pivot](../reference/apis-arkui/js-apis-arkui-renderNode.md#pivot), [scale](../reference/apis-arkui/js-apis-arkui-renderNode.md#scale), [translation](../reference/apis-arkui/js-apis-arkui-renderNode.md#translation), [rotation](../reference/apis-arkui/js-apis-arkui-renderNode.md#rotation), [transform](../reference/apis-arkui/js-apis-arkui-renderNode.md#transform), [shadowColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowcolor), [shadowOffset](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowoffset), [shadowAlpha](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowalpha), [shadowElevation](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowelevation), [shadowRadius](../reference/apis-arkui/js-apis-arkui-renderNode.md#shadowradius), [borderStyle](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderstyle12), [borderWidth](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderwidth12), [borderColor](../reference/apis-arkui/js-apis-arkui-renderNode.md#bordercolor12), [borderRadius](../reference/apis-arkui/js-apis-arkui-renderNode.md#borderradius12), [shapeMask](../reference/apis-arkui/js-apis-arkui-renderNode.md#shapemask12), [shapeClip](../reference/apis-arkui/js-apis-arkui-renderNode.md#shapeclip12), [markNodeGroup](../reference/apis-arkui/js-apis-arkui-renderNode.md#marknodegroup12). For details about the supported attributes, see [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md#rendernode). 83 84> **NOTE** 85> 86> - The properties obtained from a query in **RenderNode** are the values that have been explicitly set. 87> 88> - If no parameters are provided or if the provided parameters are invalid, the query will return the default values. 89> 90> - Avoid modifying RenderNodes in a BuilderNode. In **BuilderNode**, how properties are applied and updated is governed by the state management system, independently of manual intervention. Be aware that setting the same **RenderNode** property in both BuilderNode and FrameNode could lead to unexpected behavior. 91 92```ts 93import { RenderNode, FrameNode, NodeController, ShapeMask, ShapeClip } from '@kit.ArkUI'; 94 95const mask = new ShapeMask(); 96mask.setRectShape({ left: 0, right: 150, top: 0, bottom: 150 }); 97mask.fillColor = 0X55FF0000; 98mask.strokeColor = 0XFFFF0000; 99mask.strokeWidth = 24; 100 101const clip = new ShapeClip(); 102clip.setCommandPath({ commands: "M100 0 L0 100 L50 200 L150 200 L200 100 Z" }); 103 104const renderNode = new RenderNode(); 105renderNode.backgroundColor = 0xffff0000; 106renderNode.size = { width: 100, height: 100 }; 107 108class MyNodeController extends NodeController { 109 private rootNode: FrameNode | null = null; 110 111 makeNode(uiContext: UIContext): FrameNode | null { 112 this.rootNode = new FrameNode(uiContext); 113 114 const rootRenderNode = this.rootNode.getRenderNode(); 115 if (rootRenderNode !== null) { 116 rootRenderNode.appendChild(renderNode); 117 } 118 119 return this.rootNode; 120 } 121} 122 123@Entry 124@Component 125struct Index { 126 private myNodeController: MyNodeController = new MyNodeController(); 127 128 build() { 129 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 130 Column() { 131 NodeContainer(this.myNodeController) 132 } 133 Button("position") 134 .width(300) 135 .onClick(() => { 136 renderNode.position = { x: 10, y: 10 }; 137 console.log("renderNode position:" + JSON.stringify(renderNode.position)); 138 }) 139 Button("pivot") 140 .width(300) 141 .onClick(() => { 142 renderNode.pivot = { x: 0.5, y: 0.6 }; 143 console.log("renderNode pivot:" + JSON.stringify(renderNode.pivot)); 144 }) 145 Button("scale") 146 .width(300) 147 .onClick(() => { 148 renderNode.scale = { x: 0.5, y: 1 }; 149 console.log("renderNode scale:" + JSON.stringify(renderNode.scale)); 150 }) 151 Button("translation") 152 .width(300) 153 .onClick(() => { 154 renderNode.translation = { x: 100, y: 0 }; 155 console.log("renderNode translation:" + JSON.stringify(renderNode.translation)); 156 }) 157 Button("rotation") 158 .width(300) 159 .onClick(() => { 160 renderNode.rotation = { x: 45, y: 0, z: 0 }; 161 console.log("renderNode rotation:" + JSON.stringify(renderNode.rotation)); 162 }) 163 Button("transform") 164 .width(300) 165 .onClick(() => { 166 renderNode.transform = [ 167 1, 0, 0, 0, 168 0, 2, 0, 0, 169 0, 0, 1, 0, 170 0, 0, 0, 1 171 ]; 172 console.log("renderNode transform:" + JSON.stringify(renderNode.transform)); 173 }) 174 Button("shadow") 175 .width(300) 176 .onClick(() => { 177 renderNode.shadowElevation = 10; 178 renderNode.shadowColor = 0XFF00FF00; 179 renderNode.shadowOffset = { x: 10, y: 10 }; 180 renderNode.shadowAlpha = 0.1; 181 console.log("renderNode shadowElevation:" + JSON.stringify(renderNode.shadowElevation)); 182 console.log("renderNode shadowColor:" + JSON.stringify(renderNode.shadowColor)); 183 console.log("renderNode shadowOffset:" + JSON.stringify(renderNode.shadowOffset)); 184 console.log("renderNode shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha)); 185 }) 186 Button("shadowRadius") 187 .width(300) 188 .onClick(() => { 189 renderNode.shadowOffset = { x: 10, y: 10 }; 190 renderNode.shadowAlpha = 0.7 191 renderNode.shadowRadius = 30; 192 console.log("renderNode shadowOffset:" + JSON.stringify(renderNode.shadowOffset)); 193 console.log("renderNode shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha)); 194 console.log("renderNode shadowRadius:" + JSON.stringify(renderNode.shadowRadius)); 195 }) 196 Button("border") 197 .width(300) 198 .onClick(() => { 199 renderNode.borderWidth = { left: 8, top: 8, right: 8, bottom: 8 }; 200 renderNode.borderStyle = { 201 left: BorderStyle.Solid, 202 top: BorderStyle.Dotted, 203 right: BorderStyle.Dashed, 204 bottom: BorderStyle.Solid 205 } 206 renderNode.borderColor = { left: 0xFF0000FF, top: 0xFF0000FF, right: 0xFF0000FF, bottom: 0xFF0000FF }; 207 renderNode.borderRadius = { topLeft: 32, topRight: 32, bottomLeft: 32, bottomRight: 32 }; 208 console.log("renderNode borderWidth:" + JSON.stringify(renderNode.borderWidth)); 209 console.log("renderNode borderStyle:" + JSON.stringify(renderNode.borderStyle)); 210 console.log("renderNode borderColor:" + JSON.stringify(renderNode.borderColor)); 211 console.log("renderNode borderRadius:" + JSON.stringify(renderNode.borderRadius)); 212 }) 213 Button("shapeMask") 214 .width(300) 215 .onClick(() => { 216 renderNode.shapeMask = mask; 217 console.log("renderNode shapeMask:" + JSON.stringify(renderNode.shapeMask)); 218 }) 219 Button("shapeClip") 220 .width(300) 221 .onClick(() => { 222 renderNode.shapeClip = clip; 223 console.log("renderNode shapeMask:" + JSON.stringify(renderNode.shapeMask)); 224 }) 225 } 226 .padding({ left: 35, right: 35, top: 35, bottom: 35 }) 227 .width("100%") 228 .height("100%") 229 } 230} 231``` 232 233## Using Custom Drawing 234 235Override the [draw](../reference/apis-arkui/js-apis-arkui-renderNode.md#draw) API in **RenderNode** to customize the drawing content and use the [invalidate](../reference/apis-arkui/js-apis-arkui-renderNode.md#invalidate) API to manually trigger a redraw of the node. 236 237> **NOTE** 238> 239> - Triggering multiple **invalidate** calls at once will only result in a single redraw. 240> 241> - Custom drawing can be implemented by calling ArkTS APIs or Node-APIs. 242 243**ArkTS API sample code** 244 245```ts 246import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI'; 247import { drawing } from '@kit.ArkGraphics2D'; 248 249class MyRenderNode extends RenderNode { 250 draw(context: DrawContext) { 251 // Obtain the canvas object. 252 const canvas = context.canvas; 253 // Create a brush. 254 const brush = new drawing.Brush(); 255 // Set the brush color. 256 brush.setColor({ alpha: 255, red: 255, green: 0, blue: 0 }); 257 canvas.attachBrush(brush); 258 // Draw a rectangle. 259 canvas.drawRect({ left: 0, right: 200, top: 0, bottom: 200 }); 260 canvas.detachBrush(); 261 } 262} 263 264const renderNode = new MyRenderNode(); 265renderNode.frame = { x: 0, y: 0, width: 300, height: 300 }; 266renderNode.backgroundColor = 0xff0000ff; 267renderNode.opacity = 0.5; 268 269class MyNodeController extends NodeController { 270 private rootNode: FrameNode | null = null; 271 272 makeNode(uiContext: UIContext): FrameNode | null { 273 this.rootNode = new FrameNode(uiContext); 274 275 const rootRenderNode = this.rootNode?.getRenderNode(); 276 if (rootRenderNode !== null) { 277 rootRenderNode.frame = { x: 0, y: 0, width: 500, height: 500 } 278 rootRenderNode.appendChild(renderNode); 279 } 280 281 return this.rootNode; 282 } 283} 284 285@Entry 286@Component 287struct Index { 288 private myNodeController: MyNodeController = new MyNodeController(); 289 290 build() { 291 Column() { 292 NodeContainer(this.myNodeController) 293 .width('100%') 294 Button('Invalidate') 295 .onClick(() => { 296 // Triggering multiple invalidate calls at once will only result in a single redraw. 297 renderNode.invalidate(); 298 renderNode.invalidate(); 299 }) 300 } 301 } 302} 303``` 304 305**Node-API sample code** 306 307The C++ side can obtain the canvas through the Node-API and perform subsequent custom drawing operations. 308 309```c++ 310// native_bridge.cpp 311#include "napi/native_api.h" 312#include <native_drawing/drawing_canvas.h> 313#include <native_drawing/drawing_color.h> 314#include <native_drawing/drawing_path.h> 315#include <native_drawing/drawing_pen.h> 316 317static napi_value OnDraw(napi_env env, napi_callback_info info) 318{ 319 size_t argc = 4; 320 napi_value args[4] = { nullptr }; 321 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 322 323 int32_t id; 324 napi_get_value_int32(env, args[0], &id); 325 326 // Obtain the pointer to the canvas. 327 void* temp = nullptr; 328 napi_unwrap(env, args[1], &temp); 329 OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas*>(temp); 330 331 // Obtain the canvas width. 332 int32_t width; 333 napi_get_value_int32(env, args[2], &width); 334 335 // Obtain the canvas height. 336 int32_t height; 337 napi_get_value_int32(env, args[3], &height); 338 339 // Pass in information such as the canvas, height, and width to the drawing API for custom drawing. 340 auto path = OH_Drawing_PathCreate(); 341 OH_Drawing_PathMoveTo(path, width / 4, height / 4); 342 OH_Drawing_PathLineTo(path, width * 3 / 4, height / 4); 343 OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4); 344 OH_Drawing_PathLineTo(path, width / 4, height * 3 / 4); 345 OH_Drawing_PathLineTo(path, width / 4, height / 4); 346 OH_Drawing_PathClose(path); 347 348 auto pen = OH_Drawing_PenCreate(); 349 OH_Drawing_PenSetWidth(pen, 10); 350 OH_Drawing_PenSetColor(pen, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0x00, 0x00)); 351 OH_Drawing_CanvasAttachPen(canvas, pen); 352 353 OH_Drawing_CanvasDrawPath(canvas, path); 354 355 return nullptr; 356} 357 358EXTERN_C_START 359static napi_value Init(napi_env env, napi_value exports) 360{ 361 napi_property_descriptor desc[] = { 362 { "nativeOnDraw", nullptr, OnDraw, nullptr, nullptr, nullptr, napi_default, nullptr } 363 }; 364 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 365 return exports; 366} 367EXTERN_C_END 368 369static napi_module demoModule = { 370 .nm_version =1, 371 .nm_flags = 0, 372 .nm_filename = nullptr, 373 .nm_register_func = Init, 374 .nm_modname = "entry", 375 .nm_priv = ((void*)0), 376 .reserved = { 0 }, 377}; 378 379extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 380{ 381 napi_module_register(&demoModule); 382} 383``` 384 385Add the following content to the **src/main/cpp/CMakeLists.txt** file of the project: 386```cmake 387# the minimum version of CMake. 388cmake_minimum_required(VERSION 3.4.1) 389project(NapiTest) 390 391set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 392 393include_directories(${NATIVERENDER_ROOT_PATH} 394 ${NATIVERENDER_ROOT_PATH}/include) 395 396add_library(entry SHARED native_bridge.cpp) 397target_link_libraries(entry PUBLIC libace_napi.z.so) 398target_link_libraries(entry PUBLIC libace_ndk.z.so) 399target_link_libraries(entry PUBLIC libnative_drawing.so) 400``` 401 402Add the definition of the custom drawing API on the ArkTS side to the **src/main/cpp/types/libentry/index.d.ts** file of the project. The following is an example: 403```ts 404import { DrawContext } from '@kit.ArkUI' 405 406export const nativeOnDraw: (id: number, context: DrawContext, width: number, height: number) => number; 407``` 408 409Code in ArkTS: 410 411```ts 412// Index.ets 413import bridge from "libentry.so" // This .so file is written and generated by Node-API. 414import { DrawContext, FrameNode, NodeController, RenderNode } from '@kit.ArkUI' 415 416class MyRenderNode extends RenderNode { 417 draw(context: DrawContext) { 418 // The width and height in the context need to be converted from vp to px. 419 bridge.nativeOnDraw(0, context, vp2px(context.size.height), vp2px(context.size.width)); 420 } 421} 422 423class MyNodeController extends NodeController { 424 private rootNode: FrameNode | null = null; 425 426 makeNode(uiContext: UIContext): FrameNode | null { 427 this.rootNode = new FrameNode(uiContext); 428 429 const rootRenderNode = this.rootNode.getRenderNode(); 430 if (rootRenderNode !== null) { 431 const renderNode = new MyRenderNode(); 432 renderNode.size = { width: 100, height: 100 } 433 rootRenderNode.appendChild(renderNode); 434 } 435 return this.rootNode; 436 } 437} 438 439@Entry 440@Component 441struct Index { 442 private myNodeController: MyNodeController = new MyNodeController(); 443 444 build() { 445 Row() { 446 NodeContainer(this.myNodeController) 447 } 448 } 449} 450``` 451 452## Setting the Label 453 454You can use the [label](../reference/apis-arkui/js-apis-arkui-renderNode.md#label12) API to assign labels for RenderNodes. This makes it easier to distinguish between nodes under node **Inspector**. 455 456```ts 457import { RenderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 458 459class MyNodeController extends NodeController { 460 private rootNode: FrameNode | null = null; 461 462 makeNode(uiContext: UIContext): FrameNode | null { 463 this.rootNode = new FrameNode(uiContext); 464 const renderNode: RenderNode | null = this.rootNode.getRenderNode(); 465 if (renderNode !== null) { 466 const renderChildNode: RenderNode = new RenderNode(); 467 renderChildNode.frame = { x: 0, y: 0, width: 100, height: 100 }; 468 renderChildNode.backgroundColor = 0xffff0000; 469 renderChildNode.label = 'customRenderChildNode'; 470 console.log('label:', renderChildNode.label); 471 renderNode.appendChild(renderChildNode); 472 } 473 474 return this.rootNode; 475 } 476} 477 478@Entry 479@Component 480struct Index { 481 private myNodeController: MyNodeController = new MyNodeController(); 482 483 build() { 484 Column() { 485 NodeContainer(this.myNodeController) 486 .width(300) 487 .height(700) 488 .backgroundColor(Color.Gray) 489 } 490 } 491} 492``` 493