1# 同层渲染 2<!--Kit: ArkWeb--> 3<!--Subsystem: Web--> 4<!--Owner: @ding-xin88--> 5<!--Designer: @LongLie--> 6<!--Tester: @ghiker--> 7<!--Adviser: @HelloCrease--> 8 9在系统中,应用可以使用Web组件加载Web网页。当非系统框架的UI组件功能或性能不如系统组件时,可使用同层渲染技术,通过ArkUI组件渲染这些组件(简称为同层组件)。 10 11## 使用场景 12### Web网页 13小程序的地图组件,可以使用ArkUI的XComponent组件渲染来提升性能。小程序的输入框组件,可以使用ArkUI的TextInput组件渲染,达到与系统应用一致的输入体验。 14- 在网页侧,应用开发者可将<embed>、<object>的网页UI组件(简称为同层标签),按一定规则进行同层渲染,详细规格见同层渲染规格小节。 15 16- 在应用侧,应用开发者可以通过Web组件的同层渲染事件上报接口,感知到H5同层标签的生命周期以及输入事件,进行同层渲染组件的相应业务逻辑处理。 17 18- 在应用侧,应用开发者可以使用ArkUI的NodeContainer等接口,构建H5同层标签对应的同层渲染组件。可支持同层渲染的ArkUI常用组件包括:[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/arkts-basic-components-web.md)。具体规格可参见[同层渲染规格小节](#规格约束)。 19 20### 三方UI框架 21Flutter提供了PlatformView与Texture抽象组件,这些组件可使用系统组件渲染,用来支持Flutter组件功能不足的部分。Weex2.0框架的Camera、Video和Canvas组件可以使用系统组件渲染,以增强功能和性能。 22 23- 在三方框架页面侧,由于Flutter、Weex等三方框架不在操作系统范围内,本文不列举可被同层渲染的三方框架UI组件的范围与使用方式。 24 25- 在应用侧,应用开发者可以使用ArkUI的NodeContainer等接口,构建三方框架同层标签对应的同层渲染组件。可支持同层渲染的ArkUI常用组件包括:[TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/arkts-basic-components-web.md)。具体规格可参见[同层渲染规格](#规格约束)。 26 27## 整体架构 28ArkWeb同层渲染特性主要提供两种能力:同层标签生命周期和事件命中转发处理。 29 30同层标签生命周期主要关联前端标签(<embed>/<object>),同时命中到同层标签的事件会被上报到开发者侧,由开发者分发到对应组件树。整体框架如下图所示: 31 32**图1** 同层渲染整体架构 33 34 35 36## 规格约束 37### 可被同层渲染的ArkUI组件 38 39以下规格对Web网页和三方框架场景均生效。 40 41**支持的组件范围:** 42 43- 基础组件:[AlphabetIndexer](../reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md), [Blank](../reference/apis-arkui/arkui-ts/ts-basic-components-blank.md), [Button](../reference/apis-arkui/arkui-ts/ts-basic-components-button.md), [CalendarPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-calendarpicker.md), [Checkbox](../reference/apis-arkui/arkui-ts/ts-basic-components-checkbox.md), [CheckboxGroup](../reference/apis-arkui/arkui-ts/ts-basic-components-checkboxgroup.md), [ContainerSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-containerspan.md), [DataPanel](../reference/apis-arkui/arkui-ts/ts-basic-components-datapanel.md), [DatePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-datepicker.md), [Divider](../reference/apis-arkui/arkui-ts/ts-basic-components-divider.md), [Gauge](../reference/apis-arkui/arkui-ts/ts-basic-components-gauge.md), [Hyperlink](../reference/apis-arkui/arkui-ts/ts-container-hyperlink.md), [Image](../reference/apis-arkui/arkui-ts/ts-basic-components-image.md), [ImageAnimator](../reference/apis-arkui/arkui-ts/ts-basic-components-imageanimator.md), [ImageSpan](../reference/apis-arkui/arkui-ts/ts-basic-components-imagespan.md), [LoadingProgress](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md), [Marquee](../reference/apis-arkui/arkui-ts/ts-basic-components-marquee.md), [PatternLock](../reference/apis-arkui/arkui-ts/ts-basic-components-patternlock.md), [Progress](../reference/apis-arkui/arkui-ts/ts-basic-components-progress.md), [QRCode](../reference/apis-arkui/arkui-ts/ts-basic-components-qrcode.md), [Radio](../reference/apis-arkui/arkui-ts/ts-basic-components-radio.md), [Rating](../reference/apis-arkui/arkui-ts/ts-basic-components-rating.md), [Refresh](../reference/apis-arkui/arkui-ts/ts-container-refresh.md), [ScrollBar](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Search](../reference/apis-arkui/arkui-ts/ts-basic-components-search.md), [Span](../reference/apis-arkui/arkui-ts/ts-basic-components-span.md), [Select](../reference/apis-arkui/arkui-ts/ts-basic-components-select.md), [Slider](../reference/apis-arkui/arkui-ts/ts-basic-components-slider.md), [Text](../reference/apis-arkui/arkui-ts/ts-basic-components-text.md), [TextArea](../reference/apis-arkui/arkui-ts/ts-basic-components-textarea.md), [TextClock](../reference/apis-arkui/arkui-ts/ts-basic-components-textclock.md), [TextInput](../reference/apis-arkui/arkui-ts/ts-basic-components-textinput.md), [TextPicker](../reference/apis-arkui/arkui-ts/ts-basic-components-textpicker.md), [TextTimer](../reference/apis-arkui/arkui-ts/ts-basic-components-texttimer.md), [TimePicker](../reference/apis-arkui/arkui-ts/ts-basic-components-timepicker.md), [Toggle](../reference/apis-arkui/arkui-ts/ts-basic-components-toggle.md) 44 45- 容器类组件:[Badge](../reference/apis-arkui/arkui-ts/ts-container-badge.md), [Column](../reference/apis-arkui/arkui-ts/ts-container-column.md), [ColumnSplit](../reference/apis-arkui/arkui-ts/ts-container-columnsplit.md), [Counter](../reference/apis-arkui/arkui-ts/ts-container-counter.md), [Flex](../reference/apis-arkui/arkui-ts/ts-container-flex.md), [GridCol](../reference/apis-arkui/arkui-ts/ts-container-gridcol.md), [GridRow](../reference/apis-arkui/arkui-ts/ts-container-gridrow.md), [Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md), [GridItem](../reference/apis-arkui/arkui-ts/ts-container-griditem.md),[List](../reference/apis-arkui/arkui-ts/ts-container-list.md), [ListItem](../reference/apis-arkui/arkui-ts/ts-container-listitem.md), [ListItemGroup](../reference/apis-arkui/arkui-ts/ts-container-listitemgroup.md), [RelativeContainer](../reference/apis-arkui/arkui-ts/ts-container-relativecontainer.md), [Row](../reference/apis-arkui/arkui-ts/ts-container-row.md), [RowSplit](../reference/apis-arkui/arkui-ts/ts-container-rowsplit.md), [Scroll](../reference/apis-arkui/arkui-ts/ts-container-scroll.md), [Stack](../reference/apis-arkui/arkui-ts/ts-container-stack.md), [Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md), [Tabs](../reference/apis-arkui/arkui-ts/ts-container-tabs.md), [TabContent](../reference/apis-arkui/arkui-ts/ts-container-tabcontent.md), [NodeContainer](../reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md), [SideBarContainer](../reference/apis-arkui/arkui-ts/ts-container-sidebarcontainer.md), [Stepper](../reference/apis-arkui/arkui-ts/ts-basic-components-stepper.md), [StepperItem](../reference/apis-arkui/arkui-ts/ts-basic-components-stepperitem.md), [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md), [FlowItem](../reference/apis-arkui/arkui-ts/ts-container-flowitem.md) 46 47- 自绘制类组件:[XComponent](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md), [Canvas](../reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md), [Video](../reference/apis-arkui/arkui-ts/ts-media-components-video.md), [Web](../reference/apis-arkweb/arkts-basic-components-web.md) 48 49- 命令式自定义绘制节点:[BuilderNode](../reference/apis-arkui/js-apis-arkui-builderNode.md), [ComponentContent](../reference/apis-arkui/js-apis-arkui-ComponentContent.md), [ContentSlot](../reference/apis-arkui/arkui-ts/ts-components-contentSlot.md), [FrameNode](../reference/apis-arkui/js-apis-arkui-frameNode.md), [Graphics](../reference/apis-arkui/js-apis-arkui-graphics.md), [NodeController](../reference/apis-arkui/js-apis-arkui-nodeController.md), [RenderNode](../reference/apis-arkui/js-apis-arkui-renderNode.md), [XComponentNode](../reference/apis-arkui/js-apis-arkui-xcomponentNode.md), [AttributeUpdater](../reference/apis-arkui/js-apis-arkui-AttributeUpdater.md), [CAPI](../reference/apis-arkui/capi-arkui-nativemodule.md)(支持同层渲染的组件范围同ArkTS) 50 51**支持的组件通用属性与事件:** 52 53- 不支持的通用属性:[分布式迁移标识](../reference/apis-arkui/arkui-ts/ts-universal-attributes-restoreId.md),[特效绘制合并](../reference/apis-arkui/arkui-ts/ts-universal-attributes-use-effect.md)。 54 55- 其他未明确标注不支持的属性与事件及组件能力,均默认支持。 56 57### Web网页的同层渲染标签 58此规格仅针对Web网页,不适用于三方框架场景。 59 60如果应用需要在Web组件加载的网页中使用同层渲染,需要按照以下规格将网页中的<embed>、<object>标签指定为同层渲染组件。 61 62**支持的H5标签:** 63- 支持<embed>标签:在开启同层渲染后,仅支持type类型为native前缀的标签识别为同层组件,不支持自定义属性。 64 65- 支持<object>标签:在开启同层渲染后,支持将非标准MIME type的object标签识别为同层组件,支持通过param/value的自定义属性解析。 66 67- 不支持W3C规范标准标签(如<input>、<video>)定义为同层标签。 68 69- 不支持同时配置<object>标签和<embed>标签作为同层标签。 70 71- 标签类型只支持英文字符,不区分大小写。 72 73**同层标签支持的css属性:** 74 75display,position,z-index,visibility,opacity, background-color,background-image,width,height,padding,padding-left,padding-top,padding-right,padding-bottom,margin,margin-left,margin-top,margin-right,margin-bottom,border-width,border-style,border-color,border-left-width,border-left-style,border-left-color,border-top-width,border-top-style,border-top-color,border-right-width,border-right-style,border-right-color,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-right,border-top,border-bottom,border,border-top-left-radius,border-top-right-radius,border-bottom-left-radius,border-bottom-right-radius,border-radius,transition,transform(仅支持translate/scale,scale对应参数只支持大于等于0的值) 76 77 除上面支持的css属性范围,其他的css属性均不保证符合预期,比如transform属性中的rotate,skew等。 78 79**同层标签的生命周期管理:** 80当同层标签生命周期变化时触发[onNativeEmbedLifecycleChange()](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedlifecyclechange11)回调。 81 82- 支持创建、销毁、位置宽高变化。 83 84- 支持同层组件所在Web页面进入前进后退缓存。 85 86**同层标签的输入事件分发处理:** 87- 支持触摸事件TouchEvent的DOWN/UP/MOVE/CANCEL。支持[配置触摸事件消费结果](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedgestureevent11),默认为应用侧消费。 88 89- 不支持同层标签所在的应用页面缩放和[initialScale](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#initialscale9)、[zoom](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#zoom)、[zoomIn](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#zoomin)、[zoomOut](../reference/apis-arkweb/arkts-apis-webview-WebviewController.md#zoomout)等缩放接口。 90 91- 暂不支持鼠标、键盘、触摸板事件上报。 92 93- 支持默认将鼠标和触摸板左键事件(MousePress/MouseRelease/MouseMOVE)转换为触摸事件(TouchDOWN/TouchUP/TouchMOVE)上报。 94 95**同层标签的可见状态变化:** 96当同层标签可见状态变化时触发[onNativeEmbedVisibilityChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedvisibilitychange12)回调。 97 98- 支持同层标签相对于视口的可见状态上报。 99 100- 默认不支持由于同层标签CSS样式或尺寸变化导致的可见状态变化上报,具体规格参考[onNativeEmbedVisibilityChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedvisibilitychange12)。 101 102**约束限制:** 103 104- Web页面内同层标签数量应控制在5个以内。超过5个,渲染性能将会下降。 105 106- 受GPU限制,同层标签最大高度不超过8000px,最大纹理大小为8000px。 107 108- 开启同层渲染后,Web组件打开的所有Web页面将不支持同步渲染模式[RenderMode](../reference/apis-arkweb/arkts-basic-components-web-e.md#rendermode12)。 109 110- Video组件:在非全屏Video变为全屏时,Video组件变为非纹理导出模式,视频播放状态保持延续;恢复为非全屏时,变为纹理导出模式,视频播放状态保持延续。 111 112- Web组件:仅支持一层同层渲染嵌套,不支持多层同层渲染嵌套。输入事件只支持滑动、点击、长按,不支持拖拽、旋转、缩放。 113 114- 涉及界面交互的ArkUI组件(如TextInput等):建议在页面布局中使用Stack包裹同层组件容器与BuilderNode,并使两者位置一致,NodeContainer要与<embed>/<object>标签对齐,以保障组件正常交互。如两者位置不一致,可能出现的问题有:TextInput/TextArea等附属的文本选择框位置错位(如下图)、LoadingProgress/Marquee等组件的动画启停与组件可见状态不匹配。 115 116 **图2** 未使用Stack包裹,TextInput的位置错位 117 118  119 120 **图3** 使用Stack包裹,TextInput的位置正常 121 122  123 124## Web页面中同层渲染输入框 125在Web页面中,可以使用ArkUI系统的TextInput组件进行同层渲染。此处利用同层渲染展示三个输入框,渲染效果图如下: 126 127**图4** 同层渲染输入框 128 129  130 1311. 在Web页面中标记需要同层渲染的HTML标签。 132 133 同层渲染支持<embed>/<object>两种标签。type类型可任意指定,两个字符串参数均不区分大小写,ArkWeb内核将会统一转换为小写。其中,tag字符串使用全字符串匹配,type使用字符串前缀匹配。 134 135 若开发者不使用该接口或该接口接收的为非法字符串(空字符串)时,ArkWeb内核将使用默认设置,即"embed" + "native/"前缀模式。若指定类型与w3c定义的<embed>或<object>标准类型重合,如registerNativeEmbedRule("object", "application/pdf"),ArkWeb将遵循w3c标准行为,不会将其识别为同层标签。 136 137 - 采用<embed>标签。 138 139 ```html 140 <!--HAP's src/main/resources/rawfile/text.html--> 141 <!DOCTYPE html> 142 <html> 143 <head> 144 <title>同层渲染html</title> 145 <meta name="viewport"> 146 </head> 147 148 <body style="background:white"> 149 150 <embed id = "input1" type="native/view" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"/> 151 152 <embed id = "input2" type="native/view2" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/> 153 154 <embed id = "input3" type="native/view3" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/> 155 156 </body> 157 </html> 158 ``` 159 160 - 采用<object>标签。 161 162 需要使用registerNativeEmbedRule注册object标签。 163 ```ts 164 // ... 165 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 166 // 注册同层标签为"object",类型为"test"前缀。 167 .registerNativeEmbedRule("object", "test") 168 // ... 169 ``` 170 171 与registerNativeEmbedRule相对应的前端页面代码,类型可使用"test"及以"test"为前缀的字串。 172 173 ```html 174 <!--HAP's src/main/resources/rawfile/text.html--> 175 <!DOCTYPE html> 176 <html> 177 <head> 178 <title>同层渲染html</title> 179 <meta name="viewport"> 180 </head> 181 182 <body style="background:white"> 183 184 <object id = "input1" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"></object> 185 186 <object id = "input2" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object> 187 188 <object id = "input3" type="test/input" style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"></object> 189 190 </body> 191 </html> 192 ``` 193 1942. 在应用侧开启同层渲染功能。 195 196 同层渲染功能默认不开启,如果要使用同层渲染的功能,可通过[enableNativeEmbedMode](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#enablenativeembedmode11)来开启。 197 198 ```ts 199 // xxx.ets 200 import { webview } from '@kit.ArkWeb'; 201 @Entry 202 @Component 203 struct WebComponent { 204 controller: webview.WebviewController = new webview.WebviewController(); 205 206 build() { 207 Column() { 208 Web({ src: 'www.example.com', controller: this.controller }) 209 // 配置同层渲染开关开启。 210 .enableNativeEmbedMode(true) 211 } 212 } 213 } 214 ``` 215 2163. 创建自定义组件。 217 218 同层渲染功能开启后,展示在对应区域的系统组件。 219 220 ```ts 221 @Component 222 struct TextInputComponent { 223 @Prop params: Params 224 @State bkColor: Color = Color.White 225 226 build() { 227 Column() { 228 TextInput({text: '', placeholder: 'please input your word...'}) 229 .placeholderColor(Color.Gray) 230 .id(this.params?.elementId) 231 .placeholderFont({size: 13, weight: 400}) 232 .caretColor(Color.Gray) 233 .width(this.params?.width) 234 .height(this.params?.height) 235 .fontSize(14) 236 .fontColor(Color.Black) 237 } 238 //自定义组件中的最外层容器组件宽高应该为同层标签的宽高。 239 .width(this.params.width) 240 .height(this.params.height) 241 } 242 } 243 244 @Builder 245 function TextInputBuilder(params:Params) { 246 TextInputComponent({params: params}) 247 .width(params.width) 248 .height(params.height) 249 .backgroundColor(Color.White) 250 } 251 ``` 252 2534. 创建节点控制器。 254 255 用于控制和反馈对应NodeContainer上的节点行为。 256 257 ```ts 258 class MyNodeController extends NodeController { 259 private rootNode: BuilderNode<[Params]> | undefined | null; 260 private embedId_: string = ""; 261 private surfaceId_: string = ""; 262 private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; 263 private width_: number = 0; 264 private height_: number = 0; 265 private type_: string = ""; 266 private isDestroy_: boolean = false; 267 268 setRenderOption(params: NodeControllerParams) { 269 this.surfaceId_ = params.surfaceId; 270 this.renderType_ = params.renderType; 271 this.embedId_ = params.embedId; 272 this.width_ = params.width; 273 this.height_ = params.height; 274 this.type_ = params.type; 275 } 276 277 // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。 278 // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。 279 makeNode(uiContext: UIContext): FrameNode | null { 280 if (this.isDestroy_) { // rootNode为null。 281 return null; 282 } 283 if (!this.rootNode) {// rootNode 为undefined时。 284 this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ }); 285 if(this.rootNode) { 286 this.rootNode.build(wrapBuilder(TextInputBuilder), { textOne: "myTextInput", width: this.width_, height: this.height_ }) 287 return this.rootNode.getFrameNode(); 288 }else{ 289 return null; 290 } 291 } 292 // 返回FrameNode节点。 293 return this.rootNode.getFrameNode(); 294 } 295 296 updateNode(arg: Object): void { 297 this.rootNode?.update(arg); 298 } 299 300 getEmbedId(): string { 301 return this.embedId_; 302 } 303 304 setDestroy(isDestroy: boolean): void { 305 this.isDestroy_ = isDestroy; 306 if (this.isDestroy_) { 307 this.rootNode = null; 308 } 309 } 310 311 postEvent(event: TouchEvent | undefined): boolean { 312 return this.rootNode?.postTouchEvent(event) as boolean 313 } 314 } 315 ``` 316 3175. 监听同层渲染的生命周期变化。 318 319 开启该功能后,当网页中存在同层渲染支持的标签时,ArkWeb内核会触发由[onNativeEmbedLifecycleChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedlifecyclechange11)注册的回调函数。 320 321 开发者则需要调用[onNativeEmbedLifecycleChange](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedlifecyclechange11)来监听同层渲染标签的生命周期变化。 322 323 ```ts 324 build() { 325 Row() { 326 Column() { 327 Stack() { 328 ForEach(this.componentIdArr, (componentId: string) => { 329 NodeContainer(this.nodeControllerMap.get(componentId)) 330 .position(this.positionMap.get(componentId)) 331 .width(this.widthMap.get(componentId)) 332 .height(this.heightMap.get(componentId)) 333 }, (embedId: string) => embedId) 334 // Web组件加载本地text.html页面。 335 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 336 // 配置同层渲染开关开启。 337 .enableNativeEmbedMode(true) 338 // 注册同层标签为<object>,类型为"test"前缀。 339 .registerNativeEmbedRule("object", "test") 340 // 获取<embed>标签的生命周期变化数据。 341 .onNativeEmbedLifecycleChange((embed) => { 342 console.info("NativeEmbed surfaceId" + embed.surfaceId); 343 // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。 344 const componentId = embed.info?.id?.toString() as string 345 if (embed.status == NativeEmbedStatus.CREATE) { 346 console.info("NativeEmbed create" + JSON.stringify(embed.info)); 347 // 创建节点控制器、设置参数并rebuild。 348 let nodeController = new MyNodeController() 349 // embed.info.width和embed.info.height单位是px格式,需要转换成ets侧的默认单位vp。 350 nodeController.setRenderOption({surfaceId : embed.surfaceId as string, 351 type : embed.info?.type as string, 352 renderType : NodeRenderType.RENDER_TYPE_TEXTURE, 353 embedId : embed.embedId as string, 354 width : this.uiContext.px2vp(embed.info?.width), 355 height : this.uiContext.px2vp(embed.info?.height)}) 356 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 357 nodeController.setDestroy(false); 358 //根据web传入的embed的id属性作为key,将nodeController存入Map。 359 this.nodeControllerMap.set(componentId, nodeController); 360 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 361 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 362 this.positionMap.set(componentId, this.edges); 363 // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。 364 this.componentIdArr.push(componentId) 365 } else if (embed.status == NativeEmbedStatus.UPDATE) { 366 let nodeController = this.nodeControllerMap.get(componentId); 367 console.info("NativeEmbed update" + JSON.stringify(embed)); 368 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 369 this.positionMap.set(componentId, this.edges); 370 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 371 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 372 nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject) 373 } else if (embed.status == NativeEmbedStatus.DESTROY) { 374 console.info("NativeEmbed destroy" + JSON.stringify(embed)); 375 let nodeController = this.nodeControllerMap.get(componentId); 376 nodeController?.setDestroy(true); 377 this.nodeControllerMap.delete(componentId); 378 this.positionMap.delete(componentId); 379 this.widthMap.delete(componentId); 380 this.heightMap.delete(componentId); 381 this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId); 382 } else { 383 console.info("NativeEmbed status" + embed.status); 384 } 385 }) 386 }.height("80%") 387 } 388 } 389 } 390 ``` 391 3926. 同层渲染手势事件。 393 394 开启该功能后,每当在同层渲染的区域进行触摸操作时,ArkWeb内核会触发[onNativeEmbedGestureEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedgestureevent11)注册的回调函数。 395 396 开发者则需要调用[onNativeEmbedGestureEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedgestureevent11)来监听同层渲染区域的手势事件。 397 398 ```ts 399 build() { 400 Row() { 401 Column() { 402 Stack() { 403 ForEach(this.componentIdArr, (componentId: string) => { 404 NodeContainer(this.nodeControllerMap.get(componentId)) 405 .position(this.positionMap.get(componentId)) 406 .width(this.widthMap.get(componentId)) 407 .height(this.heightMap.get(componentId)) 408 }, (embedId: string) => embedId) 409 // Web组件加载本地text.html页面。 410 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 411 // 配置同层渲染开关开启。 412 .enableNativeEmbedMode(true) 413 // 获取<embed>标签的生命周期变化数据。 414 .onNativeEmbedLifecycleChange((embed) => { 415 // 生命周期变化实现。 416 }) 417 .onNativeEmbedGestureEvent((touch) => { 418 console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent)); 419 this.componentIdArr.forEach((componentId: string) => { 420 let nodeController = this.nodeControllerMap.get(componentId); 421 // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。 422 if(nodeController?.getEmbedId() == touch.embedId) { 423 let ret = nodeController?.postEvent(touch.touchEvent) 424 if(ret) { 425 console.info("onNativeEmbedGestureEvent success " + componentId); 426 } else { 427 console.info("onNativeEmbedGestureEvent fail " + componentId); 428 } 429 if(touch.result) { 430 // 通知Web组件手势事件消费结果。 431 touch.result.setGestureEventResult(ret); 432 } 433 } 434 }) 435 }) 436 } 437 } 438 } 439 } 440 ``` 441 4427. 同层渲染鼠标事件 443 444 开启该功能后,在同层渲染的区域进行下述动作时,ArkWeb内核会触发[onNativeEmbedMouseEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedmouseevent20)注册的回调函数: 445 446 - 使用鼠标左键、中键、右键进行点击或长按。 447 - 使用触摸板进行对应鼠标左键、中键、右键点击长按的操作。 448 449 开发者则需要调用[onNativeEmbedMouseEvent](../reference/apis-arkweb/arkts-basic-components-web-events.md#onnativeembedmouseevent20)来监听同层渲染同层渲染区域的鼠标事件。 450 451 ```ts 452 build() { 453 Row() { 454 Column() { 455 Stack() { 456 ForEach(this.componentIdArr, (componentId: string) => { 457 NodeContainer(this.nodeControllerMap.get(componentId)) 458 .position(this.positionMap.get(componentId)) 459 .width(this.widthMap.get(componentId)) 460 .height(this.heightMap.get(componentId)) 461 }, (embedId: string) => embedId) 462 // Web组件加载本地text.html页面。 463 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 464 // 配置同层渲染开关开启。 465 .enableNativeEmbedMode(true) 466 // 获取<embed>标签的生命周期变化数据。 467 .onNativeEmbedLifecycleChange((embed) => { 468 // 生命周期变化实现。 469 }) 470 .onNativeEmbedGestureEvent((touch) => { 471 // 处理同层渲染手势事件。 472 }) 473 .onNativeEmbedMouseEvent((mouse) => { 474 console.info("NativeEmbed onNativeEmbedMouseEvent" + JSON.stringify(mouse.mouseEvent)); 475 this.componentIdArr.forEach((componentId: string) => { 476 let nodeController = this.nodeControllerMap.get(componentId); 477 // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。 478 if(nodeController?.getEmbedId() == mouse.embedId) { 479 let ret = nodeController?.postInputEvent(mouse.mouseEvent) 480 if(ret) { 481 console.info("onNativeEmbedMouseEvent success " + componentId); 482 } else { 483 console.info("onNativeEmbedMouseEvent fail " + componentId); 484 } 485 if(mouse.result) { 486 // 通知Web组件鼠标事件消费结果。 487 mouse.result.setMouseEventResult(ret); 488 } 489 } 490 }) 491 }) 492 } 493 } 494 } 495 } 496 ``` 497**完整示例:** 498 499使用前请在module.json5中添加网络权限,添加方法请参考[在配置文件中声明权限](../security/AccessToken/declare-permissions.md#在配置文件中声明权限)。 500 501 ``` 502 "requestPermissions":[ 503 { 504 "name" : "ohos.permission.INTERNET" 505 } 506 ] 507 ``` 508 509应用侧代码。 510 511 ```ts 512 // 创建NodeController 513 import { webview } from '@kit.ArkWeb'; 514 import { UIContext } from '@kit.ArkUI'; 515 import { NodeController, BuilderNode, NodeRenderType, FrameNode } from '@kit.ArkUI'; 516 517 @Observed 518 declare class Params{ 519 elementId: string 520 textOne: string 521 textTwo: string 522 width: number 523 height: number 524 } 525 526 declare class NodeControllerParams { 527 surfaceId: string 528 type: string 529 renderType: NodeRenderType 530 embedId: string 531 width: number 532 height: number 533 } 534 535 // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。 536 class MyNodeController extends NodeController { 537 private rootNode: BuilderNode<[Params]> | undefined | null; 538 private embedId_: string = ""; 539 private surfaceId_: string = ""; 540 private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; 541 private width_: number = 0; 542 private height_: number = 0; 543 private type_: string = ""; 544 private isDestroy_: boolean = false; 545 546 setRenderOption(params: NodeControllerParams) { 547 this.surfaceId_ = params.surfaceId; 548 this.renderType_ = params.renderType; 549 this.embedId_ = params.embedId; 550 this.width_ = params.width; 551 this.height_ = params.height; 552 this.type_ = params.type; 553 } 554 555 // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。 556 // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。 557 makeNode(uiContext: UIContext): FrameNode | null { 558 if (this.isDestroy_) { // rootNode为null。 559 return null; 560 } 561 if (!this.rootNode) {// rootNode 为undefined时。 562 this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ }); 563 if(this.rootNode) { 564 this.rootNode.build(wrapBuilder(TextInputBuilder), { textOne: "myTextInput", width: this.width_, height: this.height_ }) 565 return this.rootNode.getFrameNode(); 566 }else{ 567 return null; 568 } 569 } 570 // 返回FrameNode节点。 571 return this.rootNode.getFrameNode(); 572 } 573 574 updateNode(arg: Object): void { 575 this.rootNode?.update(arg); 576 } 577 578 getEmbedId(): string { 579 return this.embedId_; 580 } 581 582 setDestroy(isDestroy: boolean): void { 583 this.isDestroy_ = isDestroy; 584 if (this.isDestroy_) { 585 this.rootNode = null; 586 } 587 } 588 589 postEvent(event: TouchEvent | undefined): boolean { 590 return this.rootNode?.postTouchEvent(event) as boolean 591 } 592 593 postInputEvent(event: MouseEvent | undefined): boolean { 594 return this.rootNode?.postInputEvent(event) as boolean 595 } 596 } 597 598 @Component 599 struct TextInputComponent { 600 @Prop params: Params 601 @State bkColor: Color = Color.White 602 603 build() { 604 Column() { 605 TextInput({text: '', placeholder: 'please input your word...'}) 606 .placeholderColor(Color.Gray) 607 .id(this.params?.elementId) 608 .placeholderFont({size: 13, weight: 400}) 609 .caretColor(Color.Gray) 610 .fontSize(14) 611 .fontColor(Color.Black) 612 } 613 //自定义组件中的最外层容器组件宽高应该为同层标签的宽高。 614 .width(this.params.width) 615 .height(this.params.height) 616 } 617 } 618 619 // @Builder中为动态组件的具体组件内容。 620 @Builder 621 function TextInputBuilder(params:Params) { 622 TextInputComponent({params: params}) 623 .width(params.width) 624 .height(params.height) 625 .backgroundColor(Color.White) 626 } 627 628 @Entry 629 @Component 630 struct Page{ 631 browserTabController: WebviewController = new webview.WebviewController() 632 private nodeControllerMap: Map<string, MyNodeController> = new Map(); 633 @State componentIdArr: Array<string> = []; 634 @State widthMap: Map<string, number> = new Map(); 635 @State heightMap: Map<string, number> = new Map(); 636 @State positionMap: Map<string, Edges> = new Map(); 637 @State edges: Edges = {}; 638 uiContext: UIContext = this.getUIContext(); 639 640 build() { 641 Row() { 642 Column() { 643 Stack() { 644 ForEach(this.componentIdArr, (componentId: string) => { 645 NodeContainer(this.nodeControllerMap.get(componentId)) 646 .position(this.positionMap.get(componentId)) 647 .width(this.widthMap.get(componentId)) 648 .height(this.heightMap.get(componentId)) 649 }, (embedId: string) => embedId) 650 // Web组件加载本地text.html页面。 651 Web({src: $rawfile("text.html"), controller: this.browserTabController}) 652 // 配置同层渲染开关开启。 653 .enableNativeEmbedMode(true) 654 // 获取<embed>标签的生命周期变化数据。 655 .onNativeEmbedLifecycleChange((embed) => { 656 console.info("NativeEmbed surfaceId" + embed.surfaceId); 657 // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。 658 const componentId = embed.info?.id?.toString() as string 659 if (embed.status == NativeEmbedStatus.CREATE) { 660 console.info("NativeEmbed create" + JSON.stringify(embed.info)); 661 // 创建节点控制器、设置参数并rebuild。 662 let nodeController = new MyNodeController() 663 // embed.info.width和embed.info.height单位是px格式,需要转换成ets侧的默认单位vp。 664 nodeController.setRenderOption({surfaceId : embed.surfaceId as string, 665 type : embed.info?.type as string, 666 renderType : NodeRenderType.RENDER_TYPE_TEXTURE, 667 embedId : embed.embedId as string, 668 width : this.uiContext.px2vp(embed.info?.width), 669 height : this.uiContext.px2vp(embed.info?.height)}) 670 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 671 nodeController.setDestroy(false); 672 //根据web传入的embed的id属性作为key,将nodeController存入Map。 673 this.nodeControllerMap.set(componentId, nodeController); 674 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 675 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 676 this.positionMap.set(componentId, this.edges); 677 // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。 678 this.componentIdArr.push(componentId) 679 } else if (embed.status == NativeEmbedStatus.UPDATE) { 680 let nodeController = this.nodeControllerMap.get(componentId); 681 console.info("NativeEmbed update" + JSON.stringify(embed)); 682 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 683 this.positionMap.set(componentId, this.edges); 684 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 685 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 686 nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject) 687 } else if (embed.status == NativeEmbedStatus.DESTROY) { 688 console.info("NativeEmbed destroy" + JSON.stringify(embed)); 689 let nodeController = this.nodeControllerMap.get(componentId); 690 nodeController?.setDestroy(true); 691 this.nodeControllerMap.delete(componentId); 692 this.positionMap.delete(componentId); 693 this.widthMap.delete(componentId); 694 this.heightMap.delete(componentId); 695 this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId); 696 } else { 697 console.info("NativeEmbed status" + embed.status); 698 } 699 })// 获取同层渲染组件触摸事件信息。 700 .onNativeEmbedGestureEvent((touch) => { 701 console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent)); 702 this.componentIdArr.forEach((componentId: string) => { 703 let nodeController = this.nodeControllerMap.get(componentId); 704 // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。 705 if(nodeController?.getEmbedId() == touch.embedId) { 706 let ret = nodeController?.postEvent(touch.touchEvent) 707 if(ret) { 708 console.info("onNativeEmbedGestureEvent success " + componentId); 709 } else { 710 console.info("onNativeEmbedGestureEvent fail " + componentId); 711 } 712 if(touch.result) { 713 // 通知Web组件手势事件消费结果。 714 touch.result.setGestureEventResult(ret); 715 } 716 } 717 }) 718 }) 719 .onNativeEmbedMouseEvent((mouse) => { 720 console.info("NativeEmbed onNativeEmbedMouseEvent" + JSON.stringify(mouse.mouseEvent)); 721 this.componentIdArr.forEach((componentId: string) => { 722 let nodeController = this.nodeControllerMap.get(componentId); 723 // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。 724 if(nodeController?.getEmbedId() == mouse.embedId) { 725 let ret = nodeController?.postInputEvent(mouse.mouseEvent) 726 if(ret) { 727 console.info("onNativeEmbedMouseEvent success " + componentId); 728 } else { 729 console.info("onNativeEmbedMouseEvent fail " + componentId); 730 } 731 if(mouse.result) { 732 // 通知Web组件鼠标事件消费结果。 733 mouse.result.setMouseEventResult(ret); 734 } 735 } 736 }) 737 }) 738 } 739 } 740 } 741 } 742 } 743 ``` 744 745## 绘制XComponent+AVPlayer和Button组件 746 747- 应用侧代码组件使用示例。 748 749 ```ts 750 // HAP's src/main/ets/pages/Index.ets 751 // 创建NodeController 752 import { webview } from '@kit.ArkWeb'; 753 import { UIContext, NodeController, BuilderNode, NodeRenderType, FrameNode } from "@kit.ArkUI"; 754 import { AVPlayerDemo } from './PlayerDemo'; 755 756 @Observed 757 declare class Params { 758 textOne : string 759 textTwo : string 760 width : number 761 height : number 762 } 763 764 declare class NodeControllerParams { 765 surfaceId : string 766 type : string 767 renderType : NodeRenderType 768 embedId : string 769 width : number 770 height : number 771 } 772 773 // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。 774 class MyNodeController extends NodeController { 775 private rootNode: BuilderNode<[Params]> | undefined | null; 776 private embedId_ : string = ""; 777 private surfaceId_ : string = ""; 778 private renderType_ :NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; 779 private width_ : number = 0; 780 private height_ : number = 0; 781 private type_ : string = ""; 782 private isDestroy_ : boolean = false; 783 784 setRenderOption(params : NodeControllerParams) { 785 this.surfaceId_ = params.surfaceId; 786 this.renderType_ = params.renderType; 787 this.embedId_ = params.embedId; 788 this.width_ = params.width; 789 this.height_ = params.height; 790 this.type_ = params.type; 791 } 792 // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。 793 // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。 794 makeNode(uiContext: UIContext): FrameNode | null{ 795 if (this.isDestroy_) { // rootNode为null。 796 return null; 797 } 798 if (!this.rootNode) { // rootNode 为undefined时。 799 this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_}); 800 if (this.type_ === 'native/video') { 801 this.rootNode.build(wrapBuilder(VideoBuilder), {textOne: "myButton", width : this.width_, height : this.height_}); 802 } else { 803 // other 804 } 805 } 806 // 返回FrameNode节点。 807 return this.rootNode.getFrameNode(); 808 } 809 810 updateNode(arg: Object): void { 811 this.rootNode?.update(arg); 812 } 813 getEmbedId() : string { 814 return this.embedId_; 815 } 816 817 setDestroy(isDestroy : boolean) : void { 818 this.isDestroy_ = isDestroy; 819 if (this.isDestroy_) { 820 this.rootNode = null; 821 } 822 } 823 824 postEvent(event: TouchEvent | undefined) : boolean { 825 return this.rootNode?.postTouchEvent(event) as boolean 826 } 827 828 postInputEvent(event: MouseEvent | undefined): boolean { 829 return this.rootNode?.postInputEvent(event) as boolean 830 } 831 } 832 833 @Component 834 struct VideoComponent { 835 @ObjectLink params: Params 836 @State bkColor: Color = Color.Red 837 mXComponentController: XComponentController = new XComponentController(); 838 @State player_changed: boolean = false; 839 player?: AVPlayerDemo; 840 841 build() { 842 Column() { 843 Button(this.params.textOne) 844 845 XComponent({ id: 'video_player_id', type: XComponentType.SURFACE, controller: this.mXComponentController}) 846 .border({width: 1, color: Color.Red}) 847 .onLoad(() => { 848 this.player = new AVPlayerDemo(); 849 this.player.setSurfaceID(this.mXComponentController.getXComponentSurfaceId()); 850 this.player_changed = !this.player_changed; 851 this.player.avPlayerLiveDemo() 852 }) 853 .width(300) 854 .height(200) 855 } 856 //自定义组件中的最外层容器组件宽高应该为同层标签的宽高。 857 .width(this.params.width) 858 .height(this.params.height) 859 } 860 } 861 // @Builder中为动态组件的具体组件内容。 862 @Builder 863 function VideoBuilder(params: Params) { 864 VideoComponent({ params: params }) 865 .backgroundColor(Color.Gray) 866 } 867 868 @Entry 869 @Component 870 struct WebIndex { 871 browserTabController: WebviewController = new webview.WebviewController() 872 private nodeControllerMap: Map<string, MyNodeController> = new Map(); 873 @State componentIdArr: Array<string> = []; 874 @State widthMap: Map<string, number> = new Map(); 875 @State heightMap: Map<string, number> = new Map(); 876 @State positionMap: Map<string, Edges> = new Map(); 877 @State edges: Edges = {}; 878 uiContext: UIContext = this.getUIContext(); 879 880 aboutToAppear() { 881 // 配置web开启调试模式。 882 webview.WebviewController.setWebDebuggingAccess(true); 883 } 884 885 build(){ 886 Row() { 887 Column() { 888 Stack() { 889 ForEach(this.componentIdArr, (componentId: string) => { 890 NodeContainer(this.nodeControllerMap.get(componentId)) 891 .position(this.positionMap.get(componentId)) 892 .width(this.widthMap.get(componentId)) 893 .height(this.heightMap.get(componentId)) 894 }, (embedId: string) => embedId) 895 // Web组件加载本地test.html页面。 896 Web({ src: $rawfile("test.html"), controller: this.browserTabController }) 897 // 配置同层渲染开关开启。 898 .enableNativeEmbedMode(true) 899 // 获取<embed>标签的生命周期变化数据。 900 .onNativeEmbedLifecycleChange((embed) => { 901 console.info("NativeEmbed surfaceId" + embed.surfaceId); 902 // 1. 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。 903 const componentId = embed.info?.id?.toString() as string 904 if (embed.status == NativeEmbedStatus.CREATE) { 905 console.info("NativeEmbed create" + JSON.stringify(embed.info)) 906 // 创建节点控制器,设置参数并rebuild。 907 let nodeController = new MyNodeController() 908 // 1. embed.info.width和embed.info.height单位是px格式,需要转换成ets侧的默认单位vp。 909 nodeController.setRenderOption({surfaceId : embed.surfaceId as string, type : embed.info?.type as string, 910 renderType : NodeRenderType.RENDER_TYPE_TEXTURE, embedId : embed.embedId as string, 911 width : this.uiContext.px2vp(embed.info?.width), height : this.uiContext.px2vp(embed.info?.height)}) 912 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 913 nodeController.setDestroy(false); 914 // 根据web传入的embed的id属性作为key,将nodeController存入map。 915 this.nodeControllerMap.set(componentId, nodeController) 916 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 917 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 918 this.positionMap.set(componentId, this.edges); 919 // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。 920 this.componentIdArr.push(componentId) 921 } else if (embed.status == NativeEmbedStatus.UPDATE) { 922 let nodeController = this.nodeControllerMap.get(componentId) 923 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 924 this.positionMap.set(componentId, this.edges); 925 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 926 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 927 nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject) 928 } else if (embed.status == NativeEmbedStatus.DESTROY) { 929 let nodeController = this.nodeControllerMap.get(componentId); 930 nodeController?.setDestroy(true); 931 this.nodeControllerMap.delete(componentId); 932 this.positionMap.delete(componentId); 933 this.widthMap.delete(componentId); 934 this.heightMap.delete(componentId); 935 this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId); 936 } else { 937 console.info("NativeEmbed status" + embed.status); 938 } 939 })// 获取同层渲染组件触摸事件信息。 940 .onNativeEmbedGestureEvent((touch) => { 941 console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent)); 942 this.componentIdArr.forEach((componentId: string) => { 943 let nodeController = this.nodeControllerMap.get(componentId) 944 // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。 945 if (nodeController?.getEmbedId() === touch.embedId) { 946 let ret = nodeController?.postEvent(touch.touchEvent) 947 if (ret) { 948 console.info("onNativeEmbedGestureEvent success " + componentId) 949 } else { 950 console.info("onNativeEmbedGestureEvent fail " + componentId) 951 } 952 if (touch.result) { 953 // 通知Web组件手势事件消费结果。 954 touch.result.setGestureEventResult(ret); 955 } 956 } 957 }) 958 }) 959 .onNativeEmbedMouseEvent((mouse) => { 960 console.info("NativeEmbed onNativeEmbedMouseEvent" + JSON.stringify(mouse.mouseEvent)); 961 this.componentIdArr.forEach((componentId: string) => { 962 let nodeController = this.nodeControllerMap.get(componentId); 963 // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。 964 if(nodeController?.getEmbedId() == mouse.embedId) { 965 let ret = nodeController?.postInputEvent(mouse.mouseEvent) 966 if(ret) { 967 console.info("onNativeEmbedMouseEvent success " + componentId); 968 } else { 969 console.info("onNativeEmbedMouseEvent fail " + componentId); 970 } 971 if(mouse.result) { 972 // 通知Web组件鼠标事件消费结果。 973 mouse.result.setMouseEventResult(ret); 974 } 975 } 976 }) 977 }) 978 } 979 } 980 } 981 } 982 } 983 ``` 984 985- 应用侧代码示例,视频播放,使用时需替换为正确的视频链接地址。 986 987 ```ts 988 // HAP's src/main/ets/pages/PlayerDemo.ets 989 import { media } from '@kit.MediaKit'; 990 import { BusinessError } from '@ohos.base'; 991 992 export class AVPlayerDemo { 993 private count: number = 0; 994 private surfaceID: string = ''; // surfaceID用于播放画面显示,具体的值需要通过Xcomponent接口获取,相关文档链接见上面Xcomponent创建方法。 995 private isSeek: boolean = true; // 用于区分模式是否支持seek操作。 996 997 setSurfaceID(surface_id: string){ 998 console.info('setSurfaceID : ' + surface_id); 999 this.surfaceID = surface_id; 1000 } 1001 // 注册avplayer回调函数。 1002 setAVPlayerCallback(avPlayer: media.AVPlayer) { 1003 // seek操作结果回调函数。 1004 avPlayer.on('seekDone', (seekDoneTime: number) => { 1005 console.info(`AVPlayer seek succeeded, seek time is ${seekDoneTime}`); 1006 }) 1007 // error回调监听函数,当avplayer在操作过程中出现错误时,调用reset接口触发重置流程。 1008 avPlayer.on('error', (err: BusinessError) => { 1009 console.error(`Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`); 1010 avPlayer.reset(); 1011 }) 1012 // 状态机变化回调函数。 1013 avPlayer.on('stateChange', async (state: string, reason: media.StateChangeReason) => { 1014 switch (state) { 1015 case 'idle': // 成功调用reset接口后触发该状态机上报。 1016 console.info('AVPlayer state idle called.'); 1017 avPlayer.release(); // 调用release接口销毁实例对象。 1018 break; 1019 case 'initialized': // avplayer 设置播放源后触发该状态上报。 1020 console.info('AVPlayer state initialized called.'); 1021 avPlayer.surfaceId = this.surfaceID; // 设置显示画面,当播放的资源为纯音频时无需设置。 1022 avPlayer.prepare(); 1023 break; 1024 case 'prepared': // prepared调用成功后上报该状态机。 1025 console.info('AVPlayer state prepared called.'); 1026 avPlayer.play(); // 调用播放接口开始播放。 1027 break; 1028 case 'playing': // play成功调用后触发该状态机上报。 1029 console.info('AVPlayer state playing called.'); 1030 if(this.count !== 0) { 1031 if (this.isSeek) { 1032 console.info('AVPlayer start to seek.'); 1033 avPlayer.seek(avPlayer.duration); // seek到视频末尾。 1034 } else { 1035 // 当播放模式不支持seek操作时继续播放到结尾。 1036 console.info('AVPlayer wait to play end.'); 1037 } 1038 } else { 1039 avPlayer.pause(); // 调用暂停接口暂停播放。 1040 } 1041 this.count++; 1042 break; 1043 case 'paused': // pause成功调用后触发该状态机上报。 1044 console.info('AVPlayer state paused called.'); 1045 avPlayer.play(); // 再次播放接口开始播放。 1046 break; 1047 case 'completed': //播放接口后触发该状态机上报。 1048 console.info('AVPlayer state completed called.'); 1049 avPlayer.stop(); // 调用播放接口。 1050 break; 1051 case 'stopped': // stop接口后触发该状态机上报。 1052 console.info('AVPlayer state stopped called.'); 1053 avPlayer.reset(); // 调用reset接口初始化avplayer状态。 1054 break; 1055 case 'released': //播放接口后触发该状态机上报。 1056 console.info('AVPlayer state released called.'); 1057 break; 1058 default: 1059 break; 1060 } 1061 }) 1062 } 1063 1064 // 通过url设置网络地址来实现播放直播码流。 1065 async avPlayerLiveDemo(){ 1066 // 创建avPlayer实例对象。 1067 let avPlayer: media.AVPlayer = await media.createAVPlayer(); 1068 // 创建状态机变化回调函数。 1069 this.setAVPlayerCallback(avPlayer); 1070 this.isSeek = false; // 不支持seek操作。 1071 // 使用时需要自行替换视频链接。 1072 avPlayer.url = 'https://xxx.xxx/demo.mp4'; 1073 } 1074 } 1075 ``` 1076 1077- 前端页面示例。 1078 1079 ```html 1080 <!--HAP's src/main/resources/rawfile/test.html--> 1081 <!DOCTYPE html> 1082 <html> 1083 <head> 1084 <title>同层渲染测试html</title> 1085 <meta name="viewport"> 1086 </head> 1087 <body> 1088 <div> 1089 <div id="bodyId"> 1090 <embed id="nativeVideo" type = "native/video" width="1000" height="1500" src="test" style = "background-color:red"/> 1091 </div> 1092 </div> 1093 </body> 1094 </html> 1095 ``` 1096 1097- 实现效果: 1098 1099  1100 1101## 同层标签设置为最高层级 1102 1103同层渲染支持私有属性arkwebnativestyle,该属性仅在开启同层渲染后的<embed>和<object>中生效,该属性的display属性用于控制同层标签的显示层级,使其高于其他Web元素。如果多个同层标签都设置了arkwebnativestyle的display属性,并且属性相同,则它们的层级顺序将遵循W3C标准层级排序规则:先比较z-index属性值,当z-index相同时,按照元素在DOM中的先后顺序排序。display属性取值说明如下: 1104 1105| display取值 | 说明 | 1106| - | - | 1107| overlay | 设置同层标签层级高于其他Web元素。 | 1108| overlay-infinity | 设置同层标签层级高于其他Web元素和设置overlay的同层标签。 | 1109 1110- 应用侧代码: 1111 ```ts 1112 import { webview } from '@kit.ArkWeb'; 1113 import { UIContext } from '@kit.ArkUI'; 1114 import { NodeController, BuilderNode, NodeRenderType, FrameNode } from '@kit.ArkUI'; 1115 1116 @Observed 1117 declare class Params{ 1118 elementId: string 1119 textOne: string 1120 textTwo: string 1121 width: number 1122 height: number 1123 } 1124 1125 declare class NodeControllerParams { 1126 surfaceId: string 1127 type: string 1128 renderType: NodeRenderType 1129 embedId: string 1130 width: number 1131 height: number 1132 } 1133 1134 // 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。 1135 class MyNodeController extends NodeController { 1136 private rootNode: BuilderNode<[Params]> | undefined | null; 1137 private embedId_: string = ""; 1138 private surfaceId_: string = ""; 1139 private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY; 1140 private width_: number = 0; 1141 private height_: number = 0; 1142 private type_: string = ""; 1143 private isDestroy_: boolean = false; 1144 1145 setRenderOption(params: NodeControllerParams) { 1146 this.surfaceId_ = params.surfaceId; 1147 this.renderType_ = params.renderType; 1148 this.embedId_ = params.embedId; 1149 this.width_ = params.width; 1150 this.height_ = params.height; 1151 this.type_ = params.type; 1152 } 1153 1154 // 必须要重写的方法,用于构建节点数、返回节点数挂载在对应NodeContainer中。 1155 // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新。 1156 makeNode(uiContext: UIContext): FrameNode | null { 1157 if (this.isDestroy_) { // rootNode为null。 1158 return null; 1159 } 1160 if (!this.rootNode) {// rootNode 为undefined时。 1161 this.rootNode = new BuilderNode(uiContext, { surfaceId: this.surfaceId_, type: this.renderType_ }); 1162 if (this.type_ == 'native/view1') { 1163 this.rootNode.build(wrapBuilder(TextInputBuilder1), { textOne: "myTextInput", width: this.width_, height: this.height_ }) 1164 return this.rootNode.getFrameNode(); 1165 } else if (this.type_ == 'native/view2') { 1166 this.rootNode.build(wrapBuilder(TextInputBuilder2), { textOne: "myTextInput", width: this.width_, height: this.height_ }) 1167 return this.rootNode.getFrameNode(); 1168 } else{ 1169 return null; 1170 } 1171 } 1172 // 返回FrameNode节点。 1173 return this.rootNode.getFrameNode(); 1174 } 1175 1176 updateNode(arg: Object): void { 1177 this.rootNode?.update(arg); 1178 } 1179 1180 getEmbedId(): string { 1181 return this.embedId_; 1182 } 1183 1184 setDestroy(isDestroy: boolean): void { 1185 this.isDestroy_ = isDestroy; 1186 if (this.isDestroy_) { 1187 this.rootNode = null; 1188 } 1189 } 1190 1191 postEvent(event: TouchEvent | undefined): boolean { 1192 return this.rootNode?.postTouchEvent(event) as boolean 1193 } 1194 } 1195 1196 @Component 1197 struct TextInputComponent1 { 1198 @Prop params: Params; 1199 @State bkColor: Color = Color.White; 1200 1201 build() { 1202 Column() { 1203 Text("display:overlay-infinity") 1204 TextInput({text: '', placeholder: 'please input your word...'}) 1205 .placeholderColor(Color.Gray) 1206 .id(this.params?.elementId) 1207 .placeholderFont({size: 13, weight: 400}) 1208 .caretColor(Color.Gray) 1209 .fontSize(14) 1210 .fontColor(Color.Black) 1211 } 1212 // 自定义组件中的最外层容器组件宽高应该为同层标签的宽高。 1213 .width(this.params.width) 1214 .height(this.params.height) 1215 } 1216 } 1217 1218 // @Builder中为动态组件的具体组件内容。 1219 @Builder 1220 function TextInputBuilder1(params:Params) { 1221 TextInputComponent1({params: params}) 1222 .width(params.width) 1223 .height(params.height) 1224 .backgroundColor(Color.Pink) 1225 } 1226 1227 @Component 1228 struct TextInputComponent2 { 1229 @Prop params: Params; 1230 @State bkColor: Color = Color.White; 1231 1232 build() { 1233 Column() { 1234 Text("display:overlay") 1235 TextInput({text: '', placeholder: 'please input your word...'}) 1236 .placeholderColor(Color.Gray) 1237 .id(this.params?.elementId) 1238 .placeholderFont({size: 13, weight: 400}) 1239 .caretColor(Color.Gray) 1240 .fontSize(14) 1241 .fontColor(Color.Black) 1242 } 1243 // 自定义组件中的最外层容器组件宽高应该为同层标签的宽高。 1244 .width(this.params.width) 1245 .height(this.params.height) 1246 } 1247 } 1248 1249 1250 // @Builder中为动态组件的具体组件内容。 1251 @Builder 1252 function TextInputBuilder2(params:Params) { 1253 TextInputComponent2({params: params}) 1254 .width(params.width) 1255 .height(params.height) 1256 .backgroundColor(Color.Gray) 1257 } 1258 1259 @Entry 1260 @Component 1261 struct Page{ 1262 browserTabController: webview.WebviewController = new webview.WebviewController(); 1263 private nodeControllerMap: Map<string, MyNodeController> = new Map(); 1264 @State componentIdArr: Array<string> = []; 1265 @State widthMap: Map<string, number> = new Map(); 1266 @State heightMap: Map<string, number> = new Map(); 1267 @State positionMap: Map<string, Edges> = new Map(); 1268 @State edges: Edges = {}; 1269 uiContext: UIContext = this.getUIContext(); 1270 1271 build() { 1272 Row() { 1273 Column() { 1274 Stack() { 1275 ForEach(this.componentIdArr, (componentId: string) => { 1276 NodeContainer(this.nodeControllerMap.get(componentId)) 1277 .position(this.positionMap.get(componentId)) 1278 .width(this.widthMap.get(componentId)) 1279 .height(this.heightMap.get(componentId)) 1280 }, (embedId: string) => embedId) 1281 // Web组件加载本地text.html页面。 1282 Web({src: $rawfile("overlay.html"), controller: this.browserTabController}) 1283 // 配置同层渲染开关开启。 1284 .enableNativeEmbedMode(true) 1285 // 获取<embed>标签的生命周期变化数据。 1286 .onNativeEmbedLifecycleChange((embed) => { 1287 console.info("NativeEmbed surfaceId" + embed.surfaceId); 1288 // 如果使用embed.info.id作为映射nodeController的key,请在h5页面显式指定id。 1289 const componentId = embed.info?.id?.toString() as string 1290 if (embed.status == NativeEmbedStatus.CREATE) { 1291 console.info("NativeEmbed create" + JSON.stringify(embed.info)); 1292 // 创建节点控制器、设置参数并rebuild。 1293 let nodeController = new MyNodeController() 1294 // embed.info.width和embed.info.height单位是px格式,需要转换成ets侧的默认单位vp。 1295 nodeController.setRenderOption({surfaceId : embed.surfaceId as string, 1296 type : embed.info?.type as string, 1297 renderType : NodeRenderType.RENDER_TYPE_TEXTURE, 1298 embedId : embed.embedId as string, 1299 width : this.uiContext.px2vp(embed.info?.width), 1300 height : this.uiContext.px2vp(embed.info?.height)}) 1301 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 1302 nodeController.setDestroy(false); 1303 // 根据web传入的embed的id属性作为key,将nodeController存入Map。 1304 this.nodeControllerMap.set(componentId, nodeController); 1305 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 1306 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 1307 this.positionMap.set(componentId, this.edges); 1308 // 将web传入的embed的id属性存入@State状态数组变量中,用于动态创建nodeContainer节点容器,需要将push动作放在set之后。 1309 this.componentIdArr.push(componentId) 1310 } else if (embed.status == NativeEmbedStatus.UPDATE) { 1311 let nodeController = this.nodeControllerMap.get(componentId); 1312 console.info("NativeEmbed update" + JSON.stringify(embed)); 1313 this.edges = {left: `${embed.info?.position?.x as number}px`, top: `${embed.info?.position?.y as number}px`} 1314 this.positionMap.set(componentId, this.edges); 1315 this.widthMap.set(componentId, this.uiContext.px2vp(embed.info?.width)); 1316 this.heightMap.set(componentId, this.uiContext.px2vp(embed.info?.height)); 1317 nodeController?.updateNode({textOne: 'update', width: this.uiContext.px2vp(embed.info?.width), height: this.uiContext.px2vp(embed.info?.height)} as ESObject) 1318 } else if (embed.status == NativeEmbedStatus.DESTROY) { 1319 console.info("NativeEmbed destroy" + JSON.stringify(embed)); 1320 let nodeController = this.nodeControllerMap.get(componentId); 1321 nodeController?.setDestroy(true); 1322 this.nodeControllerMap.delete(componentId); 1323 this.positionMap.delete(componentId); 1324 this.widthMap.delete(componentId); 1325 this.heightMap.delete(componentId); 1326 this.componentIdArr = this.componentIdArr.filter((value: string) => value !== componentId); 1327 } else { 1328 console.info("NativeEmbed status" + embed.status); 1329 } 1330 })// 获取同层渲染组件触摸事件信息。 1331 .onNativeEmbedGestureEvent((touch) => { 1332 console.info("NativeEmbed onNativeEmbedGestureEvent" + JSON.stringify(touch.touchEvent)); 1333 this.componentIdArr.forEach((componentId: string) => { 1334 let nodeController = this.nodeControllerMap.get(componentId); 1335 // 将获取到的同层区域的事件发送到该区域embedId对应的nodeController上。 1336 if(nodeController?.getEmbedId() == touch.embedId) { 1337 let ret = nodeController?.postEvent(touch.touchEvent) 1338 if(ret) { 1339 console.info("onNativeEmbedGestureEvent success " + componentId); 1340 } else { 1341 console.info("onNativeEmbedGestureEvent fail " + componentId); 1342 } 1343 if(touch.result) { 1344 // 通知Web组件手势事件消费结果。 1345 touch.result.setGestureEventResult(ret); 1346 } 1347 } 1348 }) 1349 }) 1350 .border({width: 2, color: Color.Gray}) 1351 .height("50%") 1352 } 1353 } 1354 } 1355 } 1356 } 1357 ``` 1358 1359- 前端页面示例: 1360 1361 示例代码使用<embed>标签,若使用<object>标签,请在ets侧注册<object>标签及type类型。 1362 ```html 1363 <!--HAP's src/main/resources/rawfile/overlay.html--> 1364 <!DOCTYPE html> 1365 <html> 1366 <head> 1367 <title>同层渲染html</title> 1368 <meta name="viewport" content="initial-scale=1.0"> 1369 </head> 1370 <body> 1371 <div> 1372 <div id = "test" style = "position: absolute; z-index: 9999; text-align: center; background-color: rgb(61, 157, 180); top: 40px; left: 30px; width: 300px; height: 120px"> 1373 z-index: 9999 1374 </div> 1375 1376 <embed id = "input1" type = "native/view1" arkwebnativestyle = "display:overlay-infinity" style = "position: absolute; top: 60px; left: 50px; width: 300px; height: 100px"> 1377 1378 <embed id = "input2" type = "native/view2" arkwebnativestyle = "display:overlay" style = "position: absolute; top: 150px; left: 40px; width: 300px; height: 100px"> 1379 </div> 1380 </body> 1381 </html> 1382 ``` 1383 1384- 实现效果: 1385 1386 未设置arkwebnativestyle的display属性: 1387 1388  1389 1390 设置arkwebnativestyle的display属性: 1391 1392  1393 1394## 常见问题 1395### 同层渲染组件被拉伸该如何解决? 1396 1397- 组件高度过大 1398 1399 受GPU限制,同层标签存在8000px的高度限制,如果html5中同层标签高度过高,会存在组件被拉伸的情况,这时需要将同层标签的高度设为8000px以下。 1400 1401- 自定义组件宽高未指定为同层渲染标签的宽高 1402 1403 自定义的同层渲染组件宽高需要与同层标签的宽高保持一致,示例如下: 1404 ```ts 1405 @Component 1406 struct TextInputComponent { 1407 @Prop params: Params 1408 @State bkColor: Color = Color.White 1409 1410 build() { 1411 Column() { 1412 TextInput({text: '', placeholder: 'please input your word...'}) 1413 .fontColor(Color.Black) 1414 } 1415 // 自定义组件中的最外层容器组件宽高应该为同层标签的宽高。 1416 .width(this.params.width) 1417 .height(this.params.height) 1418 } 1419 } 1420 ``` 1421 1422### 如何将同层渲染组件捕获到的事件透传到web前端? 1423同层渲染手势事件通过[setGestureEventResult()](../reference/apis-arkweb/arkts-basic-components-web-EventResult.md#setgestureeventresult14)设置手势事件消费结果,可以选择系统组件侧或ArkWeb侧消费手势事件。如果要实现系统组件侧和ArkWeb侧同时消费手势事件,可以在[setGestureEventResult()](../reference/apis-arkweb/arkts-basic-components-web-EventResult.md#setgestureeventresult14)中将stopPropagation设置为false,即系统组件侧消费的同时可以将手势事件向上冒泡给ArkWeb。 1424 1425### 同层渲染页面显示该插件不支持该如何解决? 1426 1427- 同层渲染开关[enableNativeEmbedMode](../reference/apis-arkweb/arkts-basic-components-web-attributes.md#enablenativeembedmode11)未开启 1428 1429 使用同层渲染技术需要显式开启同层渲染开关 1430 ```ts 1431 Web({ src: $rawfile("text.html"), controller: this.controller }) 1432 // 配置同层渲染开关开启。 1433 .enableNativeEmbedMode(true) 1434 ``` 1435 1436- 同层标签使用有误 1437 1438 如果使用<embed>标签,需要显式书写embed,并且type类型以"native/"开头;如果使用<object>标签,需要注册<object>标签及type类型。 1439 1440### 涉及界面交互的ArkUI组件(如TextInput等)光标与输入框错位该如何解决? 1441首先,需使用Stack包裹同层组件容器和BuilderNode。其次,同层组件容器NodeContainer应与同层标签的位置绑定。示例如下: 1442```ts 1443ForEach(this.componentIdArr, (componentId: string) => { 1444 NodeContainer(this.nodeControllerMap.get(componentId)) 1445 // 同层组件容器应与同层标签的宽高和位置绑定。 1446 .position(this.positionMap.get(componentId)) 1447 .width(this.widthMap.get(componentId)) 1448 .height(this.heightMap.get(componentId)) 1449}, (embedId: string) => embedId) 1450```