• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![addComponentContent_before](figures/backgroundEffect_before.png)
20
21变更后:backgroundEffect通过modifier使用时单位为vp。<br/>
22![addComponentContent_after](figures//backgroundEffect_after.png)
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![demoRenderNodeRotation](figures/demoRenderNodeRotation.png)
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>![light_mode](figures/light_mode1.jpg)                             | 浅色模式拉起。<br>![light_mode](figures/light_mode1.jpg)                           |
587|                                                                                      |
588| 切换深色时,无法使用资源文件触发UI的更新。<br>![light_mode](figures/light_mode1.jpg) | 切换深色时,可以使用资源文件触发UI的更新。<br>![dark_mode](figures/dark_mode1.jpg) |
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>![light_mode](figures/light_mode1.jpg) | 深色模式拉起。<br>![dark_mode](figures/dark_mode1.jpg) |
654| 切换深色。<br>![light_mode](figures/light_mode1.jpg)     | 切换浅色。<br>![dark_mode](figures/dark_mode1.jpg)     |
655
656## cl.arkui.8 命令式节点跨页面复用行为变更
657
658**访问级别**
659
660公开接口
661
662**变更原因**
663
664当使用router.replacerouter.backrouter.clear接口进行页面跳转时,原页面将被销毁,页面上的所有节点将被标记为InDestroying,无法在后续的流程中进行布局和绘制。由于命令式节点无法清除InDestroying标志位,因此在新页面上复用这些节点时,无法显示更新后的内容。
665
666**变更影响**
667
668此变更涉及应用适配。
669
670以下生命周期接口的行为将会受到影响。如果开发者在这些生命周期接口中实现了业务代码,由于变更前后的不同,通过BuilderNode进行跨页面复用时,生命周期接口的触发情况存在差异。若想保持原有的业务行为,则需要进行相应的适配。
671
672| 模块            | 变更前                                                                                                                                                                                                                                                                                                                                                                          | 变更后                                                                                                                                                                                                                                                                                                                                                                        |
673| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
674| FrameNode       | 使用router.replacerouter.backrouter.clear发生页面跳转后,被复用的FrameNode无法在新页面中进行布局和绘制,onMeasure、onLayout和onDraw生命周期函数不触发                                                                                                                                                                                                                       | 使用router.replacerouter.backrouter.clear发生页面跳转后,被复用的FrameNode能够在新页面中进行布局和绘制,onMeasure、onLayout和onDraw生命周期函数将会触发。                                                                                                                                                                                                                 |
675| RenderNode      | 使用router.replacerouter.backrouter.clear发生页面跳转后,被复用的RenderNode无法在新页面中进行绘制,draw回调接口不触发。                                                                                                                                                                                                                                                     | 使用router.replacerouter.backrouter.clear发生页面跳转后,被复用的RenderNode能够在新页面中进行绘制,draw回调接口将会触发。                                                                                                                                                                                                                                                 |
676| CustomSpan      | 使用router.replacerouter.backrouter.clear发生页面跳转后,被复用的CustomSpan无法在新页面中进行布局和绘制,onMeasure和onDraw生命周期函数不触发。                                                                                                                                                                                                                              | 使用router.replacerouter.backrouter.clear发生页面跳转后,被复用的CustomSpan能够在新页面中进行布局和绘制,onMeasure和onDraw生命周期函数将会触发。                                                                                                                                                                                                                          |
677| CustomComponent | 使用router.replacerouter.backrouter.clear发生页面跳转后,当自定义组件CustomComponent用于BuilderNode进行复用时,自定义组件无法在新页面中进行布局,onMeasureSize生命周期函数不触发。                                                                                                                                                                                          | 使用router.replacerouter.backrouter.clear发生页面跳转后,当自定义组件CustomComponent用于BuilderNode进行复用时,自定义组件能够在新页面中进行布局,onMeasureSize生命周期函数将会触发。                                                                                                                                                                                      |
678| LazyForEach     | 使用router.replacerouter.backrouter.clear发生页面跳转后,当LazyForEach用于BuilderNode进行复用时,BuilderNode无法在新页面中进行布局,getData不触发。                                                                                                                                                                                                                         | 使用router.replacerouter.backrouter.clear发生页面跳转后,当LazyForEach用于BuilderNode进行复用时,BuilderNode能够在新页面中进行布局,getData将会触发。                                                                                                                                                                                                                     |
679| DrawModifier    | 使用router.replacerouter.backrouter.clear发生页面跳转后,在BuilderNode中使用的DrawModifier,由于BuilderNode及其子节点无法在新页面中进行布局和绘制,drawFront、drawContent和drawBehind生命周期函数不触发。                                                                                                                                                                   | 使用router.replacerouter.backrouter.clear发生页面跳转后,在BuilderNode中使用的DrawModifier,BuilderNode及其子节点能够在新页面中进行布局和绘制,drawFront、drawContent和drawBehind生命周期函数将会触发。                                                                                                                                                                   |
680| C API           | 使用router.replacerouter.backrouter.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.replacerouter.backrouter.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| ![](figures/unSupportPreviewText.gif)| ![](figures/supportPreviewText.gif)  |
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|![verticalSheet1](figures/verticalSheet1.png)|![verticalSheet2](figures/verticalSheet2.png)|
1664
1665场景2:横屏下,状态栏不隐藏时,半模态底部样式最大高度也需要避让状态栏安全区。
1666
1667- 变更前:状态栏不隐藏时,半模态底部样式最大高度距离屏幕上边界8vp,未避让状态栏安全区,且与状态栏区域重合。
1668- 变更后:API version 18及以后,状态栏不隐藏时,半模态底部样式最大高度距离状态栏下边界8vp,避让状态栏安全区。该样式的最大高度为屏幕高度 - (窗口状态栏安全区高度 + 安全间距8vp)。
1669
1670| 变更前 | 变更后 |
1671|------ |--------|
1672|![horizontalSheet1](figures/horizontalSheet1.jpg)|![horizontalSheet2](figures/horizontalSheet2.jpg)|
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|![sheetCenter1](figures/sheetCenter1.png)|![sheetCenter2](figures/sheetCenter2.png)|
1788
1789场景2:半模态底部弹窗样式
1790
1791- 变更前:半模态底部弹窗样式的面板最大高度为窗口高度 - 8vp。
1792- 变更后:API version 18及以后,该样式的最大高度为窗口高度 - (窗口安全区高度 + 安全间距8vp)。
1793
1794| 变更前 | 变更后 |
1795|------ |--------|
1796|![sheetBottom1](figures/sheetBottom1.png)|![sheetBottom2](figures/sheetBottom2.png)|
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 | ![demoBlendModeXor](figures/demoBlendModeNone.png) | ![demoBlendModeXor](figures/demoBlendModeXor.png) |
1904| BlendMode.NONE  | ![demoBlendModeNone](figures/demoBlendModeNone.png) | ![demoBlendModeNone](figures/demoBlendModeNone.png) |
1905
1906应用若需保持变更前行为,XComponent组件上的blendMode接口使用BlendMode.None入参即可。
1907