1# 自定义渲染节点 (RenderNode) 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @xiang-shouxing--> 5<!--Designer: @xiang-shouxing--> 6<!--Tester: @sally__--> 7<!--Adviser: @HelloCrease--> 8 9## 概述 10 11对于不具备自己的渲染环境的三方框架,尽管已实现前端解析、布局及事件处理等功能,但仍需依赖系统的基础渲染和动画能力。[FrameNode](./arkts-user-defined-arktsNode-frameNode.md)上的通用属性与通用事件对这类框架而言是冗余的,会导致多次不必要的操作,涵盖布局、事件处理等逻辑。 12 13自定义渲染节点 ([RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md))是更加轻量的渲染节点,仅具备与渲染相关的功能。它提供了设置基础渲染属性的能力,以及节点的动态添加、删除和自定义绘制的能力。RenderNode能够为第三方框架提供基础的渲染和动画支持。 14 15## 创建和删除节点 16 17RenderNode提供了节点创建和删除的能力。可以通过RenderNode的构造函数创建自定义的RenderNode节点。通过构造函数创建的节点对应一个实体的节点。同时,可以通过RenderNode中的[dispose](../reference/apis-arkui/js-apis-arkui-renderNode.md#dispose12)接口来实现与实体节点的绑定关系的解除。 18 19## 操作节点树 20 21RenderNode提供了节点的增、删、查、改的能力,能够修改节点的子树结构;可以对所有RenderNode的节点的父子节点做出查询操作,并返回查询结果。 22 23> **说明:** 24> 25> - RenderNode中查询获取得到的子树结构按照开发通过RenderNode的接口传递的参数构建。 26> 27> - RenderNode如果要与系统直接结合显示,使用需要依赖FrameNode中获取的RenderNode进行挂载上树。 28 29```ts 30import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI'; 31 32const TEST_TAG: string = "RenderNode"; 33const renderNode = new RenderNode(); 34renderNode.frame = { 35 x: 0, 36 y: 0, 37 width: 200, 38 height: 350 39}; 40renderNode.backgroundColor = 0xffff0000; 41for (let i = 0; i < 5; i++) { 42 const node = new RenderNode(); 43 // 设置node节点的Frame大小 44 node.frame = { 45 x: 10, 46 y: 10 + 60 * i, 47 width: 50, 48 height: 50 49 }; 50 // 设置node节点的背景颜色 51 node.backgroundColor = 0xff00ff00; 52 // 将新增节点挂载在renderNode上 53 renderNode.appendChild(node); 54} 55 56class MyNodeController extends NodeController { 57 private rootNode: FrameNode | null = null; 58 59 makeNode(uiContext: UIContext): FrameNode | null { 60 this.rootNode = new FrameNode(uiContext); 61 62 const rootRenderNode = this.rootNode?.getRenderNode(); 63 if (rootRenderNode) { 64 rootRenderNode.appendChild(renderNode); 65 } 66 return this.rootNode; 67 } 68} 69 70@Entry 71@Component 72struct Index { 73 private myNodeController: MyNodeController = new MyNodeController(); 74 75 build() { 76 Row() { 77 NodeContainer(this.myNodeController) 78 .width(200) 79 .height(350) 80 Button('getNextSibling') 81 .onClick(() => { 82 const child = renderNode.getChild(1); 83 const nextSibling = child!.getNextSibling() 84 if (child === null || nextSibling === null) { 85 console.info(TEST_TAG + ' the child or nextChild is null'); 86 } else { 87 // 获取子节点的位置信息 88 console.info(`${TEST_TAG} the position of child is x: ${child.position.x}, y: ${child.position.y}, ` + 89 `the position of nextSibling is x: ${nextSibling.position.x}, y: ${nextSibling.position.y}`); 90 } 91 }) 92 } 93 } 94} 95``` 96 97## 设置和获取渲染相关属性 98 99RenderNode中可以设置渲染相关的属性,包括:[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)等。具体属性支持范围参考[RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md)接口说明。 100 101> **说明:** 102> 103> - RenderNode中查询获取得到的属性为设置的属性值。 104> 105> - 若未传入参数或者传入参数为非法值则查询获得的为默认值。 106> 107> - 不建议对BuilderNode中的RenderNode进行修改操作。BuilderNode中具体属性设置是由状态管理实现的,属性更新的时序开发者不可控,BuilderNode和FrameNode中同时设置RenderNode属性可能会导致RenderNode属性设置与预期不相符。 108 109```ts 110import { RenderNode, FrameNode, NodeController, ShapeMask, ShapeClip } from '@kit.ArkUI'; 111 112const TEST_TAG: string = "RenderNode"; 113const mask = new ShapeMask(); 114mask.setRectShape({ 115 left: 0, 116 right: 150, 117 top: 0, 118 bottom: 150 119}); 120mask.fillColor = 0X55FF0000; 121mask.strokeColor = 0XFFFF0000; 122mask.strokeWidth = 24; 123 124const clip = new ShapeClip(); 125clip.setCommandPath({ commands: "M100 0 L0 100 L50 200 L150 200 L200 100 Z" }); 126 127const renderNode = new RenderNode(); 128renderNode.backgroundColor = 0xffff0000; 129renderNode.size = { width: 100, height: 100 }; 130 131class MyNodeController extends NodeController { 132 private rootNode: FrameNode | null = null; 133 134 makeNode(uiContext: UIContext): FrameNode | null { 135 this.rootNode = new FrameNode(uiContext); 136 137 const rootRenderNode = this.rootNode.getRenderNode(); 138 if (rootRenderNode !== null) { 139 rootRenderNode.appendChild(renderNode); 140 } 141 142 return this.rootNode; 143 } 144} 145 146@Entry 147@Component 148struct Index { 149 private myNodeController: MyNodeController = new MyNodeController(); 150 151 build() { 152 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceBetween }) { 153 Column() { 154 NodeContainer(this.myNodeController) 155 } 156 157 Button("position") 158 .width(300) 159 .onClick(() => { 160 renderNode.position = { x: 10, y: 10 }; 161 console.info(TEST_TAG + " position:" + JSON.stringify(renderNode.position)); 162 }) 163 Button("pivot") 164 .width(300) 165 .onClick(() => { 166 renderNode.pivot = { x: 0.5, y: 0.6 }; 167 console.info(TEST_TAG + " pivot:" + JSON.stringify(renderNode.pivot)); 168 }) 169 Button("scale") 170 .width(300) 171 .onClick(() => { 172 renderNode.scale = { x: 0.5, y: 1 }; 173 console.info(TEST_TAG + " scale:" + JSON.stringify(renderNode.scale)); 174 }) 175 Button("translation") 176 .width(300) 177 .onClick(() => { 178 renderNode.translation = { x: 100, y: 0 }; 179 console.info(TEST_TAG + " translation:" + JSON.stringify(renderNode.translation)); 180 }) 181 Button("rotation") 182 .width(300) 183 .onClick(() => { 184 renderNode.rotation = { x: 45, y: 0, z: 0 }; 185 console.info(TEST_TAG + " rotation:" + JSON.stringify(renderNode.rotation)); 186 }) 187 Button("transform") 188 .width(300) 189 .onClick(() => { 190 renderNode.transform = [ 191 1, 0, 0, 0, 192 0, 2, 0, 0, 193 0, 0, 1, 0, 194 0, 0, 0, 1 195 ]; 196 console.info(TEST_TAG + " transform:" + JSON.stringify(renderNode.transform)); 197 }) 198 Button("shadow") 199 .width(300) 200 .onClick(() => { 201 renderNode.shadowElevation = 10; 202 renderNode.shadowColor = 0XFF00FF00; 203 renderNode.shadowOffset = { x: 10, y: 10 }; 204 renderNode.shadowAlpha = 0.1; 205 console.info(TEST_TAG + " shadowElevation:" + JSON.stringify(renderNode.shadowElevation)); 206 console.info(TEST_TAG + " shadowColor:" + JSON.stringify(renderNode.shadowColor)); 207 console.info(TEST_TAG + " shadowOffset:" + JSON.stringify(renderNode.shadowOffset)); 208 console.info(TEST_TAG + " shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha)); 209 }) 210 Button("shadowRadius") 211 .width(300) 212 .onClick(() => { 213 renderNode.shadowOffset = { x: 10, y: 10 }; 214 renderNode.shadowAlpha = 0.7 215 renderNode.shadowRadius = 30; 216 console.info(TEST_TAG + " shadowOffset:" + JSON.stringify(renderNode.shadowOffset)); 217 console.info(TEST_TAG + " shadowAlpha:" + JSON.stringify(renderNode.shadowAlpha)); 218 console.info(TEST_TAG + " shadowRadius:" + JSON.stringify(renderNode.shadowRadius)); 219 }) 220 Button("border") 221 .width(300) 222 .onClick(() => { 223 renderNode.borderWidth = { 224 left: 8, 225 top: 8, 226 right: 8, 227 bottom: 8 228 }; 229 renderNode.borderStyle = { 230 left: BorderStyle.Solid, 231 top: BorderStyle.Dotted, 232 right: BorderStyle.Dashed, 233 bottom: BorderStyle.Solid 234 } 235 renderNode.borderColor = { 236 left: 0xFF0000FF, 237 top: 0xFF0000FF, 238 right: 0xFF0000FF, 239 bottom: 0xFF0000FF 240 }; 241 renderNode.borderRadius = { 242 topLeft: 32, 243 topRight: 32, 244 bottomLeft: 32, 245 bottomRight: 32 246 }; 247 console.info(TEST_TAG + " borderWidth:" + JSON.stringify(renderNode.borderWidth)); 248 console.info(TEST_TAG + " borderStyle:" + JSON.stringify(renderNode.borderStyle)); 249 console.info(TEST_TAG + " borderColor:" + JSON.stringify(renderNode.borderColor)); 250 console.info(TEST_TAG + " borderRadius:" + JSON.stringify(renderNode.borderRadius)); 251 }) 252 Button("shapeMask") 253 .width(300) 254 .onClick(() => { 255 renderNode.shapeMask = mask; 256 console.info(TEST_TAG + " shapeMask:" + JSON.stringify(renderNode.shapeMask)); 257 }) 258 Button("shapeClip") 259 .width(300) 260 .onClick(() => { 261 renderNode.shapeClip = clip; 262 console.info(TEST_TAG + " shapeClip:" + JSON.stringify(renderNode.shapeClip)); 263 }) 264 } 265 .padding({ 266 left: 35, 267 right: 35, 268 top: 35, 269 bottom: 35 270 }) 271 .width("100%") 272 .height("100%") 273 } 274} 275``` 276 277## 自定义绘制 278 279通过重写RenderNode中的[draw](../reference/apis-arkui/js-apis-arkui-renderNode.md#draw)方法,可以自定义RenderNode的绘制内容,通过[invalidate](../reference/apis-arkui/js-apis-arkui-renderNode.md#invalidate)接口可以主动触发节点的重新绘制。 280 281> **说明:** 282> 283> - 同时同步触发多个invalidate仅会触发一次重新绘制。 284> 285> - 自定义绘制有两种绘制方式:通过ArkTS接口进行调用和通过Node-API进行调用。 286 287**ArkTS接口调用示例:** 288 289```ts 290import { FrameNode, NodeController, RenderNode } from '@kit.ArkUI'; 291import { drawing } from '@kit.ArkGraphics2D'; 292 293class MyRenderNode extends RenderNode { 294 width: number = 200; 295 296 draw(context: DrawContext) { 297 // 获取canvas对象 298 const canvas = context.canvas; 299 // 创建笔刷 300 const brush = new drawing.Brush(); 301 // 设置笔刷颜色 302 brush.setColor({ 303 alpha: 255, 304 red: 255, 305 green: 0, 306 blue: 0 307 }); 308 canvas.attachBrush(brush); 309 // 绘制矩阵 310 canvas.drawRect({ 311 left: 0, 312 right: this.width, 313 top: 0, 314 bottom: 200 315 }); 316 canvas.detachBrush(); 317 console.info(`RenderNode draw width = ${this.width}`); 318 } 319} 320 321const renderNode = new MyRenderNode(); 322renderNode.frame = { 323 x: 0, 324 y: 0, 325 width: 300, 326 height: 300 327}; 328renderNode.backgroundColor = 0xff0000ff; 329renderNode.opacity = 0.5; 330 331class MyNodeController extends NodeController { 332 private rootNode: FrameNode | null = null; 333 334 makeNode(uiContext: UIContext): FrameNode | null { 335 this.rootNode = new FrameNode(uiContext); 336 337 const rootRenderNode = this.rootNode?.getRenderNode(); 338 if (rootRenderNode !== null) { 339 rootRenderNode.frame = { 340 x: 0, 341 y: 0, 342 width: 500, 343 height: 500 344 } 345 rootRenderNode.appendChild(renderNode); 346 } 347 348 return this.rootNode; 349 } 350} 351 352@Entry 353@Component 354struct Index { 355 private myNodeController: MyNodeController = new MyNodeController(); 356 357 build() { 358 Column() { 359 NodeContainer(this.myNodeController) 360 .width('100%') 361 Button('Invalidate') 362 .onClick(() => { 363 // 同步调用多次,仅触发一次重绘,draw回调中的日志仅打印一次 364 renderNode.width += 10; 365 renderNode.invalidate(); 366 renderNode.invalidate(); 367 }) 368 } 369 } 370} 371``` 372 373**Node-API调用示例:** 374 375C++侧可通过Node-API来获取Canvas,并进行后续的自定义绘制操作。 376 377```c++ 378// native_bridge.cpp 379#include "napi/native_api.h" 380#include <native_drawing/drawing_canvas.h> 381#include <native_drawing/drawing_color.h> 382#include <native_drawing/drawing_path.h> 383#include <native_drawing/drawing_pen.h> 384 385static napi_value OnDraw(napi_env env, napi_callback_info info) 386{ 387 size_t argc = 4; 388 napi_value args[4] = { nullptr }; 389 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 390 391 int32_t id; 392 napi_get_value_int32(env, args[0], &id); 393 394 // 获取 Canvas 指针 395 void* temp = nullptr; 396 napi_unwrap(env, args[1], &temp); 397 OH_Drawing_Canvas *canvas = reinterpret_cast<OH_Drawing_Canvas*>(temp); 398 399 // 获取 Canvas 宽度 400 int32_t width; 401 napi_get_value_int32(env, args[2], &width); 402 403 // 获取 Canvas 高度 404 int32_t height; 405 napi_get_value_int32(env, args[3], &height); 406 407 // 传入canvas、height、width等信息至绘制函数中进行自定义绘制 408 auto path = OH_Drawing_PathCreate(); 409 OH_Drawing_PathMoveTo(path, width / 4, height / 4); 410 OH_Drawing_PathLineTo(path, width * 3 / 4, height / 4); 411 OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4); 412 OH_Drawing_PathLineTo(path, width / 4, height * 3 / 4); 413 OH_Drawing_PathLineTo(path, width / 4, height / 4); 414 OH_Drawing_PathClose(path); 415 416 auto pen = OH_Drawing_PenCreate(); 417 OH_Drawing_PenSetWidth(pen, 10); 418 OH_Drawing_PenSetColor(pen, OH_Drawing_ColorSetArgb(0xFF, 0xFF, 0x00, 0x00)); 419 OH_Drawing_CanvasAttachPen(canvas, pen); 420 421 OH_Drawing_CanvasDrawPath(canvas, path); 422 423 return nullptr; 424} 425 426EXTERN_C_START 427static napi_value Init(napi_env env, napi_value exports) 428{ 429 napi_property_descriptor desc[] = { 430 { "nativeOnDraw", nullptr, OnDraw, nullptr, nullptr, nullptr, napi_default, nullptr } 431 }; 432 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 433 return exports; 434} 435EXTERN_C_END 436 437static napi_module demoModule = { 438 .nm_version =1, 439 .nm_flags = 0, 440 .nm_filename = nullptr, 441 .nm_register_func = Init, 442 .nm_modname = "entry", 443 .nm_priv = ((void*)0), 444 .reserved = { 0 }, 445}; 446 447extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 448{ 449 napi_module_register(&demoModule); 450} 451``` 452 453修改工程中的`src/main/cpp/CMakeLists.txt`文件,添加如下内容: 454```cmake 455# the minimum version of CMake. 456cmake_minimum_required(VERSION 3.4.1) 457project(NapiTest) 458 459set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 460 461include_directories(${NATIVERENDER_ROOT_PATH} 462 ${NATIVERENDER_ROOT_PATH}/include) 463 464add_library(entry SHARED native_bridge.cpp) 465target_link_libraries(entry PUBLIC libace_napi.z.so) 466target_link_libraries(entry PUBLIC libace_ndk.z.so) 467target_link_libraries(entry PUBLIC libnative_drawing.so) 468``` 469 470同时在工程中的`src/main/cpp/types/libentry/index.d.ts`文件中,添加自定义绘制函数在ArkTS侧的定义,如: 471```ts 472import { DrawContext } from '@kit.ArkUI' 473 474export const nativeOnDraw: (id: number, context: DrawContext, width: number, height: number) => number; 475``` 476 477ArkTS侧代码: 478 479```ts 480// Index.ets 481import bridge from "libentry.so" // 该 so 由 Node-API 编写并生成 482import { DrawContext, FrameNode, NodeController, RenderNode } from '@kit.ArkUI' 483 484class MyRenderNode extends RenderNode { 485 uiContext: UIContext; 486 487 constructor(uiContext: UIContext) { 488 super(); 489 this.uiContext = uiContext; 490 } 491 492 draw(context: DrawContext) { 493 // 需要将 context 中的宽度和高度从vp转换为px 494 bridge.nativeOnDraw(0, context, this.uiContext.vp2px(context.size.height), this.uiContext.vp2px(context.size.width)); 495 } 496} 497 498class MyNodeController extends NodeController { 499 private rootNode: FrameNode | null = null; 500 501 makeNode(uiContext: UIContext): FrameNode | null { 502 this.rootNode = new FrameNode(uiContext); 503 504 const rootRenderNode = this.rootNode.getRenderNode(); 505 if (rootRenderNode !== null) { 506 const renderNode = new MyRenderNode(uiContext); 507 renderNode.size = { width: 100, height: 100 } 508 rootRenderNode.appendChild(renderNode); 509 } 510 return this.rootNode; 511 } 512} 513 514@Entry 515@Component 516struct Index { 517 private myNodeController: MyNodeController = new MyNodeController(); 518 519 build() { 520 Row() { 521 NodeContainer(this.myNodeController) 522 } 523 } 524} 525``` 526 527## 设置标签 528 529开发者可利用[label](../reference/apis-arkui/js-apis-arkui-renderNode.md#label12)接口向RenderNode设置标签信息,这有助于在节点Inspector中更清晰地区分各节点。 530 531```ts 532import { RenderNode, FrameNode, NodeController, UIContext } from '@kit.ArkUI'; 533 534class MyNodeController extends NodeController { 535 private rootNode: FrameNode | null = null; 536 537 makeNode(uiContext: UIContext): FrameNode | null { 538 this.rootNode = new FrameNode(uiContext); 539 const renderNode: RenderNode | null = this.rootNode.getRenderNode(); 540 if (renderNode !== null) { 541 const renderChildNode: RenderNode = new RenderNode(); 542 renderChildNode.frame = { x: 0, y: 0, width: 100, height: 100 }; 543 renderChildNode.backgroundColor = 0xffff0000; 544 renderChildNode.label = 'customRenderChildNode'; 545 console.info('label:', renderChildNode.label); 546 renderNode.appendChild(renderChildNode); 547 } 548 549 return this.rootNode; 550 } 551} 552 553@Entry 554@Component 555struct Index { 556 private myNodeController: MyNodeController = new MyNodeController(); 557 558 build() { 559 Column() { 560 NodeContainer(this.myNodeController) 561 .width(300) 562 .height(700) 563 .backgroundColor(Color.Gray) 564 } 565 } 566} 567``` 568 569## 查询当前RenderNode是否解除引用 570 571前端节点均绑定有相应的后端实体节点,当节点调用dispose接口解除绑定后,再次调用接口可能会出现crash、返回默认值的情况。 572 573从API version 20开始,使用[isDisposed](../reference/apis-arkui/js-apis-arkui-renderNode.md#isdisposed20)接口查询当前RenderNode对象是否已解除与后端实体节点的引用关系,从而可以在操作节点前检查其有效性,避免潜在风险。 574 575```ts 576import { NodeController, FrameNode, RenderNode } from '@kit.ArkUI'; 577 578class MyNodeController extends NodeController { 579 private rootNode: FrameNode | null = null; 580 private renderNode: RenderNode | null = null; 581 582 makeNode(uiContext: UIContext): FrameNode | null { 583 this.rootNode = new FrameNode(uiContext); 584 this.renderNode = new RenderNode(); 585 this.renderNode.size = { width: 300, height: 300 }; 586 this.renderNode.backgroundColor = 0xffd5d5d5; 587 588 // 挂载RenderNode 589 this.rootNode.getRenderNode()?.appendChild(this.renderNode); 590 return this.rootNode; 591 } 592 593 disposeRenderNode() { 594 // 解除RenderNode与后端实体节点的引用关系 595 this.renderNode?.dispose(); 596 } 597 598 isDisposed() : string { 599 if (this.renderNode !== null) { 600 // 查询RenderNode是否解除引用 601 if (this.renderNode.isDisposed()) { 602 return 'renderNode isDisposed is true'; 603 } 604 else { 605 return 'renderNode isDisposed is false'; 606 } 607 } 608 return 'renderNode is null'; 609 } 610} 611 612@Entry 613@Component 614struct Index { 615 @State text: string = '' 616 private myNodeController: MyNodeController = new MyNodeController(); 617 618 build() { 619 Column({ space: 4 }) { 620 NodeContainer(this.myNodeController) 621 Button('RenderNode dispose') 622 .onClick(() => { 623 this.myNodeController.disposeRenderNode(); 624 this.text = ''; 625 }) 626 .width(200) 627 .height(50) 628 Button('RenderNode isDisposed') 629 .onClick(() => { 630 this.text = this.myNodeController.isDisposed(); 631 }) 632 .width(200) 633 .height(50) 634 Text(this.text) 635 .fontSize(25) 636 } 637 .width('100%') 638 .height('100%') 639 } 640} 641```