diff --git a/zh-cn/application-dev/ui/Readme-CN.md b/zh-cn/application-dev/ui/Readme-CN.md index ad9cd062ffac50f87eeb5cc5a6c979155f12d9a2..db50761380a653bc99159cad4e5867d712513f58 100755 --- a/zh-cn/application-dev/ui/Readme-CN.md +++ b/zh-cn/application-dev/ui/Readme-CN.md @@ -196,6 +196,7 @@ - [自定义扩展概述](arkts-user-defined-modifier.md) - [属性修改器 (AttributeModifier)](arkts-user-defined-extension-attributeModifier.md) - [属性更新器 (AttributeUpdater)](arkts-user-defined-extension-attributeUpdater.md) + - [自定义绘制修改器 (DrawModifier)](arkts-user-defined-extension-drawModifier.md) - [使用镜像能力](arkts-mirroring-display.md) - 无障碍与适老化 - [支持无障碍](arkts-universal-attributes-accessibility.md) @@ -231,6 +232,7 @@ - [嵌入ArkTS组件](ndk-embed-arkts-components.md) - [通过XComponent接入无障碍](ndk-accessibility-xcomponent.md) - [自定义绘制](arkts-user-defined-draw.md) + - [查询和操作自定义节点](ndk-node-query-operate.md) - [通过EmbeddedComponent拉起EmbeddedUIExtensionAbility](ndk-embedded-component.md) - UI开发 (兼容JS的类Web开发范式) - [UI开发 (兼容JS的类Web开发范式)概述](ui-js-overview.md) diff --git a/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-builderNode.md b/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-builderNode.md index c20c4bf445a046596c0f61dad039991215e3a7c2..b6961484a16827c4e59e42c6e46d91587d60ac66 100644 --- a/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-builderNode.md +++ b/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-builderNode.md @@ -1000,3 +1000,84 @@ struct Child { } } ``` + +## 查询当前BuilderNode是否解除引用 + +前端节点均绑定有相应的后端实体节点,当节点调用dispose接口解除绑定后,再次调用接口可能会出现crash、返回默认值的情况。 + +从API version 20开始,使用[isDisposed](../reference/apis-arkui/js-apis-arkui-builderNode.md#isdisposed20)接口查询当前BuilderNode对象是否已解除与后端实体节点的引用关系,从而可以在操作节点前检查其有效性,避免潜在风险。 + +```ts +import { NodeController, FrameNode, BuilderNode } from '@kit.ArkUI'; + +@Builder +function buildText() { + Text("Test") + .fontSize(20) + .fontWeight(FontWeight.Bold) +} + +class MyNodeController extends NodeController { + private rootNode: FrameNode | null = null; + private builderNode: BuilderNode<[]> | null = null; + + makeNode(uiContext: UIContext): FrameNode | null { + this.rootNode = new FrameNode(uiContext); + this.rootNode.commonAttribute.width(100).height(100).backgroundColor(Color.Pink); + this.builderNode = new BuilderNode<[]>(uiContext); + this.builderNode.build(wrapBuilder<[]>(buildText)); + + // 挂载BuilderNode + this.rootNode.appendChild(this.builderNode.getFrameNode()); + return this.rootNode; + } + + disposeBuilderNode() { + // 解除BuilderNode与后端实体节点的引用关系 + this.builderNode?.dispose(); + } + + isDisposed() : string { + if (this.builderNode !== null) { + // 查询BuilderNode是否解除引用 + if (this.builderNode.isDisposed()) { + return 'builderNode isDisposed is true'; + } + else { + return 'builderNode isDisposed is false'; + } + } + return 'builderNode is null'; + } +} + +@Entry +@Component +struct Index { + @State text: string = '' + private myNodeController: MyNodeController = new MyNodeController(); + + build() { + Column({ space: 4 }) { + NodeContainer(this.myNodeController) + Button('BuilderNode dispose') + .onClick(() => { + this.myNodeController.disposeBuilderNode(); + this.text = ''; + }) + .width(200) + .height(50) + Button('BuilderNode isDisposed') + .onClick(() => { + this.text = this.myNodeController.isDisposed(); + }) + .width(200) + .height(50) + Text(this.text) + .fontSize(25) + } + .width('100%') + .height('100%') + } +} +``` \ No newline at end of file diff --git a/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-frameNode.md b/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-frameNode.md index 52499e7f7dbaf0b18f02243410c9b3315ea68877..381c6ff497795cacc77c1aa53b3cda5fca369015 100644 --- a/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-frameNode.md +++ b/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-frameNode.md @@ -324,6 +324,82 @@ struct Index { } ``` +## 使用moveTo移动命令式节点 + +使用[moveTo](../reference/apis-arkui/js-apis-arkui-frameNode.md#moveto18)接口可以将FrameNode节点移动到新的父节点下,从而按需改变节点树结构。 + +> **说明:** +> +> 当前FrameNode如果不可修改,抛出异常信息。 +> +> 目标父节点为[typeNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#typenode12)时会校验子组件类型或个数,不满足抛出异常信息,限制情况请查看[typeNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#typenode12)描述。 +> +> 当前不支持对无组件类型的命令式节点进行移动。 +> +> 当前仅支持以下类型的[TypedFrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#typedframenode12)进行移动操作:[Stack](../reference/apis-arkui/js-apis-arkui-frameNode.md#stack12)、[XComponent](../reference/apis-arkui/js-apis-arkui-frameNode.md#xcomponent12)。对于其他类型的节点,移动操作不会生效。 +> +> 当前仅支持根节点为以下类型组件的[BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md#buildernode-1)进行移动操作:[Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md)、[XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md)、[EmbeddedComponent](../reference/apis-arkui/arkui-ts/ts-container-embedded-component.md)。对于其他类型的组件,移动操作不会生效。 + +```ts +import { FrameNode, NodeController, UIContext, typeNode } from '@kit.ArkUI'; + +class MyNodeController extends NodeController { + uiContext: UIContext | null = null; + rootNode: FrameNode | null = null; + rowNode: FrameNode | null = null; + stackNode1: FrameNode | null = null; + stackNode2: FrameNode | null = null; + stackNode3: FrameNode | null = null; + + makeNode(uiContext: UIContext): FrameNode | null { + this.uiContext = uiContext; + this.rootNode = new FrameNode(uiContext); + + const row = typeNode.createNode(this.uiContext, 'Row'); + row.initialize({ space: 10 }); + this.rowNode = row; + this.rootNode.appendChild(this.rowNode); + + const stack1 = typeNode.createNode(this.uiContext, 'Stack'); + stack1.commonAttribute.width(50).height(50).backgroundColor(Color.Pink); + this.stackNode1 = stack1; + this.rowNode?.appendChild(this.stackNode1); + const stack2 = typeNode.createNode(this.uiContext, 'Stack'); + stack2.commonAttribute.width(50).height(50).backgroundColor(Color.Yellow); + this.stackNode2 = stack2; + this.rowNode?.appendChild(this.stackNode2); + const stack3 = typeNode.createNode(this.uiContext, 'Stack'); + stack3.commonAttribute.width(50).height(50).backgroundColor(Color.Green); + this.stackNode3 = stack3; + this.rowNode?.appendChild(this.stackNode3); + + return this.rootNode; + } +} + +@Entry +@Component +struct Index { + private myNodeController1: MyNodeController = new MyNodeController() + private myNodeController2: MyNodeController = new MyNodeController() + + build() { + Column({ space: 20 }) { + NodeContainer(this.myNodeController1) + NodeContainer(this.myNodeController2) + Button("move") + .onClick(() => { + this.myNodeController1.stackNode1?.moveTo(this.myNodeController2.rowNode, 2); + }) + } + .height('100%') + .width('100%') + } +} +``` + +![moveToDemo](figures/moveToDemo.gif) + ## 设置节点通用属性和事件回调 FrameNode提供了[commonAttribute](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonattribute12)和[commonEvent](../reference/apis-arkui/js-apis-arkui-frameNode.md#commonevent12)两个对象用于设置节点的[通用属性](../reference/apis-arkui/arkui-ts/ts-component-general-attributes.md)和[设置事件回调](../reference/apis-arkui/arkui-ts/ts-uicommonevent.md)。 @@ -1233,6 +1309,75 @@ struct Index { } ``` +## 查询当前FrameNode是否解除引用 + +前端节点均绑定有相应的后端实体节点,当节点调用dispose接口解除绑定后,再次调用接口可能会出现crash、返回默认值的情况。 + +从API version 20开始,使用[isDisposed](../reference/apis-arkui/js-apis-arkui-frameNode.md#isdisposed20)接口查询当前FrameNode对象是否已解除与后端实体节点的引用关系,从而可以在操作节点前检查其有效性,避免潜在风险。 + +```ts +import { NodeController, FrameNode } from '@kit.ArkUI'; + +class MyNodeController extends NodeController { + private rootNode: FrameNode | null = null; + + makeNode(uiContext: UIContext): FrameNode | null { + this.rootNode = new FrameNode(uiContext); + this.rootNode.commonAttribute.width(100).height(100).backgroundColor(Color.Pink); + + return this.rootNode; + } + + disposeFrameNode() { + // 解除当前FrameNode对象对实体FrameNode节点的引用关系 + this.rootNode?.dispose(); + } + + isDisposed() : string { + if (this.rootNode !== null) { + // 查询FrameNode是否解除引用 + if (this.rootNode.isDisposed()) { + return 'frameNode isDisposed is true'; + } + else { + return 'frameNode isDisposed is false'; + } + } + return 'frameNode is null'; + } +} + +@Entry +@Component +struct Index { + @State text: string = '' + private myNodeController: MyNodeController = new MyNodeController(); + + build() { + Column({ space: 4 }) { + NodeContainer(this.myNodeController) + Button('FrameNode dispose') + .onClick(() => { + this.myNodeController.disposeFrameNode(); + this.text = ''; + }) + .width(200) + .height(50) + Button('FrameNode isDisposed') + .onClick(() => { + this.text = this.myNodeController.isDisposed(); + }) + .width(200) + .height(50) + Text(this.text) + .fontSize(25) + } + .width('100%') + .height('100%') + } +} +``` + ## FrameNode的数据懒加载能力 提供[NodeAdapter](../reference/apis-arkui/js-apis-arkui-frameNode.md#nodeadapter12)对象替代ArkTS侧的LazyForEach功能,提供自定义节点的数据懒加载功能,实现按需迭代数据。 @@ -1437,3 +1582,273 @@ struct ListNodeTest { } } ``` + +## 查询LazyForEach中的FrameNode节点信息 + +如果FrameNode子节点中包含[LazyForEach](../reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md)节点,[getChild](../reference/apis-arkui/js-apis-arkui-frameNode.md#getchild15)接口支持指定子节点展开模式[ExpandMode](../reference/apis-arkui/js-apis-arkui-frameNode.md#expandmode15),以不同展开模式获取子节点。 + +当前支持如下子节点展开模式: + +- ExpandMode.NOT_EXPAND:表示不展开当前FrameNode的子节点。如果FrameNode子节点中包含LazyForEach节点,获取在主节点树上的子节点时,不展开当前FrameNode的子节点。子节点序列号按在主节点树上的子节点计算。 +- ExpandMode.EXPAND:表示展开当前FrameNode的子节点。如果FrameNode子节点中包含LazyForEach节点,获取任何子节点时,展开当前FrameNode的子节点。子节点序列号按所有子节点计算。 +- ExpandMode.LAZY_EXPAND:表示按需展开当前FrameNode的子节点。如果FrameNode子节点中包含LazyForEach节点,获取在主节点树上的子节点时,不展开当前FrameNode的子节点;获取不在主节点树上的子节点时,展开当前FrameNode的子节点。子节点序列号按所有子节点计算。 + +可以使用[getFirstChildIndexWithoutExpand](../reference/apis-arkui/js-apis-arkui-frameNode.md#getfirstchildindexwithoutexpand15)和[getLastChildIndexWithoutExpand](../reference/apis-arkui/js-apis-arkui-frameNode.md#getlastchildindexwithoutexpand15)获取当前节点第一个和最后一个在主节点树上的子节点的序列号,其中子节点序列号按所有子节点计算。 + +```ts +import { NodeController, FrameNode, UIContext, BuilderNode, ExpandMode, LengthUnit } from '@kit.ArkUI'; + +const TEST_TAG: string = "FrameNode "; + +// BasicDataSource实现了IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新 +class BasicDataSource implements IDataSource { + private listeners: DataChangeListener[] = []; + private originDataArray: string[] = []; + + public totalCount(): number { + return 0; + } + + public getData(index: number): string { + return this.originDataArray[index]; + } + + // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + console.info('add listener'); + this.listeners.push(listener); + } + } + + // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + console.info('remove listener'); + this.listeners.splice(pos, 1); + } + } + + // 通知LazyForEach组件需要重载所有子组件 + notifyDataReload(): void { + this.listeners.forEach(listener => { + listener.onDataReloaded(); + }) + } + + // 通知LazyForEach组件需要在index对应索引处添加子组件 + notifyDataAdd(index: number): void { + this.listeners.forEach(listener => { + listener.onDataAdd(index); + // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]); + }) + } + + // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 + notifyDataChange(index: number): void { + this.listeners.forEach(listener => { + listener.onDataChange(index); + // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]); + }) + } + + // 通知LazyForEach组件需要在index对应索引处删除该子组件 + notifyDataDelete(index: number): void { + this.listeners.forEach(listener => { + listener.onDataDelete(index); + // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]); + }) + } + + // 通知LazyForEach组件将from索引和to索引处的子组件进行交换 + notifyDataMove(from: number, to: number): void { + this.listeners.forEach(listener => { + listener.onDataMove(from, to); + // 写法2:listener.onDatasetChange( + // [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]); + }) + } + + notifyDatasetChange(operations: DataOperation[]): void { + this.listeners.forEach(listener => { + listener.onDatasetChange(operations); + }) + } +} + +class MyDataSource extends BasicDataSource { + private dataArray: string[] = [] + + public totalCount(): number { + return this.dataArray.length; + } + + public getData(index: number): string { + return this.dataArray[index]; + } + + public addData(index: number, data: string): void { + this.dataArray.splice(index, 0, data); + this.notifyDataAdd(index); + } + + public pushData(data: string): void { + this.dataArray.push(data); + this.notifyDataAdd(this.dataArray.length - 1); + } +} + +class Params { + data: MyDataSource | null = null; + scroller: Scroller | null = null; + constructor(data: MyDataSource, scroller: Scroller) { + this.data = data; + this.scroller = scroller; + } +} + +@Builder +function buildData(params: Params) { + List({ scroller: params.scroller }) { + LazyForEach(params.data, (item: string) => { + ListItem() { + Column() { + Text(item) + .fontSize(20) + .onAppear(() => { + console.log(TEST_TAG + " node appear: " + item) + }) + .backgroundColor(Color.Pink) + .margin({ + top: 30, + bottom: 30, + left: 10, + right: 10 + }) + } + } + .id(item) + }, (item: string) => item) + } + .cachedCount(5) + .listDirection(Axis.Horizontal) +} + +class MyNodeController extends NodeController { + private rootNode: FrameNode | null = null; + private uiContext: UIContext | null = null; + private data: MyDataSource = new MyDataSource(); + private scroller: Scroller = new Scroller(); + + makeNode(uiContext: UIContext): FrameNode | null { + this.uiContext = uiContext; + for (let i = 0; i <= 20; i++) { + this.data.pushData(`N${i}`); + } + const params: Params = new Params(this.data, this.scroller); + const dataNode: BuilderNode<[Params]> = new BuilderNode(uiContext); + dataNode.build(wrapBuilder<[Params]>(buildData), params); + this.rootNode = dataNode.getFrameNode(); + const scrollToIndexOptions: ScrollToIndexOptions = { + extraOffset: { + value: 20, unit: LengthUnit.VP + } + }; + this.scroller.scrollToIndex(6, true, ScrollAlign.START, scrollToIndexOptions); + return this.rootNode; + } + + getFirstChildIndexWithoutExpand() { + console.log(`${TEST_TAG} getFirstChildIndexWithoutExpand: ${this.rootNode!.getFirstChildIndexWithoutExpand()}`); + } + + getLastChildIndexWithoutExpand() { + console.log(`${TEST_TAG} getLastChildIndexWithoutExpand: ${this.rootNode!.getLastChildIndexWithoutExpand()}`); + } + + getChildWithNotExpand() { + const childNode = this.rootNode!.getChild(3, ExpandMode.NOT_EXPAND); + console.log(TEST_TAG + " getChild(3, ExpandMode.NOT_EXPAND): " + childNode!.getId()); + if (childNode!.getId() === "N9") { + console.log(TEST_TAG + " getChild(3, ExpandMode.NOT_EXPAND) result: success."); + } else { + console.log(TEST_TAG + " getChild(3, ExpandMode.NOT_EXPAND) result: fail."); + } + } + + getChildWithExpand() { + const childNode = this.rootNode!.getChild(3, ExpandMode.EXPAND); + console.log(TEST_TAG + " getChild(3, ExpandMode.EXPAND): " + childNode!.getId()); + if (childNode!.getId() === "N3") { + console.log(TEST_TAG + " getChild(3, ExpandMode.EXPAND) result: success."); + } else { + console.log(TEST_TAG + " getChild(3, ExpandMode.EXPAND) result: fail."); + } + } + + getChildWithLazyExpand() { + const childNode = this.rootNode!.getChild(3, ExpandMode.LAZY_EXPAND); + console.log(TEST_TAG + " getChild(3, ExpandMode.LAZY_EXPAND): " + childNode!.getId()); + if (childNode!.getId() === "N3") { + console.log(TEST_TAG + " getChild(3, ExpandMode.LAZY_EXPAND) result: success."); + } else { + console.log(TEST_TAG + " getChild(3, ExpandMode.LAZY_EXPAND) result: fail."); + } + } +} + +@Entry +@Component +struct Index { + private myNodeController: MyNodeController = new MyNodeController(); + private scroller: Scroller = new Scroller(); + + build() { + Scroll(this.scroller) { + Column({ space: 8 }) { + Column() { + Text("This is a NodeContainer.") + .textAlign(TextAlign.Center) + .borderRadius(10) + .backgroundColor(0xFFFFFF) + .width('100%') + .fontSize(16) + NodeContainer(this.myNodeController) + .borderWidth(1) + .width(300) + .height(100) + } + + Button("getFirstChildIndexWithoutExpand") + .width(300) + .onClick(() => { + this.myNodeController.getFirstChildIndexWithoutExpand(); + }) + Button("getLastChildIndexWithoutExpand") + .width(300) + .onClick(() => { + this.myNodeController.getLastChildIndexWithoutExpand(); + }) + Button("getChildWithNotExpand") + .width(300) + .onClick(() => { + this.myNodeController.getChildWithNotExpand(); + }) + Button("getChildWithExpand") + .width(300) + .onClick(() => { + this.myNodeController.getChildWithExpand(); + }) + Button("getChildWithLazyExpand") + .width(300) + .onClick(() => { + this.myNodeController.getChildWithLazyExpand(); + }) + } + .width("100%") + } + .scrollable(ScrollDirection.Vertical) // 滚动方向纵向 + } +} +``` \ No newline at end of file diff --git a/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-renderNode.md b/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-renderNode.md index f66556cba01f7aed9f1bce2905c895a409c730ad..6a735a3930f35a99079962e7614160bf06127ee1 100644 --- a/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-renderNode.md +++ b/zh-cn/application-dev/ui/arkts-user-defined-arktsNode-renderNode.md @@ -559,3 +559,77 @@ struct Index { } } ``` + +## 查询当前RenderNode是否解除引用 + +前端节点均绑定有相应的后端实体节点,当节点调用dispose接口解除绑定后,再次调用接口可能会出现crash、返回默认值的情况。 + +从API version 20开始,使用[isDisposed](../reference/apis-arkui/js-apis-arkui-renderNode.md#isdisposed20)接口查询当前RenderNode对象是否已解除与后端实体节点的引用关系,从而可以在操作节点前检查其有效性,避免潜在风险。 + +```ts +import { NodeController, FrameNode, RenderNode } from '@kit.ArkUI'; + +class MyNodeController extends NodeController { + private rootNode: FrameNode | null = null; + private renderNode: RenderNode | null = null; + + makeNode(uiContext: UIContext): FrameNode | null { + this.rootNode = new FrameNode(uiContext); + this.renderNode = new RenderNode(); + this.renderNode.size = { width: 300, height: 300 }; + this.renderNode.backgroundColor = 0xffd5d5d5; + + // 挂载RenderNode + this.rootNode.getRenderNode()?.appendChild(this.renderNode); + return this.rootNode; + } + + disposeRenderNode() { + // 解除RenderNode与后端实体节点的引用关系 + this.renderNode?.dispose(); + } + + isDisposed() : string { + if (this.renderNode !== null) { + // 查询RenderNode是否解除引用 + if (this.renderNode.isDisposed()) { + return 'renderNode isDisposed is true'; + } + else { + return 'renderNode isDisposed is false'; + } + } + return 'renderNode is null'; + } +} + +@Entry +@Component +struct Index { + @State text: string = '' + private myNodeController: MyNodeController = new MyNodeController(); + + build() { + Column({ space: 4 }) { + NodeContainer(this.myNodeController) + Button('RenderNode dispose') + .onClick(() => { + this.myNodeController.disposeRenderNode(); + this.text = ''; + }) + .width(200) + .height(50) + Button('RenderNode isDisposed') + .onClick(() => { + this.text = this.myNodeController.isDisposed(); + }) + .width(200) + .height(50) + Text(this.text) + .fontSize(25) + } + .width('100%') + .height('100%') + } +} +``` \ No newline at end of file diff --git a/zh-cn/application-dev/ui/arkts-user-defined-extension-drawModifier.md b/zh-cn/application-dev/ui/arkts-user-defined-extension-drawModifier.md new file mode 100644 index 0000000000000000000000000000000000000000..82c9808041ade4fec0930553a06be3174b966776 --- /dev/null +++ b/zh-cn/application-dev/ui/arkts-user-defined-extension-drawModifier.md @@ -0,0 +1,288 @@ +# 自定义绘制修改器 (DrawModifier) + +## 概述 + +当某些组件本身的绘制内容不满足需求时,可使用组件自定义绘制功能,在原有组件基础上部分绘制、或者全部自行绘制,以达到预期效果。例如:独特的按钮形状、文字和图像混合的图标等。组件自定义绘制提供了自定义绘制修改器DrawModifier,来实现更自由的组件绘制。 + +## 使用DrawModifier接口 + +```ts +declare class DrawModifier { + + drawBehind?(drawContext: DrawContext): void; + + drawContent?(drawContext: DrawContext): void; + + drawFront?(drawContext: DrawContext): void; + + drawForeground?(drawContext: DrawContext): void; + + invalidate(): void; +} +``` + +DrawModifier可设置前景(drawForeground)、内容前景(drawFront)、内容(drawContent)和内容背景(drawBehind)的绘制方法,开发者需要重载这些方法,并通过[Canvas](arkts-drawing-customization-on-canvas.md)的接口进行自定义绘制。自定义绘制层级图如下所示。 + +![](figures/drawModifier.png) + +DrawModifier还提供主动触发重绘的方法invalidate,该接口开发者无需也无法重载,调用会触发所绑定组件的重绘。 + +> **说明:** +> +> 每个DrawModifier实例只能设置到一个组件上,禁止进行重复设置。 +> +> drawContent方法会替换组件原本的内容绘制函数。 +> +> drawForeground方法从API version 20开始支持。 +> +> NDK的自定义绘制能力和示例请参考[自定义绘制](./arkts-user-defined-draw.md)。 + +## 通过drawFront、drawContent、drawBehind进行自定义绘制 + +通过drawFront、drawContent、drawBehind接口,在内容前景、内容和内容背景三个层级上对Text组件进行了自定义绘制,从而按需改变组件的绘制效果。 + +```ts +// xxx.ets +import { drawing } from '@kit.ArkGraphics2D'; +import { AnimatorResult } from '@kit.ArkUI'; + +class MyFullDrawModifier extends DrawModifier { + public scaleX: number = 1; + public scaleY: number = 1; + uiContext: UIContext; + + constructor(uiContext: UIContext) { + super(); + this.uiContext = uiContext; + } + + drawBehind(context: DrawContext): void { + const brush = new drawing.Brush(); + brush.setColor({ + alpha: 255, + red: 161, + green: 10, + blue: 33 + }); + context.canvas.attachBrush(brush); + const halfWidth = context.size.width / 2; + const halfHeight = context.size.width / 2; + context.canvas.drawRect({ + left: this.uiContext.vp2px(halfWidth - 50 * this.scaleX), + top: this.uiContext.vp2px(halfHeight - 50 * this.scaleY), + right: this.uiContext.vp2px(halfWidth + 50 * this.scaleX), + bottom: this.uiContext.vp2px(halfHeight + 50 * this.scaleY) + }); + } + + drawContent(context: DrawContext): void { + const brush = new drawing.Brush(); + brush.setColor({ + alpha: 255, + red: 23, + green: 169, + blue: 141 + }); + context.canvas.attachBrush(brush); + const halfWidth = context.size.width / 2; + const halfHeight = context.size.width / 2; + context.canvas.drawRect({ + left: this.uiContext.vp2px(halfWidth - 30 * this.scaleX), + top: this.uiContext.vp2px(halfHeight - 30 * this.scaleY), + right: this.uiContext.vp2px(halfWidth + 30 * this.scaleX), + bottom: this.uiContext.vp2px(halfHeight + 30 * this.scaleY) + }); + } + + drawFront(context: DrawContext): void { + const brush = new drawing.Brush(); + brush.setColor({ + alpha: 255, + red: 39, + green: 135, + blue: 217 + }); + context.canvas.attachBrush(brush); + const halfWidth = context.size.width / 2; + const halfHeight = context.size.width / 2; + const radiusScale = (this.scaleX + this.scaleY) / 2; + context.canvas.drawCircle(this.uiContext.vp2px(halfWidth), this.uiContext.vp2px(halfHeight), this.uiContext.vp2px(20 * radiusScale)); + } +} + +class MyFrontDrawModifier extends DrawModifier { + public scaleX: number = 1; + public scaleY: number = 1; + uiContext: UIContext; + + constructor(uiContext: UIContext) { + super(); + this.uiContext = uiContext; + } + + drawFront(context: DrawContext): void { + const brush = new drawing.Brush(); + brush.setColor({ + alpha: 255, + red: 39, + green: 135, + blue: 217 + }); + context.canvas.attachBrush(brush); + const halfWidth = context.size.width / 2; + const halfHeight = context.size.width / 2; + const radiusScale = (this.scaleX + this.scaleY) / 2; + context.canvas.drawCircle(this.uiContext.vp2px(halfWidth), this.uiContext.vp2px(halfHeight), this.uiContext.vp2px(20 * radiusScale)); + } +} + +@Entry +@Component +struct DrawModifierExample { + private fullModifier: MyFullDrawModifier = new MyFullDrawModifier(this.getUIContext()); + private frontModifier: MyFrontDrawModifier = new MyFrontDrawModifier(this.getUIContext()); + private drawAnimator: AnimatorResult | undefined = undefined; + @State modifier: DrawModifier = new MyFrontDrawModifier(this.getUIContext()); + private count = 0; + + create() { + let self = this; + this.drawAnimator = this.getUIContext().createAnimator({ + duration: 1000, + easing: 'ease', + delay: 0, + fill: 'forwards', + direction: 'normal', + iterations: 1, + begin: 0, + end: 2 + }); + this.drawAnimator.onFrame = (value: number) => { + console.log('frame value =', value); + const tempModifier = self.modifier as MyFullDrawModifier | MyFrontDrawModifier; + tempModifier.scaleX = Math.abs(value - 1); + tempModifier.scaleY = Math.abs(value - 1); + self.modifier.invalidate(); + }; + } + + build() { + Column() { + Row() { + Text('Text组件绑定drawModifier') + .width(100) + .height(100) + .margin(10) + .backgroundColor(Color.Gray) + .onClick(() => { + const tempModifier = this.modifier as MyFullDrawModifier | MyFrontDrawModifier; + tempModifier.scaleX -= 0.1; + tempModifier.scaleY -= 0.1; + }) + .drawModifier(this.modifier) + } + + Row() { + Button('create') + .width(100) + .height(100) + .margin(10) + .backgroundColor(0xFF2787D9) + .onClick(() => { + this.create(); + }) + Button('play') + .width(100) + .height(100) + .margin(10) + .backgroundColor(0xFF2787D9) + .onClick(() => { + if (this.drawAnimator) { + this.drawAnimator.play(); + } + }) + Button('changeModifier') + .width(100) + .height(100) + .margin(10) + .backgroundColor(0xFF2787D9) + .onClick(() => { + this.count += 1; + if (this.count % 2 === 1) { + console.log('change to full modifier'); + this.modifier = this.fullModifier; + } else { + console.log('change to front modifier'); + this.modifier = this.frontModifier; + } + }) + } + } + .width('100%') + .height('100%') + } +} +``` + +![drawModifier.gif](figures/drawModifier.gif) + +## 通过drawForeground进行自定义绘制 + +通过drawForeground接口,在组件前景层级上对Column组件进行了自定义绘制,从而改变组件前景的绘制效果。 + +```ts +// xxx.ets +import { drawing } from '@kit.ArkGraphics2D'; + +class MyForegroundDrawModifier extends DrawModifier { + public scaleX: number = 3; + public scaleY: number = 3; + uiContext: UIContext; + + constructor(uiContext: UIContext) { + super(); + this.uiContext = uiContext; + } + + drawForeground(context: DrawContext): void { + const brush = new drawing.Brush(); + brush.setColor({ + alpha: 255, + red: 0, + green: 50, + blue: 100 + }); + context.canvas.attachBrush(brush); + const halfWidth = context.size.width / 2; + const halfHeight = context.size.width / 2; + context.canvas.drawRect({ + left: this.uiContext.vp2px(halfWidth - 30 * this.scaleX), + top: this.uiContext.vp2px(halfHeight - 30 * this.scaleY), + right: this.uiContext.vp2px(halfWidth + 30 * this.scaleX), + bottom: this.uiContext.vp2px(halfHeight + 30 * this.scaleY) + }); + } +} + +@Entry +@Component +struct DrawModifierExample { + private foregroundModifier: MyForegroundDrawModifier = new MyForegroundDrawModifier(this.getUIContext()); + + build() { + Column() { + Text('此文本是子节点') + .fontSize(36) + .width('100%') + .height('100%') + .textAlign(TextAlign.Center) + } + .margin(100) + .height(300) + .backgroundColor(0x87CEEB) + .drawModifier(this.foregroundModifier) + } +} + +``` +![drawForeground.png](figures/drawForeground.png) \ No newline at end of file diff --git a/zh-cn/application-dev/ui/arkts-user-defined-modifier.md b/zh-cn/application-dev/ui/arkts-user-defined-modifier.md index 46719696e54e20c20035f7320ea2ddd9e3171ab3..343c2b5b6d9e06e7e37f44be9cec269267ead173 100644 --- a/zh-cn/application-dev/ui/arkts-user-defined-modifier.md +++ b/zh-cn/application-dev/ui/arkts-user-defined-modifier.md @@ -1,9 +1,12 @@ # 自定义扩展能力概述 -ArkUI框架提供一系列基于Modifier的自定义扩展能力,通过与UI分离的方式,对已有UI组件的属性、手势、内容进行扩展修改,以满足开发者在不改变UI组件底层实现的情况下,快速调整UI组件外观、行为等需求。自定义扩展包括[AttributeModifier](arkts-user-defined-extension-attributeModifier.md)、[GestureModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-gesture-modifier.md#gesturemodifier-1)、[DrawModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-draw-modifier.md#drawmodifier-1)等。其中[AttributeModifier](arkts-user-defined-extension-attributeModifier.md)和[AttributeUpdater](arkts-user-defined-extension-attributeUpdater.md)允许开发者通过自定义类设置属性,扩展了属性设置的实现方式,能够与组件属性设置方式混合使用。例如,使用自定义扩展处理特定的逻辑或复杂交互,使用组件设置方式处理简单静态属性等。 +ArkUI框架提供一系列基于Modifier的自定义扩展能力,通过与UI分离的方式,对已有UI组件的属性、手势、内容进行扩展修改,以满足开发者在不改变UI组件底层实现的情况下,快速调整UI组件外观、行为等需求。自定义扩展包括[AttributeModifier](arkts-user-defined-extension-attributeModifier.md)、[GestureModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-gesture-modifier.md#gesturemodifier-1)、[DrawModifier](arkts-user-defined-extension-drawModifier.md)等。其中[AttributeModifier](arkts-user-defined-extension-attributeModifier.md)和[AttributeUpdater](arkts-user-defined-extension-attributeUpdater.md)允许开发者通过自定义类设置属性,扩展了属性设置的实现方式,能够与组件属性设置方式混合使用。例如,使用自定义扩展处理特定的逻辑或复杂交互,使用组件设置方式处理简单静态属性等。 ## AttributeModifier 声明式语法引入的[@Styles](../ui/state-management/arkts-style.md)和[@Extend](../ui/state-management/arkts-extend.md)两个装饰器,虽然可以解决复用相同自定义样式的问题,但是使用场景存在一定局限性,如无法跨文件导出等。为此,ArkUI引入了`AttributeModifier`机制,可以通过Modifier对象动态修改属性。与@Styles和@Extend相比,AttributeModifier提供了更强的能力和灵活性,且在持续完善全量的属性和事件设置能力,因此推荐优先使用AttributeModifier。 ## AttributeUpdater -`AttributeUpdater`是一个特殊的`AttributeModifier`,除了继承`AttributeModifier`的能力,还提供了获取属性对象的能力。通过属性对象可以不经过状态变量,直接更新对应属性。开发者可以通过`AttributeUpdater`实现自定义的更新策略,进一步提高属性更新的性能。 \ No newline at end of file +`AttributeUpdater`是一个特殊的`AttributeModifier`,除了继承`AttributeModifier`的能力,还提供了获取属性对象的能力。通过属性对象可以不经过状态变量,直接更新对应属性。开发者可以通过`AttributeUpdater`实现自定义的更新策略,进一步提高属性更新的性能。 + +## DrawModifier +`DrawModifier`提供自定义绘制能力。当某些组件本身的绘制内容不满足需求时,开发者可以使用`DrawModifier`在原有组件基础上部分绘制、或者全部自行绘制,以达到预期效果。 \ No newline at end of file diff --git a/zh-cn/application-dev/ui/arkts-user-defined-node.md b/zh-cn/application-dev/ui/arkts-user-defined-node.md index 067b08379994bdc3ff13c6daf12b6cd1971b5132..494bee6b6849f2d078544f55762a09785b55513a 100644 --- a/zh-cn/application-dev/ui/arkts-user-defined-node.md +++ b/zh-cn/application-dev/ui/arkts-user-defined-node.md @@ -28,3 +28,7 @@ RenderNode作为轻量级的渲染节点,仅提供了设置渲染相关属性 ## 自定义声明式节点 (BuilderNode) BuilderNode通过无状态的UI方法[全局自定义构建函数](../ui/state-management/arkts-builder.md#全局自定义构建函数)@Builder生成组件树,组件树内的节点为系统组件。适用于需要基于系统能力创建特定组件树与其他自定义节点进行混合显示的场景。相比较系统组件,BuilderNode具备预创建的优势,可以控制开始创建的时间。由于持有实体节点对象,因此可以同步实现节点的复用,通过占位节点结合FrameNode、RenderNode的节点操作能力控制显示位置。 + +## 设置自定义节点跨语言属性 + +ArkUI支持在前端使用ArkTS语言创建命令式节点,即FrameNode节点,也可以在Native侧使用C语言创建命令式节点,并且可以混合使用两类节点构建页面。针对上述场景,ArkUI提供命令式节点跨语言属性设置功能,即使用ArkTS语言创建的命令式节点,可以在Native侧进行属性设置。使用C语言创建的节点,可以在ArkTS侧进行属性设置。 \ No newline at end of file diff --git a/zh-cn/application-dev/ui/figures/drawForeground.png b/zh-cn/application-dev/ui/figures/drawForeground.png new file mode 100644 index 0000000000000000000000000000000000000000..395b28b3219881c8128758f5925eec28742f6785 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawForeground.png differ diff --git a/zh-cn/application-dev/ui/figures/drawModifier.gif b/zh-cn/application-dev/ui/figures/drawModifier.gif new file mode 100644 index 0000000000000000000000000000000000000000..5cf651d4168e4f28fda3c7ddf5b76fd6fa84693d Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawModifier.gif differ diff --git a/zh-cn/application-dev/ui/figures/drawModifier.png b/zh-cn/application-dev/ui/figures/drawModifier.png new file mode 100644 index 0000000000000000000000000000000000000000..08e148aef4a01457a568e9d783c5debb3776e55c Binary files /dev/null and b/zh-cn/application-dev/ui/figures/drawModifier.png differ diff --git a/zh-cn/application-dev/ui/figures/moveToDemo.gif b/zh-cn/application-dev/ui/figures/moveToDemo.gif new file mode 100644 index 0000000000000000000000000000000000000000..8e5f07d13669ae0a38f584580bb6d99c46a168ad Binary files /dev/null and b/zh-cn/application-dev/ui/figures/moveToDemo.gif differ diff --git a/zh-cn/application-dev/ui/figures/moveToNativeDemo.gif b/zh-cn/application-dev/ui/figures/moveToNativeDemo.gif new file mode 100644 index 0000000000000000000000000000000000000000..981a93adccc85d166ad6b2a94bcafeb34e89b5d7 Binary files /dev/null and b/zh-cn/application-dev/ui/figures/moveToNativeDemo.gif differ diff --git a/zh-cn/application-dev/ui/ndk-build-ui-overview.md b/zh-cn/application-dev/ui/ndk-build-ui-overview.md index 9df49cd66f8946f9eb55726c64923c356ddaa490..4ec3d573dc408fbbe4bef6e316940b36e90852c1 100644 --- a/zh-cn/application-dev/ui/ndk-build-ui-overview.md +++ b/zh-cn/application-dev/ui/ndk-build-ui-overview.md @@ -67,6 +67,7 @@ ArkUI NDK接口能力主要包括: | [嵌入ArkTS组件](ndk-embed-arkts-components.md) | 介绍了如何在Native侧构建带有ArkTS组件的界面。 | | [通过XComponent接入无障碍](ndk-accessibility-xcomponent.md) | 介绍了通过XComponent接入UI平台的三方系统如何对接无障碍。 | | [自定义绘制](arkts-user-defined-draw.md) | 介绍了如何使用自定义绘制能力,实现自定义内容的绘制。 | +| [查询和操作自定义节点](ndk-node-query-operate.md) | 介绍了如何对自定义节点进行查询和操作。 | | [通过EmbeddedComponent拉起EmbeddedUIExtensionAbility](ndk-embedded-component.md) | 介绍了如何在Native侧通过EmbeddedComponent拉起EmbeddedUIExtensionAbility。主要用于有进程隔离需求的模块化开发场景。 | | [使用文本](ndk-styled-string.md) | 介绍了Text组件对字体引擎文本的渲染显示和如何监听输入框文本相关的事件。 | diff --git a/zh-cn/application-dev/ui/ndk-node-query-operate.md b/zh-cn/application-dev/ui/ndk-node-query-operate.md new file mode 100644 index 0000000000000000000000000000000000000000..2f7919ea4725b86cfaa847f638993d72f1246237 --- /dev/null +++ b/zh-cn/application-dev/ui/ndk-node-query-operate.md @@ -0,0 +1,426 @@ +# 查询和操作自定义节点 + +NDK提供一系列节点查询、遍历、操作能力,通过使用以下接口,开发者可以高效地访问和操控节点。 + +## 通过用户id获取节点信息 + +使用[OH_ArkUI_NodeUtils_GetAttachedNodeHandleById](../reference/apis-arkui/_ark_u_i___native_module.md#oh_arkui_nodeutils_getattachednodehandlebyid)接口,可以通过用户设置的id获取目标节点的指针。 + +1. ArkTS侧接入Native组件。 + ```ts + // GetNodeById.ets + import nativeNode from 'libentry.so'; + import { NodeContent } from '@kit.ArkUI'; + + @Entry + @Component + struct GetNodeById { + private rootSlot = new NodeContent(); + + aboutToAppear(): void { + nativeNode.createNativeRoot(this.rootSlot); + } + + build() { + Scroll() { + Column({ space: 15 }) { + Column() { + ContentSlot(this.rootSlot) + } + } + .width('100%') + }.scrollBarColor(Color.Transparent) + } + } + ``` + +2. 新建`GetNodeByIdExample.h`文件,在其中创建Text节点并设置id属性,通过OH_ArkUI_NodeUtils_GetAttachedNodeHandleById接口拿到节点。 + ```c + // GetNodeByIdExample.h + #ifndef MYAPPLICATION_GETNODEBYID_H + #define MYAPPLICATION_GETNODEBYID_H + + #include "ArkUINode.h" + #include + + namespace NativeModule { + + std::shared_ptr CreateGetNodeByIdExample() { + auto nodeAPI = NativeModuleInstance::GetInstance()->GetNativeNodeAPI(); + + // 创建传入事件节点结构体 + struct A { + ArkUI_NodeHandle node; + }; + A* a = new A; + + // 创建根节点Scroll + ArkUI_NodeHandle scroll = nodeAPI->createNode(ARKUI_NODE_SCROLL); + ArkUI_NumberValue length_value[] = {{.f32 = 480}}; + ArkUI_AttributeItem length_item = {length_value, sizeof(length_value) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(scroll, NODE_WIDTH, &length_item); + ArkUI_NumberValue length_value1[] = {{.f32 = 650}}; + ArkUI_AttributeItem length_item1 = {length_value1, sizeof(length_value1) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(scroll, NODE_HEIGHT, &length_item1); + ArkUI_AttributeItem scroll_id = {.string = "Scroll_CAPI"}; + nodeAPI->setAttribute(scroll, NODE_ID, &scroll_id); + + // 创建Column + ArkUI_NodeHandle column = nodeAPI->createNode(ARKUI_NODE_COLUMN); + ArkUI_NumberValue value[] = {480}; + ArkUI_AttributeItem item = {value, sizeof(value) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(column, NODE_WIDTH, &item); + ArkUI_NumberValue column_bc[] = {{.u32 = 0xFFF00BB}}; + ArkUI_AttributeItem column_item = {column_bc, 1}; + nodeAPI->setAttribute(column, NODE_BACKGROUND_COLOR, &column_item); + ArkUI_AttributeItem column_id = {.string = "Column_CAPI"}; + nodeAPI->setAttribute(column, NODE_ID, &column_id); + + // 创建Text + ArkUI_NodeHandle text0 = nodeAPI->createNode(ARKUI_NODE_TEXT); + ArkUI_NumberValue text_width[] = {300}; + ArkUI_AttributeItem text_item0 = {text_width, sizeof(text_width) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(text0, NODE_WIDTH, &text_item0); + ArkUI_NumberValue text_height[] = {50}; + ArkUI_AttributeItem text_item1 = {text_height, sizeof(text_height) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(text0, NODE_HEIGHT, &text_item1); + ArkUI_AttributeItem text_item = {.string = "示例Text节点"}; + nodeAPI->setAttribute(text0, NODE_TEXT_CONTENT, &text_item); + ArkUI_NumberValue margin[] = {10}; + ArkUI_AttributeItem item_margin = {margin, sizeof(margin) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(text0, NODE_MARGIN, &item_margin); + ArkUI_AttributeItem text0_id = {.string = "Text0_CAPI"}; + nodeAPI->setAttribute(text0, NODE_ID, &text0_id); + a->node = text0; + + // 创建Row + ArkUI_NodeHandle row0 = nodeAPI->createNode(ARKUI_NODE_ROW); + ArkUI_NumberValue width_value[] = {{.f32=330}}; + ArkUI_AttributeItem width_item = {width_value, sizeof(width_value) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(row0, NODE_WIDTH, &width_item); + nodeAPI->setAttribute(row0, NODE_HEIGHT, &text_item1); + nodeAPI->setAttribute(row0, NODE_MARGIN, &item_margin); + + // 创建Button + ArkUI_NodeHandle bt0 = nodeAPI->createNode(ARKUI_NODE_BUTTON); + ArkUI_NumberValue btn_width[] = {150}; + ArkUI_AttributeItem btn_item0 = {btn_width, sizeof(btn_width) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(bt0, NODE_WIDTH, &btn_item0); + nodeAPI->setAttribute(bt0, NODE_HEIGHT, &text_item1); + nodeAPI->setAttribute(bt0, NODE_MARGIN, &item_margin); + ArkUI_AttributeItem bt0_item = {.string = "GetAttachedNodeHandleById"}; + nodeAPI->setAttribute(bt0, NODE_BUTTON_LABEL, &bt0_item); + nodeAPI->registerNodeEvent(bt0, NODE_ON_CLICK, 0, a); + + // 注册事件 + auto onClick = [](ArkUI_NodeEvent *event) { + ArkUI_NodeHandle node = OH_ArkUI_NodeEvent_GetNodeHandle(event); + auto nodeAPI = NativeModuleInstance::GetInstance()->GetNativeNodeAPI(); + + if (OH_ArkUI_NodeEvent_GetTargetId(event) == 0) { // GetAttachedNodeHandleById + A* a = (A*)OH_ArkUI_NodeEvent_GetUserData(event); + ArkUI_NodeHandle node = nullptr; + auto res = OH_ArkUI_NodeUtils_GetAttachedNodeHandleById("Text0_CAPI", &node); + if (node == a->node) { + OH_LOG_Print(LOG_APP, LOG_INFO, 0xFF00, "GetNodeByIdExample", "get Text0_CAPI success"); + } else { + OH_LOG_Print(LOG_APP, LOG_ERROR, 0xFF00, "GetNodeByIdExample", "get Text0_CAPI failed"); + } + } + }; + nodeAPI->registerNodeEventReceiver(onClick); + + // 节点添加 + nodeAPI->addChild(scroll, column); + nodeAPI->addChild(column, text0); + nodeAPI->addChild(column, row0); + nodeAPI->addChild(row0, bt0); + + return std::make_shared(scroll); + } + } // namespace NativeModule + + #endif //MYAPPLICATION_GETNODEBYID_H + ``` + +3. 在`NativeEntry.cpp`中,挂载Native节点。 + ```c + // NativeEntry.cpp + + + #include + #include + #include + #include "NativeEntry.h" + #include "GetNodeByIdExample.h" + + + namespace NativeModule { + + + napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1] = {nullptr}; + + + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + + // 获取NodeContent + ArkUI_NodeContentHandle contentHandle; + OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle); + NativeEntry::GetInstance()->SetContentHandle(contentHandle); + + + // 创建节点 + auto node = CreateGetNodeByIdExample(); + + + // 保持Native侧对象到管理类中,维护生命周期。 + NativeEntry::GetInstance()->SetRootNode(node); + return nullptr; + } + + + napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { + // 从管理类中释放Native侧对象。 + NativeEntry::GetInstance()->DisposeRootNode(); + return nullptr; + } + + + } // namespace NativeModule + ``` + +4. 运行程序,点击按钮,打印节点获取成功信息。 + +## 移动节点 + +使用[OH_ArkUI_NodeUtils_MoveTo](../reference/apis-arkui/_ark_u_i___native_module.md#oh_arkui_nodeutils_moveto)接口,可以将Native节点移动到新的父节点下,从而按需改变节点树结构。 + +> **说明:** +> +> 当前仅支持以下类型的[ArkUI_NodeType](../reference/apis-arkui/_ark_u_i___native_module.md#arkui_nodetype)进行移动操作:ARKUI_NODE_STACK、ARKUI_NODE_XCOMPONENT、ARKUI_NODE_EMBEDDED_COMPONENT。对于其他类型的节点,移动操作不会生效。 + +1. ArkTS侧接入Native组件。 + ```ts + // MoveTo.ets + import nativeNode from 'libentry.so'; + import { NodeContent } from '@kit.ArkUI'; + + @Entry + @Component + struct MoveTo { + private rootSlot = new NodeContent(); + + aboutToAppear(): void { + nativeNode.createNativeRoot(this.rootSlot); + } + + build() { + Scroll() { + Column({ space: 15 }) { + Column() { + ContentSlot(this.rootSlot) + } + } + .width('100%') + }.scrollBarColor(Color.Transparent) + } + } + ``` + +2. 新建`MoveTo.h`文件,在其中创建Stack节点,通过OH_ArkUI_NodeUtils_MoveTo接口移动Stack节点。 + ```c + // MoveToExample.h + #ifndef MYAPPLICATION_MOVETO_H + #define MYAPPLICATION_MOVETO_H + + #include "ArkUINode.h" + #include + + namespace NativeModule { + + std::shared_ptr CreateMoveToExample() { + auto nodeAPI = NativeModuleInstance::GetInstance()->GetNativeNodeAPI(); + + // 创建传入事件节点结构体 + struct A { + ArkUI_NodeHandle node; + ArkUI_NodeHandle targetParent; + }; + A* a = new A; + + // 创建根节点Scroll + ArkUI_NodeHandle scroll = nodeAPI->createNode(ARKUI_NODE_SCROLL); + ArkUI_NumberValue length_value[] = {{.f32 = 480}}; + ArkUI_AttributeItem length_item = {length_value, sizeof(length_value) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(scroll, NODE_WIDTH, &length_item); + ArkUI_NumberValue length_value1[] = {{.f32 = 650}}; + ArkUI_AttributeItem length_item1 = {length_value1, sizeof(length_value1) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(scroll, NODE_HEIGHT, &length_item1); + ArkUI_AttributeItem scroll_id = {.string = "Scroll_CAPI"}; + nodeAPI->setAttribute(scroll, NODE_ID, &scroll_id); + + // 创建Column + ArkUI_NodeHandle column = nodeAPI->createNode(ARKUI_NODE_COLUMN); + ArkUI_NumberValue value[] = {480}; + ArkUI_AttributeItem item = {value, sizeof(value) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(column, NODE_WIDTH, &item); + ArkUI_AttributeItem column_id = {.string = "Column_CAPI"}; + nodeAPI->setAttribute(column, NODE_ID, &column_id); + + // 创建Row + ArkUI_NodeHandle row0 = nodeAPI->createNode(ARKUI_NODE_ROW); + ArkUI_NumberValue width_value[] = {{.f32=330}}; + ArkUI_AttributeItem width_item = {width_value, sizeof(width_value) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(row0, NODE_WIDTH, &width_item); + nodeAPI->setAttribute(row0, NODE_HEIGHT, &text_item1); + nodeAPI->setAttribute(row0, NODE_MARGIN, &item_margin); + + ArkUI_NodeHandle row1 = nodeAPI->createNode(ARKUI_NODE_ROW); + nodeAPI->setAttribute(row1, NODE_WIDTH, &width_item); + nodeAPI->setAttribute(row1, NODE_HEIGHT, &text_item1); + nodeAPI->setAttribute(row1, NODE_MARGIN, &item_margin); + a->targetParent = row1; + + ArkUI_NodeHandle row2 = nodeAPI->createNode(ARKUI_NODE_ROW); + nodeAPI->setAttribute(row2, NODE_WIDTH, &width_item); + nodeAPI->setAttribute(row2, NODE_HEIGHT, &text_item1); + nodeAPI->setAttribute(row2, NODE_MARGIN, &item_margin); + + // 创建Stack + ArkUI_NodeHandle stack0 = nodeAPI->createNode(ARKUI_NODE_STACK); + ArkUI_NumberValue stack_value[] = {{.f32=50}}; + ArkUI_AttributeItem stack_item1 = {stack_value, sizeof(width_value) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(stack0, NODE_WIDTH, &stack_item1); + nodeAPI->setAttribute(stack0, NODE_HEIGHT, &stack_item1); + ArkUI_NumberValue stack_bc[] = {{.u32 = 0xFFFFB6C1}}; + ArkUI_AttributeItem stack_item2 = {stack_bc, 1}; + nodeAPI->setAttribute(stack0, NODE_BACKGROUND_COLOR, &stack_item2); + a->node = stack0; + + ArkUI_NodeHandle stack1 = nodeAPI->createNode(ARKUI_NODE_STACK); + nodeAPI->setAttribute(stack1, NODE_WIDTH, &stack_item1); + nodeAPI->setAttribute(stack1, NODE_HEIGHT, &stack_item1); + ArkUI_NumberValue stack_bc1[] = {{.u32 = 0xFF6495ED}}; + ArkUI_AttributeItem stack_item3 = {stack_bc1, 1}; + nodeAPI->setAttribute(stack1, NODE_BACKGROUND_COLOR, &stack_item3); + + ArkUI_NodeHandle stack2 = nodeAPI->createNode(ARKUI_NODE_STACK); + nodeAPI->setAttribute(stack2, NODE_WIDTH, &stack_item1); + nodeAPI->setAttribute(stack2, NODE_HEIGHT, &stack_item1); + ArkUI_NumberValue stack_bc2[] = {{.u32 = 0xFF90EE90}}; + ArkUI_AttributeItem stack_item4 = {stack_bc2, 1}; + nodeAPI->setAttribute(stack2, NODE_BACKGROUND_COLOR, &stack_item4); + + ArkUI_NodeHandle stack3 = nodeAPI->createNode(ARKUI_NODE_STACK); + nodeAPI->setAttribute(stack3, NODE_WIDTH, &stack_item1); + nodeAPI->setAttribute(stack3, NODE_HEIGHT, &stack_item1); + nodeAPI->setAttribute(stack3, NODE_BACKGROUND_COLOR, &stack_item2); + + ArkUI_NodeHandle stack4 = nodeAPI->createNode(ARKUI_NODE_STACK); + nodeAPI->setAttribute(stack4, NODE_WIDTH, &stack_item1); + nodeAPI->setAttribute(stack4, NODE_HEIGHT, &stack_item1); + nodeAPI->setAttribute(stack4, NODE_BACKGROUND_COLOR, &stack_item3); + + ArkUI_NodeHandle stack5 = nodeAPI->createNode(ARKUI_NODE_STACK); + nodeAPI->setAttribute(stack5, NODE_WIDTH, &stack_item1); + nodeAPI->setAttribute(stack5, NODE_HEIGHT, &stack_item1); + nodeAPI->setAttribute(stack5, NODE_BACKGROUND_COLOR, &stack_item4); + + // 创建Button + ArkUI_NodeHandle bt0 = nodeAPI->createNode(ARKUI_NODE_BUTTON); + ArkUI_NumberValue btn_width[] = {150}; + ArkUI_AttributeItem btn_item0 = {btn_width, sizeof(btn_width) / sizeof(ArkUI_NumberValue)}; + nodeAPI->setAttribute(bt0, NODE_WIDTH, &btn_item0); + nodeAPI->setAttribute(bt0, NODE_HEIGHT, &text_item1); + nodeAPI->setAttribute(bt0, NODE_MARGIN, &item_margin); + ArkUI_AttributeItem bt0_item = {.string = "MoveTo"}; + nodeAPI->setAttribute(bt0, NODE_BUTTON_LABEL, &bt0_item); + nodeAPI->registerNodeEvent(bt0, NODE_ON_CLICK, 0, a); + + // 注册事件 + auto onClick = [](ArkUI_NodeEvent *event) { + ArkUI_NodeHandle node = OH_ArkUI_NodeEvent_GetNodeHandle(event); + auto nodeAPI = NativeModuleInstance::GetInstance()->GetNativeNodeAPI(); + + if (OH_ArkUI_NodeEvent_GetTargetId(event) == 0) { // MoveTo + A* a = (A*)OH_ArkUI_NodeEvent_GetUserData(event); + auto res = OH_ArkUI_NodeUtils_MoveTo(a->node, a->targetParent, 2); + } + }; + nodeAPI->registerNodeEventReceiver(onClick); + + // 节点添加 + nodeAPI->addChild(scroll, column); + nodeAPI->addChild(column, row0); + nodeAPI->addChild(column, row1); + nodeAPI->addChild(column, row2); + nodeAPI->addChild(row0, stack0); + nodeAPI->addChild(row0, stack1); + nodeAPI->addChild(row0, stack2); + nodeAPI->addChild(row1, stack3); + nodeAPI->addChild(row1, stack4); + nodeAPI->addChild(row1, stack5); + nodeAPI->addChild(row2, bt0); + + return std::make_shared(scroll); + } + } // namespace NativeModule + + #endif //MYAPPLICATION_MOVETO_H + ``` + +3. 在`NativeEntry.cpp`中,挂载Native节点。 + ```c + // NativeEntry.cpp + + + #include + #include + #include + #include "NativeEntry.h" + #include "MoveToExample.h" + + + namespace NativeModule { + + + napi_value CreateNativeRoot(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1] = {nullptr}; + + + napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + + + // 获取NodeContent + ArkUI_NodeContentHandle contentHandle; + OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle); + NativeEntry::GetInstance()->SetContentHandle(contentHandle); + + + // 创建节点 + auto node = CreateMoveToExample(); + + + // 保持Native侧对象到管理类中,维护生命周期。 + NativeEntry::GetInstance()->SetRootNode(node); + return nullptr; + } + + + napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) { + // 从管理类中释放Native侧对象。 + NativeEntry::GetInstance()->DisposeRootNode(); + return nullptr; + } + + + } // namespace NativeModule + ``` + +4. 运行程序,点击按钮,Stack节点会移动到目标位置。 + +![moveToNativeDemo](figures/moveToNativeDemo.gif) \ No newline at end of file diff --git a/zh-cn/application-dev/website.md b/zh-cn/application-dev/website.md index 03fcadee253654dec903ef6823981134f07ed134..dae8fc91cd38d8f06d97e357d49750c5de6b760f 100644 --- a/zh-cn/application-dev/website.md +++ b/zh-cn/application-dev/website.md @@ -557,6 +557,7 @@ - [自定义扩展概述](ui/arkts-user-defined-modifier.md) - [属性修改器 (AttributeModifier)](ui/arkts-user-defined-extension-attributeModifier.md) - [属性更新器 (AttributeUpdater)](ui/arkts-user-defined-extension-attributeUpdater.md) + - [自定义绘制修改器 (DrawModifier)](ui/arkts-user-defined-extension-drawModifier.md) - [使用镜像能力](ui/arkts-mirroring-display.md) - 无障碍与适老化 - [支持无障碍](ui/arkts-universal-attributes-accessibility.md) @@ -592,6 +593,7 @@ - [嵌入ArkTS组件](ui/ndk-embed-arkts-components.md) - [通过XComponent接入无障碍](ui/ndk-accessibility-xcomponent.md) - [自定义绘制](ui/arkts-user-defined-draw.md) + - [查询和操作自定义节点](ui/ndk-node-query-operate.md) - [通过EmbeddedComponent拉起EmbeddedUIExtensionAbility](ui/ndk-embedded-component.md) - UI开发 (兼容JS的类Web开发范式) - [UI开发 (兼容JS的类Web开发范式)概述](ui/ui-js-overview.md)