1# ArkUI子系统Changelog 2 3 4## cl.arkui.1 通用属性backgroundEffect在modifier中radius参数单位修改 5 6**访问级别** 7 8公开接口 9 10**变更原因** 11 12 直接使用backgroundEffect时对应的模糊参数radius单位为vp。通过modifier或者CAPI使用时,单位为px。现将单位同一为vp。 13 14**变更影响** 15 16此变更涉及应用适配。 17 18变更前:backgroundEffect通过modifier使用时单位为px。<br/> 19 20 21变更后:backgroundEffect通过modifier使用时单位为vp。<br/> 22 23 24 25 26**起始API Level** 27 28API 12 29 30**变更发生版本** 31 32从OpenHarmony 5.1.0.45 版本开始。 33 34**变更的接口/组件** 35 36backgroundEffect 37 38**适配指导** 39 40默认无需适配。如需要保持之前模糊效果,在modifier中使用px2vp方法把radius参数转换为vp。 41 42```ts 43 44import { CommonModifier } from '@kit.ArkUI'; 45 46class ColumnModifier extends CommonModifier { 47 public radius: number = 0; 48 applyNormalAttribute(instance: CommonAttribute): void { 49 instance.backgroundEffect({ radius: this.radius }) 50 } 51} 52 53@Entry 54@Component 55struct Index { 56 @State testSize: number = 200; 57 @State modifier:ColumnModifier = new ColumnModifier(); 58 onPageShow(): void { 59 // 变更前 60 // this.modifier.radius = 10; 61 // 变更后适配 62 this.modifier.radius = px2vp(10); 63 } 64 build() { 65 Column() { 66 Stack() { 67 Image($r('app.media.test')).width(this.testSize).height(this.testSize) 68 Column().width(this.testSize).height(this.testSize).attributeModifier(this.modifier) 69 }.width('100%') 70 } 71 } 72} 73``` 74 75## cl.arkui.2 DrawModifier的invalidate接口行为变更 76 77**变更原因** 78 79实现bug,之前实现中未在节点标脏后请求下一帧vsync信号,导致部分场景下invalidate无法刷新绘制内容。 80 81**变更影响** 82 83此变更不涉及应用适配。 84 85变更前:invalidate接口仅对节点标脏,不请求下一帧。导致节点标脏但不会刷新重绘。等到其他操作,例如点击触发下一帧信号的时候才会重绘。 86 87变更后:nvalidate接口对节点标脏后主动请求下一帧,使节点及时刷新重绘。 88 89使用如下示例代码对影响补充说明。 90 91```ts 92import { drawing } from '@kit.ArkGraphics2D'; 93 94class MyFrontDrawModifier extends DrawModifier { 95 public scaleX: number = 1; 96 public scaleY: number = 1; 97 98 drawFront(context: DrawContext): void { 99 const brush = new drawing.Brush(); 100 brush.setColor({ 101 alpha: 255, 102 red: 0, 103 green: 0, 104 blue: 255 105 }); 106 context.canvas.attachBrush(brush); 107 const halfWidth = context.size.width / 2; 108 const halfHeight = context.size.width / 2; 109 const radiusScale = (this.scaleX + this.scaleY) / 2; 110 context.canvas.drawCircle(vp2px(halfWidth), vp2px(halfHeight), vp2px(20 * radiusScale)); 111 } 112} 113 114@Entry 115@Component 116struct DrawModifierExample { 117 @State modifier: MyFrontDrawModifier = new MyFrontDrawModifier(); 118 @State changeScale: boolean = false; 119 120 build() { 121 Column() { 122 Row() { 123 Text() 124 .width(100) 125 .height(100) 126 .margin(10) 127 .backgroundColor(Color.Gray) 128 .drawModifier(this.modifier) 129 } 130 131 Button('invalidate immediately') 132 .onClick(() => { 133 this.changeScale = !this.changeScale 134 // 变更前后不影响,节点在invalidate的下一帧刷新 135 this.changeScale ? this.modifier.scaleX = 2 : this.modifier.scaleX = 1 136 this.changeScale ? this.modifier.scaleY = 2 : this.modifier.scaleY = 1 137 this.modifier.invalidate() 138 } 139 ) 140 Button('invalidate in setTimeout ') 141 .onClick(() => { 142 setTimeout(() => { 143 // 变更前:invalidate对节点标脏,但不请求下一帧,等到其他事件触发下一帧,进行刷新绘制 144 // 变更后:节点在invalidate的下一帧刷新 145 this.changeScale = !this.changeScale 146 this.changeScale ? this.modifier.scaleX = 2 : this.modifier.scaleX = 1 147 this.changeScale ? this.modifier.scaleY = 2 : this.modifier.scaleY = 1 148 this.modifier.invalidate() 149 }, 1000) 150 } 151 ) 152 } 153 } 154} 155``` 156 157**起始API Level** 158 159API 12 160 161**变更发生版本** 162 163从OpenHarmony SDK 5.1.0.54开始。 164 165**变更的接口/组件** 166 167common.d.ts#DrawModifier.invalidate 168 169**适配指导** 170 171默认行为变更,无需适配。但需要注意invalidate调用后没有其他操作产生请求下一帧的场景下的影响。 172 173## cl.arkui.3 节点默认生命周期回调策略变更 174 175**访问级别** 176 177公开接口 178 179**变更原因** 180 181节点复用情况下,造成冗余的性能损耗。 182 183**变更影响** 184 185此变更不涉及应用适配。 186 187变更前:节点复用情况下,如果存在父布局约束。节点通过addChild重新上树默认触发测量,布局以及绘制。 188 189变更后:节点复用情况下节点通过addChild重新上树,只有布局约束发生变化才会触发测量布局以及绘制。 190 191示例代码如下,完整实例可参考使用ndk接口构建接入UI中的接入ArkTS页面。 192 193```ts 194 195static std::vector<ArkUI_NodeHandle> childList; 196 197void registerCustomEvent(ArkUI_NodeHandle node) { 198 nodeApi->registerNodeCustomEvent(node, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE, 0, nullptr); 199 nodeApi->registerNodeCustomEvent(node, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT, 0, nullptr); 200 nodeApi->registerNodeCustomEvent(node, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW, 0, nullptr); 201} 202 203void customEventReceiver(ArkUI_NodeCustomEvent *event) { 204 auto eventType = OH_ArkUI_NodeEvent_GetEventType(event); 205 // 变更前会触发自定义测量,布局,绘制事件 206 // 变更后不会触发自定义测量,布局,绘制事件 207} 208 209void init() { 210 211auto rootNode = nodeApi->CreateNode(ARKUI_NODE_CUSTOM); 212auto child1 = nodeApi->CreateNode(ARKUI_NODE_CUSTOM); 213auto child2 = nodeApi->CreateNode(ARKUI_NODE_CUSTOM); 214nodeApi->addChild(rootNode, child1); 215nodeApi->addChild(rootNode, child2); 216childList.push_back(child1); 217childList.push_back(child2); 218ArkUI_NumberValue value[] = {{.f32 = 100}}; 219ArkUI_AttributeItem item = {value, 1}; 220nodeApi->setAttribute(rootNode, NODE_WIDTH, item); 221nodeApi->setAttribute(child1, NODE_WIDTH, item); 222nodeApi->setAttribute(child2, NODE_WIDTH, item); 223nodeApi->setAttribute(rootNode, NODE_HEIGHT, item); 224nodeApi->setAttribute(child1, NODE_HEIGHT, item); 225nodeApi->setAttribute(child2, NODE_HEIGHT, item); 226 227// 注册订阅自定义事件 228registerCustomEvent(rootNode); 229registerCustomEvent(child1); 230registerCustomEvent(child2); 231nodeApi->registerNodeCustomEventReceiver(customEventReceiver); 232} 233 234void rebuildTree(ArkUI_NodeHandle root) { 235 // 触发组件上下树 236 auto front = childList.front(); 237 nodeApi->removeChild(root, front); 238 childList.erase(childList.begin()); 239 nodeApi->addChild(root, front); 240 childList.push_back(front); 241} 242 243``` 244 245**起始API Level** 246 247API 12 248 249**变更发生版本** 250 251从OpenHarmony SDK 5.1.0.54开始。 252 253**变更的接口/组件** 254 255frameNode 生命周期。 256 257**适配指导** 258 259默认行为变更无需适配。 260 261## cl.arkui.4 禁用键盘Ctrl按键和触控板轴事件触发缩放手势变更 262 263**访问级别** 264 265公开接口 266 267**变更原因** 268 269UX规格变化。 270 271**变更影响** 272 273此变更不涉及应用适配。 274 275变更前:键盘Ctrl按键和触控板双指水平滑动或者垂直滑动时,可以触发缩放手势。 276 277变更前:键盘Ctrl按键和触控板双指水平滑动或者垂直滑动时,不可以触发缩放手势。 278 279**起始API Level** 280 281接口起始版本为API version 7。 282 283**变更发生版本** 284 285从OpenHarmony SDK 5.1.0.54开始。 286 287**变更的接口/组件** 288 289ArkTS:PinchGestureInterface、PinchGestureHandler 290 291C API:createPinchGesture 292 293**适配指导** 294 295默认UX变更无需适配。 296 297## cl.arkui.5 FrameNode的isAttached接口返回值含义发生变更 298 299**访问级别** 300 301公开接口 302 303**变更原因** 304 305用户使用FrameNode的isAttached接口时,实际返回当前节点是否可见,即通用属性中的Visibility是否为Visible,而非当前节点是否被挂载到主节点树上。 306 307**变更影响** 308 309此变更涉及应用适配。 310 311变更前:返回当前节点是否可见。 312 313变更后:返回当前节点是否被挂载到主节点树上。 314 315当用户通过该接口获取目标节点是否被挂载到主节点树上,而目标节点为节点树上的不可见节点或未上树的可见节点时,返回值会发生变更,例如: 316```ts 317import { FrameNode, NodeController } from '@ohos.arkui.node'; 318import { UIContext } from '@ohos.arkui.UIContext'; 319 320class MyNodeController extends NodeController { 321 private rootNode: FrameNode | null = null; 322 private notAttachedNode: FrameNode | null = null; 323 private notVisibleNode: FrameNode | null = null; 324 325 makeNode(uiContext: UIContext) { 326 this.rootNode = new FrameNode(uiContext); 327 this.notAttachedNode = new FrameNode(uiContext); 328 this.notVisibleNode = new FrameNode(uiContext); 329 this.notVisibleNode.commonAttribute.visibility(Visibility.Hidden); 330 this.rootNode.appendChild(this.notVisibleNode); 331 return this.rootNode; 332 } 333 334 printInfo(): void { 335 if (this.notVisibleNode) { 336 // 主节点树上的不可见节点,变更前为false,变更后为true 337 console.log('notVisibleNode:', this.notVisibleNode.isAttached()); 338 } 339 if (this.notAttachedNode) { 340 // 未上树的可见节点,变更前为true,变更后为false 341 console.log('notAttachedNode:', this.notAttachedNode.isAttached()); 342 } 343 } 344} 345 346@Entry 347@Component 348struct MyComponent { 349 @State myNodeController: MyNodeController = new MyNodeController(); 350 351 build() { 352 Column() { 353 NodeContainer(this.myNodeController) 354 Button('click').onClick(() => { 355 this.myNodeController.printInfo(); 356 }) 357 } 358 .width('100%') 359 .alignItems(HorizontalAlign.Center) 360 } 361} 362``` 363 364**起始API Level** 365 366API 12 367 368**变更发生版本** 369 370从OpenHarmony SDK 5.1.0.54开始。 371 372**变更的接口/组件** 373 374FrameNode.d.ts文件isAttached接口。 375 376**适配指导** 377 378```ts 379// 变更前FrameNode的isAttached接口行为和isVisible接口一致,若需保持变更前行为,使用isVisible接口替换即可。 380node.isAttached(); // 变更前 381node.isVisible(); // 变更后 382``` 383 384## cl.arkui.6 RenderNode的rotation接口角度单位从vp变为度 385 386**访问级别** 387 388公开接口 389 390**变更原因** 391 392用户使用RenderNode的rotation接口时,传入的默认角度单位是vp,这不是正常规格的角度单位,需要变更为度。 393 394**变更影响** 395 396此变更涉及应用适配。 397 398变更前:角度单位为vp,需要经过px2vp单位转换才能转为角度。 399 400变更后:角度单位为度,直接传入数值即可,无需单位转换。 401 402当用户通过该接口设置RenderNode的旋转时,会发生变更; 例如: 403```ts 404import { FrameNode, NodeController, RenderNode, UIContext } from '@kit.ArkUI'; 405 406class MyNodeController extends NodeController { 407 private rootNode: FrameNode | null = null; 408 409 makeNode(uiContext: UIContext) { 410 this.rootNode = new FrameNode(uiContext); 411 412 // 直接传入90 413 const renderNodeSrc = new RenderNode(); 414 renderNodeSrc.backgroundColor = 0xffdddddd; 415 renderNodeSrc.frame = { x: 10, y: 110, width: 200, height: 100 }; 416 const renderNodeDst = new RenderNode(); 417 renderNodeDst.backgroundColor = 0xfffcc0ea; 418 renderNodeDst.frame = { x: 10, y: 110, width: 200, height: 100 }; 419 renderNodeDst.rotation = { x: 0, y: 0, z: 90 }; 420 421 // 传入px2vp(90) 422 const renderNodeSrc1 = new RenderNode(); 423 renderNodeSrc1.backgroundColor = 0xffdddddd; 424 renderNodeSrc1.frame = { x: 10, y: 360, width: 200, height: 100 }; 425 const renderNodeDst1 = new RenderNode(); 426 renderNodeDst1.backgroundColor = 0xfffcc0ea; 427 renderNodeDst1.frame = { x: 10, y: 360, width: 200, height: 100 }; 428 renderNodeDst1.rotation = { x: 0, y: 0, z: px2vp(90) }; 429 430 // 传入vp2px(90) 431 const renderNodeSrc2 = new RenderNode(); 432 renderNodeSrc2.backgroundColor = 0xffdddddd; 433 renderNodeSrc2.frame = { x: 10, y: 610, width: 200, height: 100 }; 434 const renderNodeDst2 = new RenderNode(); 435 renderNodeDst2.backgroundColor = 0xfffcc0ea; 436 renderNodeDst2.frame = { x: 10, y: 610, width: 200, height: 100 }; 437 renderNodeDst2.rotation = { x: 0, y: 0, z: vp2px(90) }; 438 439 const rootRenderNode = this.rootNode.getRenderNode(); 440 rootRenderNode?.appendChild(renderNodeSrc); 441 rootRenderNode?.appendChild(renderNodeDst); 442 rootRenderNode?.appendChild(renderNodeSrc1); 443 rootRenderNode?.appendChild(renderNodeDst1); 444 rootRenderNode?.appendChild(renderNodeSrc2); 445 rootRenderNode?.appendChild(renderNodeDst2); 446 447 return this.rootNode; 448 } 449} 450 451@Entry 452@Component 453struct MyComponent { 454 @State myNodeController: MyNodeController = new MyNodeController(); 455 456 build() { 457 Row() { 458 Column() { 459 Text('90') 460 Text('px2vp(90)') 461 Text('vp2px(90)') 462 } 463 .justifyContent(FlexAlign.SpaceAround) 464 .height('100%') 465 .width('30%') 466 NodeContainer(this.myNodeController) 467 .height('100%') 468 .width('70%') 469 } 470 .width('100%') 471 .alignItems(VerticalAlign.Top) 472 } 473} 474``` 475 476 477 478**起始API Level** 479 480API 12 481 482**变更发生版本** 483 484从OpenHarmony SDK 5.1.0.54开始。 485 486**变更的接口/组件** 487 488RenderNode.d.ts文件rotation接口。 489 490**适配指导** 491 492```ts 493// 变更前RenderNode的rotation接口的旋转角度单位为“vp”,变更后单位为“度”,若需保持变更前行为,使用vp2px进行单位转换即可。 494renderNode.rotation = { x: 0, y: 0, z: 90 }; // 变更前 495renderNode.rotation = { x: 0, y: 0, z: vp2px(90) }; // 变更后 496``` 497 498## cl.arkui.7 当AttributeModifier的applyNormalAttribute方法中instance参数设置为资源类型数据时更新的行为发生变更 499 500当开发者使用资源文件作为AttributeModifier的applyNormalAttribute方法中instance对象的入参时,无法通过配置资源文件更新参数,该行为与系统资源的规格不一致。 501 502**变更影响** 503 504此变更不涉及应用适配。 505 506运行以下示例时: 507 508```ts 509class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 510 private color?: ResourceColor; 511 private fontColor?: ResourceColor; 512 513 constructor(color: ResourceColor, fontColor: ResourceColor) { 514 this.color = color; 515 this.fontColor = fontColor; 516 } 517 518 applyNormalAttribute(instance: ButtonAttribute): void { 519 // instance为Button的属性对象,设置正常状态下属性值 520 instance.backgroundColor(this.color) 521 .fontColor(this.fontColor) 522 .borderWidth(1) 523 } 524} 525 526@Entry 527@Component 528struct attributeDemo { 529 @State modifier: MyButtonModifier = new MyButtonModifier($r('app.color.backColor'), $r('app.color.fontColor')); 530 531 build() { 532 Row() { 533 Column() { 534 Button("Button") 535 .attributeModifier(this.modifier) 536 }.width("100%") 537 } 538 .height('100%') 539 .backgroundColor(Color.White) 540 } 541} 542``` 543 544```json 545// src/main/resources/base/element/color.json 546{ 547 "color": [ 548 { 549 "name": "start_window_background", 550 "value": "#FFFFFF" 551 }, 552 { 553 "name": "backColor", 554 "value": "#000000" 555 }, 556 { 557 "name": "fontColor", 558 "value": "#FFFFFF" 559 } 560 ] 561} 562``` 563 564```json 565// src/main/resources/dark/element/color.json 566{ 567 "color": [ 568 { 569 "name": "start_window_background", 570 "value": "#000000" 571 }, 572 { 573 "name": "backColor", 574 "value": "#FFFFFF" 575 }, 576 { 577 "name": "fontColor", 578 "value": "#000000" 579 } 580 ] 581} 582``` 583 584| 变更前 | 变更后 | 585| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | 586| 浅色模式拉起。<br> | 浅色模式拉起。<br> | 587| | 588| 切换深色时,无法使用资源文件触发UI的更新。<br> | 切换深色时,可以使用资源文件触发UI的更新。<br> | 589| | 590 591**起始API Level** 592 593API 11 594 595**变更发生版本** 596 597从OpenHarmony SDK 5.0.1.54开始。 598 599**变更的接口/组件** 600 601common.d.ts文件attributeModifier接口。 602 603**适配指导** 604 605默认行为变更,无需适配。 606 607如不期望资源随配置文件更新可以将资源取出后使用。 608 609```ts 610class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 611 public color?: ResourceColor; 612 public fontColor?: ResourceColor; 613 614 constructor(color: ResourceColor, fontColor: ResourceColor) { 615 this.color = color; 616 this.fontColor = fontColor; 617 } 618 619 applyNormalAttribute(instance: ButtonAttribute): void { 620 // instance为Button的属性对象,设置正常状态下属性值 621 instance.backgroundColor(this.color) 622 .fontColor(this.fontColor) 623 .borderWidth(1) 624 } 625} 626 627@Entry 628@Component 629struct attributeDemo { 630 @State modifier: MyButtonModifier = new MyButtonModifier($r('app.color.backColor'), $r('app.color.fontColor')); 631 632 aboutToAppear(): void { 633 // 解析获取资源文件。 634 this.modifier.color = getContext().resourceManager.getColorSync($r('app.color.backColor').id); 635 this.modifier.fontColor = getContext().resourceManager.getColorSync($r('app.color.fontColor').id); 636 } 637 638 build() { 639 Row() { 640 Column() { 641 Button("Button") 642 .attributeModifier(this.modifier) 643 }.width("100%") 644 } 645 .height('100%') 646 .backgroundColor(Color.White) 647 } 648} 649``` 650 651| 变更前 | 变更后 | 652| -------------------------------------------------------- | ------------------------------------------------------ | 653| 浅色模式拉起。<br> | 深色模式拉起。<br> | 654| 切换深色。<br> | 切换浅色。<br> | 655 656## cl.arkui.8 命令式节点跨页面复用行为变更 657 658**访问级别** 659 660公开接口 661 662**变更原因** 663 664当使用router.replace、router.back或router.clear接口进行页面跳转时,原页面将被销毁,页面上的所有节点将被标记为InDestroying,无法在后续的流程中进行布局和绘制。由于命令式节点无法清除InDestroying标志位,因此在新页面上复用这些节点时,无法显示更新后的内容。 665 666**变更影响** 667 668此变更涉及应用适配。 669 670以下生命周期接口的行为将会受到影响。如果开发者在这些生命周期接口中实现了业务代码,由于变更前后的不同,通过BuilderNode进行跨页面复用时,生命周期接口的触发情况存在差异。若想保持原有的业务行为,则需要进行相应的适配。 671 672| 模块 | 变更前 | 变更后 | 673| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 674| FrameNode | 使用router.replace、router.back或router.clear发生页面跳转后,被复用的FrameNode无法在新页面中进行布局和绘制,onMeasure、onLayout和onDraw生命周期函数不触发 | 使用router.replace、router.back或router.clear发生页面跳转后,被复用的FrameNode能够在新页面中进行布局和绘制,onMeasure、onLayout和onDraw生命周期函数将会触发。 | 675| RenderNode | 使用router.replace、router.back或router.clear发生页面跳转后,被复用的RenderNode无法在新页面中进行绘制,draw回调接口不触发。 | 使用router.replace、router.back或router.clear发生页面跳转后,被复用的RenderNode能够在新页面中进行绘制,draw回调接口将会触发。 | 676| CustomSpan | 使用router.replace、router.back或router.clear发生页面跳转后,被复用的CustomSpan无法在新页面中进行布局和绘制,onMeasure和onDraw生命周期函数不触发。 | 使用router.replace、router.back或router.clear发生页面跳转后,被复用的CustomSpan能够在新页面中进行布局和绘制,onMeasure和onDraw生命周期函数将会触发。 | 677| CustomComponent | 使用router.replace、router.back或router.clear发生页面跳转后,当自定义组件CustomComponent用于BuilderNode进行复用时,自定义组件无法在新页面中进行布局,onMeasureSize生命周期函数不触发。 | 使用router.replace、router.back或router.clear发生页面跳转后,当自定义组件CustomComponent用于BuilderNode进行复用时,自定义组件能够在新页面中进行布局,onMeasureSize生命周期函数将会触发。 | 678| LazyForEach | 使用router.replace、router.back或router.clear发生页面跳转后,当LazyForEach用于BuilderNode进行复用时,BuilderNode无法在新页面中进行布局,getData不触发。 | 使用router.replace、router.back或router.clear发生页面跳转后,当LazyForEach用于BuilderNode进行复用时,BuilderNode能够在新页面中进行布局,getData将会触发。 | 679| DrawModifier | 使用router.replace、router.back或router.clear发生页面跳转后,在BuilderNode中使用的DrawModifier,由于BuilderNode及其子节点无法在新页面中进行布局和绘制,drawFront、drawContent和drawBehind生命周期函数不触发。 | 使用router.replace、router.back或router.clear发生页面跳转后,在BuilderNode中使用的DrawModifier,BuilderNode及其子节点能够在新页面中进行布局和绘制,drawFront、drawContent和drawBehind生命周期函数将会触发。 | 680| C API | 使用router.replace、router.back或router.clear发生页面跳转后,当C API自定义组件用于BuilderNode进行复用时,由于BuilderNode及其子节点无法在新页面中进行布局和绘制,ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE、ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT、ARKUI_NODE_CUSTOM_EVENT_ON_DRAW、ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW、ARKUI_NODE_CUSTOM_EVENT_ON_OVERLAY_DRAW生命周期函数不触发。 | 使用router.replace、router.back或router.clear发生页面跳转后,当C API自定义组件用于BuilderNode进行复用时,BuilderNode及其子节点能够在新页面中进行布局和绘制,ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE、ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT、ARKUI_NODE_CUSTOM_EVENT_ON_DRAW、ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW、ARKUI_NODE_CUSTOM_EVENT_ON_OVERLAY_DRAW生命周期函数将会触发。 | 681 682**变更发生版本** 683 684从OpenHarmony SDK 5.1.0.53 版本开始。 685 686**变更的接口/组件** 687 688| 文件 | 接口 | 689| ------------------ | ------------------------------------------ | 690| FrameNode.d.ts | FrameNode的onMeasure | 691| FrameNode.d.ts | FrameNode的onLayout | 692| FrameNode.d.ts | FrameNode的onDraw | 693| styled_string.d.ts | CustomSpan的onMeasure | 694| styled_string.d.ts | CustomSpan的onDraw | 695| common.d.ts | CustomComponent的onMeasureSize | 696| common.d.ts | DrawModifier的drawBehind | 697| common.d.ts | DrawModifier的drawContent | 698| common.d.ts | DrawModifier的drawFront | 699| lazy_for_each.d.ts | IDataSource的getData | 700| RenderNode.d.ts | RenderNode的draw | 701| native_node.h | ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE | 702| native_node.h | ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT | 703| native_node.h | ARKUI_NODE_CUSTOM_EVENT_ON_DRAW | 704| native_node.h | ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW | 705| native_node.h | ARKUI_NODE_CUSTOM_EVENT_ON_OVERLAY_DRAW | 706 707**适配指导** 708 709可通过在页面跳转时,将BuilderNode从缓存池中移除来保持节点的行为不变,示例代码如下: 710 7111. 按照[接入ArkTS页面](../../../application-dev/ui/ndk-access-the-arkts-page.md)创建前置工程以及添加相关封装对象。 712 7132. 替换cpp/NativeEntry.cpp为如下内容。 714 ```cpp 715 // NativeEntry.cpp 716 #include <arkui/native_node_napi.h> 717 #include <hilog/log.h> 718 #include <js_native_api.h> 719 #include <native_drawing/drawing_canvas.h> 720 #include <native_drawing/drawing_color.h> 721 #include <native_drawing/drawing_path.h> 722 #include <native_drawing/drawing_pen.h> 723 724 #include "NativeEntry.h" 725 #include "ArkUINode.h" 726 727 namespace NativeModule { 728 729 void DrawRect(ArkUI_NodeCustomEvent *event, int32_t width, int32_t height, uint32_t penColor) { 730 auto *drawContext = OH_ArkUI_NodeCustomEvent_GetDrawContextInDraw(event); 731 auto *canvas = OH_ArkUI_DrawContext_GetCanvas(drawContext); 732 OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1"); 733 OH_ArkUI_DrawContext_GetSize(drawContext); 734 735 auto *drawingCanvas = reinterpret_cast<OH_Drawing_Canvas *>(canvas); 736 737 auto *path = OH_Drawing_PathCreate(); 738 OH_Drawing_PathMoveTo(path, width / 4, height / 4); 739 OH_Drawing_PathLineTo(path, width * 3 / 4, height / 4); 740 OH_Drawing_PathLineTo(path, width * 3 / 4, height * 3 / 4); 741 OH_Drawing_PathLineTo(path, width / 4, height * 3 / 4); 742 OH_Drawing_PathLineTo(path, width / 4, height / 4); 743 744 auto *pen = OH_Drawing_PenCreate(); 745 OH_Drawing_PenSetWidth(pen, 10); 746 OH_Drawing_PenSetColor(pen, penColor); 747 OH_Drawing_CanvasAttachPen(drawingCanvas, pen); 748 OH_Drawing_CanvasDrawPath(drawingCanvas, path); 749 750 OH_Drawing_PathDestroy(path); 751 OH_Drawing_PenDestroy(pen); 752 } 753 754 ArkUI_NodeHandle testCustomNode(ArkUI_NativeNodeAPI_1 *nodeAPI) { 755 auto column = nodeAPI->createNode(ARKUI_NODE_COLUMN); 756 auto customNode = nodeAPI->createNode(ARKUI_NODE_CUSTOM); 757 758 static ArkUI_NativeNodeAPI_1 *nodeAPISelf = nodeAPI; 759 static ArkUI_NodeHandle customNodeSelf = customNode; 760 761 ArkUI_NumberValue columnValue[] = {400}; 762 ArkUI_AttributeItem columnItem = {columnValue, 1}; 763 nodeAPI->setAttribute(column, NODE_WIDTH, &columnItem); 764 nodeAPI->setAttribute(column, NODE_HEIGHT, &columnItem); 765 766 ArkUI_NumberValue customValue[] = {400}; 767 ArkUI_AttributeItem customItem = {customValue, 1}; 768 nodeAPI->setAttribute(customNode, NODE_WIDTH, &customItem); 769 nodeAPI->setAttribute(customNode, NODE_HEIGHT, &customItem); 770 ArkUI_NumberValue nodeBackgroundColorValue[] = {{.u32 = 0xffffff00}}; 771 ArkUI_AttributeItem nodeBackgroundColorItem = {nodeBackgroundColorValue}; 772 nodeAPI->setAttribute(customNode, NODE_BACKGROUND_COLOR, &nodeBackgroundColorItem); 773 774 nodeAPI->registerNodeCustomEvent(customNode, ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE, 1, nullptr); 775 nodeAPI->registerNodeCustomEvent(customNode, ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT, 2, nullptr); 776 nodeAPI->registerNodeCustomEvent(customNode, ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW, 3, nullptr); 777 nodeAPI->registerNodeCustomEvent(customNode, ARKUI_NODE_CUSTOM_EVENT_ON_DRAW, 4, nullptr); 778 nodeAPI->registerNodeCustomEvent(customNode, ARKUI_NODE_CUSTOM_EVENT_ON_OVERLAY_DRAW, 5, nullptr); 779 780 nodeAPI->registerNodeCustomEventReceiver([](ArkUI_NodeCustomEvent *event) { 781 auto type = OH_ArkUI_NodeCustomEvent_GetEventType(event); 782 if (type == ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE) { 783 // 变更前ARKUI_NODE_CUSTOM_EVENT_ON_MEASURE在页面返回后不会触发,变更后将会触发 784 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "CustomNode OnMeasure"); 785 nodeAPISelf->setMeasuredSize(customNodeSelf, 100, 100); 786 } else if (type == ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT) { 787 // 变更前ARKUI_NODE_CUSTOM_EVENT_ON_LAYOUT在页面返回后不会触发,变更后将会触发 788 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "CustomNode OnLayout"); 789 nodeAPISelf->setLayoutPosition(customNodeSelf, 48, 0); 790 } else if (type == ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW) { 791 // 变更前ARKUI_NODE_CUSTOM_EVENT_ON_FOREGROUND_DRAW在页面返回后不会触发,变更后将会触发 792 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "CustomNode OnForegroundDraw"); 793 DrawRect(event, 200, 200, OH_Drawing_ColorSetArgb(0xff, 0xff, 0x00, 0x00)); 794 } else if (type == ARKUI_NODE_CUSTOM_EVENT_ON_DRAW) { 795 // 变更前ARKUI_NODE_CUSTOM_EVENT_ON_DRAW在页面返回后不会触发,变更后将会触发 796 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "CustomNode OnDraw"); 797 DrawRect(event, 300, 300, OH_Drawing_ColorSetArgb(0xff, 0x00, 0xff, 0x00)); 798 } else if (type == ARKUI_NODE_CUSTOM_EVENT_ON_OVERLAY_DRAW) { 799 // 变更前ARKUI_NODE_CUSTOM_EVENT_ON_OVERLAY_DRAW在页面返回后不会触发,变更后将会触发 800 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Manager", "CustomNode OnDraw"); 801 DrawRect(event, 400, 400, OH_Drawing_ColorSetArgb(0xff, 0x00, 0x00, 0xff)); 802 } 803 }); 804 nodeAPI->addChild(column, customNode); 805 return column; 806 } 807 808 napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { 809 size_t argc = 1; 810 napi_value args[1] = {nullptr}; 811 812 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 813 814 // 获取NodeContent 815 ArkUI_NodeContentHandle contentHandle; 816 OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle); 817 NativeEntry::GetInstance()->SetContentHandle(contentHandle); 818 819 // 创建CustomNode 820 auto *nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1 *>( 821 OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1")); 822 auto column = testCustomNode(nodeAPI); 823 auto root = std::make_shared<ArkUINode>(column); 824 825 // 保持Native侧对象到管理类中,维护生命周期。 826 NativeEntry::GetInstance()->SetRootNode(root); 827 return nullptr; 828 } 829 830 napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { 831 // 从管理类中释放Native侧对象。 832 NativeEntry::GetInstance()->DisposeRootNode(); 833 return nullptr; 834 } 835 836 } // namespace NativeModule 837 ``` 838 8393. pages目录下添加如下文件。 840 841 ```ts 842 // pages/CustomComponentExample.ets 843 @Component 844 struct MyComponentWithOnMeasure { 845 @StorageLink("isShowText") isShowText: boolean = true; 846 847 build() { 848 if (this.isShowText) { 849 Column({ space: 16 }) { 850 Text("This is CustomComponent") 851 .fontWeight(FontWeight.Bold) 852 .fontSize(24) 853 } 854 .alignItems(HorizontalAlign.Center) 855 .width('100%') 856 .padding(16) 857 } 858 } 859 860 onMeasureSize(selfLayoutInfo: GeometryInfo, children: Measurable[], constraint: ConstraintSizeOptions): SizeResult { 861 // 变更前onMeasureSize在页面返回后不会触发,变更后将会触发 862 console.info(`FZY ${JSON.stringify(constraint)}`); 863 let height = 0; 864 let width = 0; 865 children.forEach((child) => { 866 let result: MeasureResult = child.measure({ 867 minHeight: 0, 868 minWidth: 0, 869 maxWidth: constraint.maxWidth, 870 maxHeight: constraint.maxHeight 871 }) 872 height += result.height; 873 width = Math.max(width, result.width); 874 ; 875 }) 876 return { width, height }; 877 } 878 } 879 880 export { MyComponentWithOnMeasure }; 881 882 ``` 883 884 ```ts 885 // pages/CustomSpanExample.ets 886 import drawing from '@ohos.graphics.drawing'; 887 888 const TEST_TAG = 'TEST' 889 890 class MyCustomSpan extends CustomSpan { 891 constructor(word: string, width: number, height: number) { 892 super(); 893 this.word = word; 894 this.width = width; 895 this.height = height; 896 } 897 898 onMeasure(measureInfo: CustomSpanMeasureInfo): CustomSpanMetrics { 899 // 变更前onMeasure在页面返回后不会触发,变更后将会触发 900 console.info(`${TEST_TAG} CustomSpan onMeasure`); 901 return { width: this.width, height: this.height }; 902 } 903 904 onDraw(context: DrawContext, options: CustomSpanDrawInfo) { 905 // 变更前onDraw在页面返回后不会触发,变更后将会触发 906 let canvas = context.canvas; 907 908 const brush = new drawing.Brush(); 909 brush.setColor({ 910 alpha: 255, 911 red: 0, 912 green: 74, 913 blue: 175 914 }); 915 const font = new drawing.Font(); 916 font.setSize(25); 917 const textBlob = drawing.TextBlob.makeFromString(this.word, font, drawing.TextEncoding.TEXT_ENCODING_UTF8); 918 canvas.attachBrush(brush); 919 canvas.drawRect({ 920 left: options.x + 10, 921 right: options.x + vp2px(this.width) - 10, 922 top: options.lineTop + 10, 923 bottom: options.lineBottom - 10 924 }); 925 926 brush.setColor({ 927 alpha: 255, 928 red: 23, 929 green: 169, 930 blue: 141 931 }); 932 canvas.attachBrush(brush); 933 canvas.drawTextBlob(textBlob, options.x + 20, options.lineBottom - 15); 934 canvas.detachBrush(); 935 } 936 937 setWord(word: string) { 938 this.word = word; 939 } 940 941 width: number = 160; 942 word: string = "drawing"; 943 height: number = 10; 944 } 945 946 export { MyCustomSpan }; 947 ``` 948 949 ```ts 950 // pages/DataSource.ets 951 // 点击Add Data->Router pageTwo->Add Data 952 // 最后一次Add Data点击后,measure和draw不触发,List无内容显示 953 954 const TEST_TAG = 'TEST' 955 956 class BasicDataSource implements IDataSource { 957 private listeners: DataChangeListener[] = []; 958 private originDataArray: string[] = []; 959 960 public totalCount(): number { 961 return 0; 962 } 963 964 public getData(index: number): string { 965 return this.originDataArray[index]; 966 } 967 968 registerDataChangeListener(listener: DataChangeListener): void { 969 if (this.listeners.indexOf(listener) < 0) { 970 console.info('add listener'); 971 this.listeners.push(listener); 972 } 973 } 974 975 unregisterDataChangeListener(listener: DataChangeListener): void { 976 const pos = this.listeners.indexOf(listener); 977 if (pos >= 0) { 978 console.info('remove listener'); 979 this.listeners.splice(pos, 1); 980 } 981 } 982 983 notifyDataReload(): void { 984 this.listeners.forEach(listener => { 985 listener.onDataReloaded(); 986 }) 987 } 988 989 notifyDataAdd(index: number): void { 990 this.listeners.forEach(listener => { 991 listener.onDataAdd(index); 992 }) 993 } 994 995 notifyDataChange(index: number): void { 996 this.listeners.forEach(listener => { 997 listener.onDataChange(index); 998 }) 999 } 1000 1001 notifyDataDelete(index: number): void { 1002 this.listeners.forEach(listener => { 1003 listener.onDataDelete(index); 1004 }) 1005 } 1006 1007 notifyDataMove(from: number, to: number): void { 1008 this.listeners.forEach(listener => { 1009 listener.onDataMove(from, to); 1010 }) 1011 } 1012 1013 notifyDatasetChange(operations: DataOperation[]): void { 1014 this.listeners.forEach(listener => { 1015 listener.onDatasetChange(operations); 1016 }) 1017 } 1018 } 1019 1020 class MyDataSource extends BasicDataSource { 1021 private dataArray: string[] = []; 1022 private getDataCount: number = 0; 1023 1024 public totalCount(): number { 1025 return this.dataArray.length; 1026 } 1027 1028 public getData(index: number): string { 1029 // 变更前getData在页面返回后不会触发,变更后将会触发 1030 console.info(`${TEST_TAG} LazyForeach getData`); 1031 this.getDataCount++; 1032 return this.dataArray[index]; 1033 } 1034 1035 public pushData(data: string): void { 1036 this.dataArray.push(data); 1037 this.notifyDataAdd(this.dataArray.length - 1); 1038 } 1039 } 1040 1041 export { MyDataSource }; 1042 ``` 1043 1044 ```ts 1045 // pages/FrameNodeExample.ets 1046 import { FrameNode, Position, LayoutConstraint } from "@kit.ArkUI" 1047 1048 const TEST_TAG = "TEST" 1049 1050 class MyFrameNode extends FrameNode { 1051 private nodeId: number; 1052 1053 constructor(uiContext: UIContext, nodeId: number) { 1054 super(uiContext); 1055 this.nodeId = nodeId; 1056 } 1057 1058 onMeasure(constraint: LayoutConstraint): void { 1059 // 变更前onMeasure在页面返回后不会触发,变更后将会触发 1060 console.info(`${TEST_TAG} FrameNode onMeasure ${this.nodeId}`); 1061 this.setMeasuredSize(constraint.maxSize); 1062 } 1063 1064 onLayout(position: Position): void { 1065 // 变更前onLayout在页面返回后不会触发,变更后将会触发 1066 console.info(`${TEST_TAG} FrameNode onLayout ${this.nodeId}`); 1067 this.setLayoutPosition(position); 1068 } 1069 1070 onDraw(context: DrawContext): void { 1071 // 变更前onDraw在页面返回后不会触发,变更后将会触发 1072 console.info(`${TEST_TAG} FrameNode onDraw ${this.nodeId}`); 1073 1074 const canvas = context.canvas; 1075 const width = context.sizeInPixel.width; 1076 const height = context.sizeInPixel.height; 1077 1078 canvas.drawCircle(width / 2, height / 2, Math.min(width, height) / 2); 1079 } 1080 } 1081 1082 export { MyFrameNode }; 1083 ``` 1084 1085 ```ts 1086 // pages/Index.ets 1087 import { NodeController, BuilderNode, FrameNode, UIContext, NodeContent } from "@kit.ArkUI"; 1088 import { MyFrameNode } from './FrameNodeExample'; 1089 import { MyRenderNode } from './RenderNodeExample'; 1090 import { MyCustomSpan } from './CustomSpanExample'; 1091 import { MyComponentWithOnMeasure } from './CustomComponentExample'; 1092 import { MyDataSource } from './DataSource'; 1093 import { MyFullDrawModifier } from './ModifierExample'; 1094 import nativeNode from "libentry.so" 1095 import "ets/pages/PageTwo" 1096 1097 const TEST_TAG = "TEST" 1098 1099 class MyController extends NodeController { 1100 private rootNode: FrameNode | null = null; 1101 private frameNode: FrameNode | null = null; 1102 private nodeId: number; 1103 1104 constructor(nodeId: number) { 1105 super(); 1106 this.nodeId = nodeId; 1107 } 1108 1109 makeNode(uiContext: UIContext): FrameNode { 1110 this.rootNode = new FrameNode(uiContext); 1111 1112 this.frameNode = new MyFrameNode(uiContext, this.nodeId); 1113 this.frameNode.commonAttribute.width(50); 1114 this.frameNode.commonAttribute.height(50); 1115 this.frameNode.commonAttribute.backgroundColor(Color.Gray); 1116 this.rootNode.appendChild(this.frameNode); 1117 1118 const renderNode = new MyRenderNode(); 1119 renderNode.size = { width: 50, height: 50 }; 1120 renderNode.position = { x: 100, y: 0 } 1121 this.rootNode.getRenderNode()?.appendChild(renderNode); 1122 1123 return this.rootNode; 1124 } 1125 } 1126 1127 1128 @Builder 1129 function buildText() { 1130 MyComponent() 1131 } 1132 1133 @Component 1134 struct MyComponent { 1135 @StorageLink("isShowText") isShowText: boolean = true; 1136 @StorageLink("controllers") controllers: Array<MyController> = []; 1137 private scroller: Scroller = new Scroller(); 1138 private customSpan: MyCustomSpan = new MyCustomSpan("TextSpan Example", 80, 10); 1139 private style: MutableStyledString = new MutableStyledString(this.customSpan); 1140 private textController: TextController = new TextController(); 1141 @State data: MyDataSource = new MyDataSource(); 1142 private rootSlot = new NodeContent(); 1143 private nodeCnt: number = 0; 1144 1145 build() { 1146 if (this.isShowText) { 1147 Scroll(this.scroller) { 1148 Column({ space: 16 }) { 1149 Column() { 1150 Text("NDK:") 1151 .fontSize(24) 1152 .fontWeight(FontWeight.Bold) 1153 ContentSlot(this.rootSlot) 1154 } 1155 .alignItems(HorizontalAlign.Center) 1156 .width('100%') 1157 .height(150) 1158 .backgroundColor(Color.Yellow) 1159 .onAppear(() => { 1160 nativeNode.createNativeRoot(this.rootSlot); 1161 }) 1162 .onDisAppear(() => { 1163 nativeNode.destroyNativeRoot(); 1164 }) 1165 Column({ space: 16 }) { 1166 Text('FrameNode & RenderNode & CustomSpan & CustomComponent & DrawModifier & LazyForeach:') 1167 .fontSize(24) 1168 .fontWeight(FontWeight.Bold) 1169 .alignSelf(ItemAlign.Start) 1170 Button("Add FrameNode") 1171 .onClick(() => { 1172 this.data.pushData((this.nodeCnt + 1).toString()); 1173 console.info(`controllers cnt ${this.controllers.length}`) 1174 }) 1175 Button("Set CustomSpan") 1176 .onClick(() => { 1177 this.textController.setStyledString(this.style) 1178 }) 1179 List() { 1180 LazyForEach(this.data, (item: string) => { 1181 ListItem() { 1182 Column({ space: 16 }) { 1183 NodeContainer(new MyController(++this.nodeCnt)) 1184 .width('100%') 1185 .backgroundColor(Color.Pink) 1186 Text(undefined, { controller: this.textController }) 1187 .copyOption(CopyOptions.InApp) 1188 .fontSize(36) 1189 MyComponentWithOnMeasure() 1190 Text('DrawModifier Example') 1191 .fontWeight(FontWeight.Bold) 1192 .fontSize(24) 1193 .width('100%') 1194 .height(200) 1195 .drawModifier(new MyFullDrawModifier()) 1196 } 1197 } 1198 }) 1199 } 1200 .width('100%') 1201 .layoutWeight(1) 1202 } 1203 .width('100%') 1204 .height('90%') 1205 .borderWidth(1) 1206 1207 } 1208 .width('100%') 1209 .height('100%') 1210 .padding(16) 1211 } 1212 .width('100%') 1213 .layoutWeight(1) 1214 } 1215 } 1216 } 1217 1218 class TextNodeController extends NodeController { 1219 private rootNode: FrameNode | null = null; 1220 private textNode: BuilderNode<[]> | null = null; 1221 1222 makeNode(context: UIContext): FrameNode | null { 1223 if (AppStorage.has("textNode")) { 1224 // Reuse the BuilderNode in AppStorage 1225 this.textNode = AppStorage.get<BuilderNode<[]>>("textNode") as BuilderNode<[]>; 1226 const parent = this.textNode.getFrameNode()?.getParent(); 1227 if (parent) { 1228 parent.removeChild(this.textNode.getFrameNode()); 1229 } 1230 } else { 1231 this.textNode = new BuilderNode(context); 1232 this.textNode.build(wrapBuilder<[]>(buildText)); 1233 // Add BuilderNode to AppStorage 1234 AppStorage.setOrCreate<BuilderNode<[]>>("textNode", this.textNode); 1235 } 1236 1237 return this.textNode.getFrameNode(); 1238 } 1239 } 1240 1241 @Entry({ routeName: "myIndex" }) 1242 @Component 1243 struct Index { 1244 aboutToAppear(): void { 1245 AppStorage.setOrCreate<boolean>("isShowText", true); 1246 } 1247 1248 build() { 1249 Row() { 1250 Column({ space: 16 }) { 1251 Button('Router pageTwo') 1252 .onClick(() => { 1253 // Change the conditions to trigger Text rebuild 1254 AppStorage.setOrCreate<boolean>("isShowText", false); 1255 // Remove BuilderNode from AppStorage 1256 AppStorage.delete("textNode"); 1257 1258 this.getUIContext().getRouter().replaceNamedRoute({ name: "pageTwo" }); 1259 }) 1260 NodeContainer(new TextNodeController()) 1261 .width('100%') 1262 .backgroundColor('#FFF0F0F0') 1263 } 1264 .width('100%') 1265 .height('100%') 1266 .padding({ bottom: 16 }) 1267 } 1268 .height('100%') 1269 } 1270 } 1271 ``` 1272 1273 ```ts 1274 // pages/ModifierExample.ets 1275 import { DrawContext } from "@kit.ArkUI"; 1276 import drawing from '@ohos.graphics.drawing'; 1277 1278 const TEST_TAG = "DrawModifier" 1279 1280 class MyFullDrawModifier extends DrawModifier { 1281 drawBehind(context: DrawContext): void { 1282 // 变更前drawBehind在页面返回后不会触发,变更后将会触发 1283 console.info(TEST_TAG + " drawBehind"); 1284 const brush = new drawing.Brush(); 1285 brush.setColor(255, 255, 0, 0); 1286 context.canvas.attachBrush(brush); 1287 const halfWidth = context.size.width / 2; 1288 const halfHeight = context.size.width / 2; 1289 context.canvas.drawRect({ 1290 left: 0, 1291 top: 0, 1292 right: vp2px(halfWidth / 2), 1293 bottom: vp2px(halfHeight / 2) 1294 }); 1295 } 1296 1297 drawContent(context: DrawContext): void { 1298 // 变更前drawContent在页面返回后不会触发,变更后将会触发 1299 console.info(TEST_TAG + " drawContent"); 1300 const brush = new drawing.Brush(); 1301 brush.setColor(255, 0, 255, 0); 1302 context.canvas.attachBrush(brush); 1303 const halfWidth = context.size.width / 2; 1304 const halfHeight = context.size.width / 2; 1305 context.canvas.drawRect({ 1306 left: 30, 1307 top: 30, 1308 right: vp2px(halfWidth / 2) - 30, 1309 bottom: vp2px(halfHeight / 2) - 30 1310 }); 1311 } 1312 1313 drawFront(context: DrawContext): void { 1314 // 变更前drawFront在页面返回后不会触发,变更后将会触发 1315 console.info(TEST_TAG + " drawFront"); 1316 const brush = new drawing.Brush(); 1317 brush.setColor(255, 0, 0, 255); 1318 context.canvas.attachBrush(brush); 1319 const halfWidth = context.size.width / 2; 1320 const halfHeight = context.size.width / 2; 1321 const radius = Math.min(halfWidth, halfHeight) / 2; 1322 context.canvas.drawCircle(vp2px(halfWidth / 2), vp2px(halfHeight / 2), vp2px(radius / 2)); 1323 } 1324 } 1325 1326 export { MyFullDrawModifier }; 1327 ``` 1328 1329 ```ts 1330 // pages/RenderNodeExample.ets 1331 import { RenderNode } from '@kit.ArkUI' 1332 import drawing from '@ohos.graphics.drawing'; 1333 1334 const TEST_TAG = "TEST" 1335 1336 class MyRenderNode extends RenderNode { 1337 draw(context: DrawContext): void { 1338 // 变更前draw在页面返回后不会触发,变更后将会触发 1339 console.info(`${TEST_TAG} RenderNode draw`); 1340 const canvas = context.canvas; 1341 const width = context.sizeInPixel.width; 1342 const height = context.sizeInPixel.height; 1343 1344 const brush = new drawing.Brush(); 1345 brush.setColor(0xff, 0xff, 0x00, 0x00); 1346 canvas.attachBrush(brush); 1347 canvas.drawCircle(width / 2, height / 2, Math.min(width, height) / 2); 1348 canvas.detachBrush(); 1349 } 1350 } 1351 1352 export { MyRenderNode }; 1353 ``` 1354 1355 ```ts 1356 // pages/PageTwo.ets 1357 // 该页面中存在一个按钮,可跳转回主页面,回到主页面后,原有的文字消失 1358 import "ets/pages/Index" 1359 1360 @Entry({ routeName: "pageTwo" }) 1361 @Component 1362 struct PageTwo { 1363 build() { 1364 Column() { 1365 Button('Router replace to index') 1366 .onClick(() => { 1367 this.getUIContext().getRouter().replaceNamedRoute({ name: "myIndex" }); 1368 }) 1369 } 1370 .height('100%') 1371 .width('100%') 1372 .alignItems(HorizontalAlign.Center) 1373 .padding(16) 1374 } 1375 } 1376 ``` 1377 1378## cl.arkui.9 使用RichEditorStyledStringController构造方式的富文本组件支持预上屏功能 1379**访问级别** 1380 1381公开接口 1382 1383**变更原因** 1384 1385使用RichEditorStyledStringController构造方式的富文本(属性字符串富文本)组件需要支持预上屏功能。 1386 1387**变更影响** 1388 1389此变更涉及应用适配。 1390 1391变更前:组件的enablePreviewText接口无法对使用RichEditorStyledStringController构造方式的富文本(属性字符串富文本)生效,属性字符串富文本不支持预上屏。在输入法插入拼音时,组件内不显示拼音内容,onWillChange回调中previewText字段为空。 1392 1393变更后:组件的enablePreviewText接口可以对使用RichEditorStyledStringController构造方式的富文本(属性字符串富文本)生效,属性字符串富文本支持预上屏。在输入法插入拼音时,组件内显示拼音内容,onWillChange回调中previewText字段为实际显示在组件中的拼音内容。 1394 1395| 变更前 | 变更后 | 1396| -------------------- | -------------------- | 1397| |  | 1398 1399**起始API Level**x 1400 1401API 12 1402 1403**变更发生版本** 1404 1405从OpenHarmony SDK 5.1.0.54开始。 1406 1407**变更的接口/组件** 1408 1409富文本组件RichEditor 1410 1411**适配指导** 1412 1413开发者需要根据onWillChange回调中previewText字段是否为空判断此次输入是预上屏输入或正式内容输入,原先在onWillChange回调中做的逻辑变更后需要在判断后再执行。 1414```ts 1415@Entry 1416@Component 1417struct Index { 1418 controller: RichEditorStyledStringController = new RichEditorStyledStringController() 1419 build() { 1420 Column() { 1421 RichEditor({ controller: this.controller }) 1422 .onReady(() => { 1423 this.controller.onContentChanged({ 1424 onWillChange: (value: StyledStringChangeValue) => { 1425 if (typeof value.previewText != 'undefined' && value.previewText.getString() != "") { 1426 // todo 逻辑A 1427 } else { 1428 // todo 逻辑B 1429 } 1430 return true 1431 } 1432 }); 1433 }) 1434 } 1435 } 1436} 1437``` 1438 1439## cl.arkui.10 RichEditor(富文本)组件鼠标右击文本触发onSelectionChange回调变更 1440**访问级别** 1441 1442公开接口 1443 1444**变更原因** 1445 1446富文本组件右击文本时会触发一次短暂的选中然后清除选中区,过程中触发两次onSelectionChange,与实际效果不符。 1447 1448**变更影响** 1449 1450此变更不涉及应用适配。 1451 1452变更前:富文本组件右击文本时会触发一次短暂的选中然后清除选中区,触发选中时回调onSelectionChange范围时选中的内容范围,清除选中时回调onSelectionChange为光标索引。 1453 1454变更后:富文本组件右击文本时不会触发短暂的选中,onSelectionChange只回调一次右击位置的光标索引。 1455 1456**起始API Level** 1457 1458API 12 1459 1460**变更发生版本** 1461 1462从OpenHarmony SDK 5.1.0.54开始。 1463 1464**变更的接口/组件** 1465 1466富文本组件RichEditor 1467 1468**适配指导** 1469 1470不涉及应用适配。 1471 1472## cl.arkui.11 RichEditor(富文本)预上屏候选词替换已上屏内容行为变更 1473 1474**访问级别** 1475 1476公开接口 1477 1478**变更原因** 1479 1480通过输入法点击候选词来替换富文本中已显示的单词,这种替换被视为正式内容的一部分,且能够被富文本的输入相关回调aboutToIMEInput所拦截。 1481 1482**变更影响** 1483 1484此变更涉及应用适配。 1485 1486变更前: 1487当aboutToIMEInput回调返回false时,点击输入法候选词替换已上屏内容,候选词能够正常上屏,不会被拦截。 1488 1489 1490变更后: 1491当aboutToIMEInput回调返回false时,点击输入法候选词替换已上屏内容,候选词不可以上屏,会被拦截。 1492 1493**起始API Level** 1494 1495API 12 1496 1497**变更发生版本** 1498 1499从OpenHarmony SDK 5.1.0.54开始。 1500 1501**变更的接口/组件** 1502 1503RichEditor 1504 1505**适配指导** 1506 1507应用可以根据aboutToIMEInput回调入参中RichEditorInsertValue#previewText是否有值,判断此次插入是否是预上屏内容插入,进而执行对应逻辑。 1508```ts 1509@Entry 1510@Component 1511struct Index { 1512 controller: RichEditorStyledStringController = new RichEditorStyledStringController() 1513 build() { 1514 Column() { 1515 TextArea() 1516 .height("50%") 1517 RichEditor({ controller: this.controller }) 1518 .aboutToIMEInput((value: RichEditorInsertValue) => { 1519 if (value.previewText == "") { 1520 // todo 逻辑A 1521 return true 1522 } else { 1523 // todo 逻辑B 1524 return false 1525 } 1526 }) 1527 } 1528 } 1529} 1530``` 1531 1532## cl.arkui.12 RichEditor(富文本)设置提示文本时鼠标拖动光标回调变更。 1533 1534**访问级别** 1535 1536公开接口 1537 1538**变更原因** 1539 1540当组件设置了提示文本且无内容时,按住鼠标左键拖动会触发onSelect和onSelectionChange异常回调。 1541 1542**变更影响** 1543 1544此变更不涉及应用适配。 1545 1546变更前: 1547当组件设置了提示文本且无内容时,若按住鼠标左键进行拖动操作,将触发onSelect和onSelectionChange异常回调,回调的范围界定为鼠标拖动时所覆盖的提示文本区域。 1548 1549变更后: 1550当组件设置了提示文本且无内容时,若按住鼠标左键进行拖动操作,不触发onSelect和onSelectionChange回调。 1551 1552**起始API Level** 1553 1554API 12 1555 1556**变更发生版本** 1557 1558从OpenHarmony SDK 5.1.0.54开始。 1559 1560**变更的接口/组件** 1561 1562RichEditor 1563 1564**适配指导** 1565 1566默认行为变更,无需适配。 1567 1568## cl.arkui.13 RichEditor(富文本)onDeleteComplete回调变更。 1569 1570**访问级别** 1571 1572公开接口 1573 1574**变更原因** 1575 1576在组件填充内容时,从内容的起始位置向前删除将触发onDeleteComplete回调,而从内容的末尾向后删除则不会触发onDeleteComplete回调,两者的表现不一致。 1577 1578**变更影响** 1579 1580此变更不涉及应用适配。 1581 1582变更前: 1583在组件填充内容时,从内容的末尾向后删除不触发onDeleteComplete回调。 1584 1585变更后: 1586在组件填充内容时,从内容的末尾向后删除触发onDeleteComplete回调。 1587 1588**起始API Level** 1589 1590API 12 1591 1592**变更发生版本** 1593 1594从OpenHarmony SDK 5.1.0.54开始。 1595 1596**变更的接口/组件** 1597 1598RichEditor 1599 1600**适配指导** 1601 1602默认行为变更,无需适配。 1603 1604## cl.arkui.14 RichEditor(富文本)RichEditorTextSpanResult接口返回值变更 1605 1606**访问级别** 1607 1608公开接口 1609 1610**变更原因** 1611 1612在应用添加文本或更新文本样式时,若未指定fontFamily,通过getSpans接口获取的文本信息中,fontFamily将显示默认值"HarmonyOS Sans"。然而,此默认值可能与系统实际应用的字体不一致,从而可能导致应用判断出现偏差。 1613 1614**变更影响** 1615 1616此变更不涉及应用适配。 1617 1618变更前: 1619在应用中添加文本或更新文本样式时,若未指定fontFamily,通过getSpans接口获取的文本信息中,fontFamily将采用默认值"HarmonyOS Sans"。 1620 1621变更后: 1622在应用中添加文本或更新文本样式时,若未指定fontFamily,通过getSpans接口获取的文本信息中,fontFamily为""。 1623 1624**起始API Level** 1625 1626API 12 1627 1628**变更发生版本** 1629 1630从OpenHarmony SDK 5.1.0.54开始。 1631 1632**变更的接口/组件** 1633 1634RichEditor 1635 1636**适配指导** 1637 1638默认行为变更,无需适配。 1639 1640## cl.arkui.15 半模态底部样式最大高度默认避让状态栏安全区 1641 1642**访问级别** 1643 1644公开接口 1645 1646**变更原因** 1647 1648UX规格变更。 1649 1650半模态底部样式最大高度默认避让状态栏安全区。 1651 1652**变更影响** 1653 1654此变更不涉及应用适配。 1655 1656场景1:竖屏下,状态栏隐藏时,半模态底部样式最大高度也需要避让状态栏安全区。 1657 1658- 变更前:状态栏隐藏时,半模态底部样式最大高度距离屏幕上边界8vp,未避让状态栏安全区。 1659- 变更后:API version 18及以后,状态栏隐藏时,半模态底部样式最大高度距离状态栏下边界8vp,避让状态栏安全区。该样式的最大高度为屏幕高度 - (窗口状态栏安全区高度 + 安全间距8vp)。 1660 1661| 变更前 | 变更后 | 1662|------ |--------| 1663||| 1664 1665场景2:横屏下,状态栏不隐藏时,半模态底部样式最大高度也需要避让状态栏安全区。 1666 1667- 变更前:状态栏不隐藏时,半模态底部样式最大高度距离屏幕上边界8vp,未避让状态栏安全区,且与状态栏区域重合。 1668- 变更后:API version 18及以后,状态栏不隐藏时,半模态底部样式最大高度距离状态栏下边界8vp,避让状态栏安全区。该样式的最大高度为屏幕高度 - (窗口状态栏安全区高度 + 安全间距8vp)。 1669 1670| 变更前 | 变更后 | 1671|------ |--------| 1672||| 1673 1674**起始API Level** 1675 167611 1677 1678**变更发生版本** 1679 1680从OpenHarmony SDK 5.1.0.54 版本开始。 1681 1682**变更的接口/组件** 1683 1684bindSheet的LARGE属性 1685 1686**适配指导** 1687 1688若开发者自定义的builder面板内容是固定高度,建议使用100%布局,变更后自定义的内容也可以自动撑满半模态面板。 1689 1690若按变更前的最大高度规格限制的builder内容,需要变更为新规格计算。 1691 1692## cl.arkui.16 sharedTransition在id入参为undefined或空字符串时的行为变更 1693 1694**访问级别** 1695 1696公开接口 1697 1698**变更原因** 1699 1700sharedTransition的id从非空字符串变为空字符串或undefined时,无法实现清空共享元素转场id的效果。 1701 1702**变更影响** 1703 1704此变更涉及应用适配,API18之前不变,API18及以后,发生变更。 1705 1706变更前:sharedTransition的id从非空字符串变为空字符串或undefined时,会维持之前的有效id值。 1707 1708变更后:sharedTransition的id从非空字符串变为空字符串或undefined时,会将共享元素转场id置为空字符串,达到取消sharedTransition匹配的效果。 1709 1710**起始API Level** 1711 1712API 7 1713 1714**变更发生版本** 1715 1716从OpenHarmony SDK 5.1.0.54版本开始。 1717 1718**变更的接口/组件** 1719 1720common.d.ts文件的sharedTransition接口 1721 1722**适配指导** 1723 1724开发者如果希望同一组件的sharedTransition的id维持有效值不变,且开发者已经主动设置id为空字符串或undefined时,需要适配。适配方式为不更改sharedTransition的id,维持之前的有效值不变。其余情况无需适配。 1725 1726## cl.arkui.17 半模态弹簧曲线时长设置默认值 1727 1728**访问级别** 1729 1730公开接口 1731 1732**变更原因** 1733 1734开发者拉起用SheetSize.FIT_CONTENT声明的半模态的同时,立刻变更了半模态面板高度,造成半模态连续做了两个弹簧曲线动效。 1735 1736半模态高度动效是没有设置时长的弹簧曲线,如果对高度值连续做两次动效,那么后一个动效会停止前面所有动效,只执行第二个动效,效果上为跳变现象,体验不佳。 1737 1738**变更影响** 1739 1740此变更不涉及应用适配,API18之前不变,API18及以后,发生变更。 1741 1742变更前:在拉起半模态的过程中同时更改半模态高度,半模态会做两次弹簧曲线动效,第一次动效直接到达终点,第二次动效从起点执行到终点,半模态onAppear和高度回调立即执行。 1743 1744变更后:在拉起半模态的过程中同时更改半模态高度,半模态会做两次弹簧曲线动效,第一次动效和第二次动效都从起点执行到终点,半模态onAppear和高度回调在第一次动效结束后执行。 1745 1746**起始API Level** 1747 1748API 11 1749 1750**变更发生版本** 1751 1752从OpenHarmony SDK 5.1.0.54版本开始。 1753 1754**变更的接口/组件** 1755 1756bindSheet的SheetSize.FIT_CONTENT属性。 1757 1758**适配指导** 1759 1760UX效果调优,应用无需适配。 1761 1762## cl.arkui.18 bindSheet在2in1设备中默认避让窗口安全区 1763 1764**访问级别** 1765 1766公开接口 1767 1768**变更原因** 1769 1770UX规格变更。 1771 1772半模态内容需默认避让窗口安全区,否则会有重叠区域。 1773 1774**变更影响** 1775 1776此变更涉及应用适配,API18之前不变,API18及以后,发生变更。 1777 1778当自由窗口标题栏类型为悬浮标题栏时,需要半模态面板默认避让标题安全区。 1779 1780场景1:半模态居中弹窗样式 1781 1782- 变更前:半模态居中弹窗样式的面板最大高度为窗口高度的90%。 1783- 变更后:API version 18及以后,该样式的最大高度为窗口高度 - (窗口安全区高度 + 安全间距8vp) * 2。 1784 1785| 变更前 | 变更后 | 1786|------ |--------| 1787||| 1788 1789场景2:半模态底部弹窗样式 1790 1791- 变更前:半模态底部弹窗样式的面板最大高度为窗口高度 - 8vp。 1792- 变更后:API version 18及以后,该样式的最大高度为窗口高度 - (窗口安全区高度 + 安全间距8vp)。 1793 1794| 变更前 | 变更后 | 1795|------ |--------| 1796||| 1797 1798**起始API Level** 1799 180011 1801 1802**变更发生版本** 1803 1804从OpenHarmony SDK 5.1.0.54版本开始。 1805 1806**变更的接口/组件** 1807 1808bindSheet的preferType属性 1809 1810**适配指导** 1811 1812若开发者自定义的builder面板内容是固定高度,建议使用100%布局,变更后自定义的内容也可以自动撑满半模态面板。 1813 1814若按变更前的最大高度规格限制的builder内容,需要变更为新规格计算。 1815 1816## cl.arkui.19 XComponent设置为Texture模式使用blendMode接口的行为由不生效变更为正常生效 1817**访问级别** 1818 1819公开接口 1820 1821**变更原因** 1822 1823用户使用XComponent组件并设置为Texture模式时,使用blendMode接口没有效果,不符合接口正常规格,需要变更为blendMode接口正常生效。 1824 1825**变更影响** 1826 1827此变更涉及应用适配。 1828 1829变更前:XComponent组件设置为Texture模式,使用blendMode接口不生效。 1830 1831变更后:XComponent组件设置为Texture模式,使用blendMode接口正常生效。 1832 1833**起始API Level** 1834 1835API 11 1836 1837**变更发生版本** 1838 1839从OpenHarmony SDK 5.1.0.54开始。 1840 1841**变更的接口/组件** 1842 1843common.d.ts文件的blendMode接口。 1844 1845**适配指导** 1846 1847需适配场景: 1848当应用使用XComponent组件并设置为Texture模式(`type`设置为`XComponentType.TEXTURE`)时,使用blendMode接口,可能会出现显示效果变更前后不一致的情况,以下是使用场景示意: 1849 1850```ts 1851@Entry 1852@Component 1853struct Index { 1854 private contextOne: Record<string, () => void> = {}; 1855 private contextTwo: Record<string, () => void> = {}; 1856 1857 build() { 1858 Column() { 1859 Stack() { 1860 XComponent({ 1861 id: 'circle', 1862 type: XComponentType.TEXTURE, 1863 libraryname: 'nativerender' 1864 }).height(50) 1865 .backgroundColor(Color.Transparent) 1866 .onLoad((contextOne?: object | Record<string, () => void>) => { 1867 if (contextOne) { 1868 this.contextOne = contextOne as Record<string, () => void>; 1869 } 1870 }) 1871 1872 XComponent({ 1873 id: 'rect', 1874 type: XComponentType.TEXTURE, 1875 libraryname: 'nativerender' 1876 }).height(50) 1877 .backgroundColor(Color.Transparent) 1878 .onLoad((contextTwo?: object | Record<string, () => void>) => { 1879 if (contextTwo) { 1880 this.contextTwo = contextTwo as Record<string, () => void>; 1881 } 1882 }) 1883 .blendMode(BlendMode.XOR) // 变更后生效,若需保持变更前行为,可使用BlendMode.None入参 1884 } 1885 .height(50) 1886 .onClick(() => { 1887 if (this.contextOne) { 1888 this.contextOne.drawCircle(); 1889 } 1890 if (this.contextTwo) { 1891 this.contextTwo.drawRectangle(); 1892 } 1893 }) 1894 } 1895 .blendMode(BlendMode.SRC_OVER, BlendApplyType.OFFSCREEN) 1896 .width('100%') 1897 .height('100%') 1898 } 1899} 1900``` 1901| 混合类型 | 变更前 | 变更后 | 1902| ------- | - | ---- | 1903| BlendMode.XOR |  |  | 1904| BlendMode.NONE |  |  | 1905 1906应用若需保持变更前行为,XComponent组件上的blendMode接口使用BlendMode.None入参即可。 1907