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