鸿蒙中 同层组件
摘要:鸿蒙系统通过同层渲染技术,让Web页面中的UI元素使用原生ArkUI组件渲染,提升性能和体验。该技术支持地图、输入框等组件在Web场景下的原生渲染,也适用于Flutter等三方框架。开发时需通过Web组件开启同层渲染模式,创建NodeController管理组件生命周期,并处理触摸/鼠标事件。技术限制包括同层标签数量≤5个、不支持同步渲染等。通过arkwebnativestyle属性可控制同
本文同步发表于我的微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
在鸿蒙系统中,应用可以使用Web组件加载网页。当非系统框架的UI组件功能或性能不如系统组件时,可使用同层渲染技术,通过ArkUI组件渲染这些组件(简称"同层组件")。
简单来说,就是让Web页面中的某些UI元素(如输入框、视频播放器等)使用鸿蒙原生组件来渲染,从而获得更好的性能和体验。
使用场景
1. Web网页场景
-
地图组件:使用ArkUI的XComponent组件渲染,提升性能
-
输入框组件:使用ArkUI的TextInput组件渲染,获得系统级输入体验
-
网页侧:开发者可将
<embed>、<object>标签按规则进行同层渲染
2. 三方UI框架场景
-
Flutter:PlatformView与Texture抽象组件可使用系统组件渲染
-
Weex2.0:Camera、Video和Canvas组件可使用系统组件渲染
整体架构
ArkWeb同层渲染特性提供两种核心能力:
-
同层标签生命周期管理:关联前端标签(
<embed>/<object>) -
事件命中转发处理:同层标签的事件上报到开发者侧,由开发者分发到对应组件树
架构流程图:
Web页面(embed/object标签)
↓ 识别同层标签
ArkWeb内核
↓ 生命周期回调/事件上报
应用开发者
↓ 创建NodeController/BuilderNode
ArkUI原生组件
↓ 渲染
界面展示
四、支持的ArkUI组件
1. 基础组件(部分列举)
-
文本类:Text、TextInput、TextArea、TextClock、TextPicker、TextTimer
-
按钮类:Button、Toggle、Radio、Checkbox、CheckboxGroup
-
选择器:DatePicker、TimePicker、CalendarPicker、Select
-
进度类:Progress、LoadingProgress、Slider、Rating
-
图片类:Image、ImageAnimator、ImageSpan
-
其他:Divider、Marquee、QRCode、PatternLock、Search
2. 容器类组件
-
布局类:Column、Row、Flex、Grid、List、Stack、RelativeContainer
-
滚动类:Scroll、Swiper、WaterFlow
-
分割类:ColumnSplit、RowSplit
-
特殊容器:Badge、Counter、Tabs、TabContent、SideBarContainer
3. 自绘制类组件
-
XComponent:支持Native绘制
-
Canvas:画布组件
-
Video:视频播放
-
Web:Web组件(仅支持一层嵌套)
4. 命令式自定义绘制节点
-
BuilderNode、ComponentContent、FrameNode、NodeController
-
RenderNode、XComponentNode、AttributeUpdater等
5. 不支持的通用属性
-
分布式迁移标识
-
特效绘制合并
注意:其他未明确标注不支持的属性与事件均默认支持
五、Web网页同层渲染规格
1. 支持的H5标签
| 标签类型 | 使用规则 |
|---|---|
<embed> |
type类型为"native/"前缀才识别为同层组件 |
<object> |
非标准MIME type,支持通过param/value自定义属性 |
不支持:W3C规范标准标签(如<input>、<video>)定义为同层标签
2. 支持的CSS属性
完整支持:
-
布局类:display、position、z-index、visibility、opacity
-
背景类:background-color、background-image
-
尺寸类:width、height
-
内边距:padding及四个方向
-
外边距:margin及四个方向
-
边框类:border系列(width/style/color/radius)
-
动画类:transition
-
变换:transform(仅支持translate/scale,scale参数≥0)
不支持:rotate、skew等变换属性
3. 生命周期管理
通过onNativeEmbedLifecycleChange()回调监听:
-
创建(CREATE)
-
销毁(DESTROY)
-
位置宽高变化(UPDATE)
-
Web页面前进后退缓存支持
4. 事件处理
触摸事件:
-
支持:TouchEvent的DOWN/UP/MOVE/CANCEL
-
可通过
setGestureEventResult()配置消费结果
鼠标事件:
-
通过
onNativeEmbedMouseEvent()回调 -
支持鼠标左键、中键、右键点击/长按
-
触摸板对应操作转换
暂不支持:
-
鼠标、键盘、触摸板事件直接上报
-
支持将鼠标/触摸板左键事件转换为触摸事件上报
5. 可见状态变化
-
通过
onNativeEmbedVisibilityChange()回调 -
支持同层标签相对于视口的可见状态上报
6. 约束限制
| 限制项 | 说明 |
|---|---|
| 同层标签数量 | ≤5个,超过性能下降 |
| 最大高度 | ≤8000px |
| 最大纹理大小 | ≤8000px |
| 渲染模式 | 不支持同步渲染模式 |
| Web嵌套 | 仅支持一层同层渲染嵌套 |
| 页面缩放 | 不支持缩放接口 |
六、开发实现
1. 前端HTML代码
方式一:使用<embed>标签
<!DOCTYPE html>
<html>
<head>
<title>同层渲染html</title>
<meta name="viewport">
</head>
<body style="background:white">
<embed id="input1" type="native/view"
style="width: 100%; height: 100px; margin: 30px; margin-top: 600px"/>
<embed id="input2" type="native/view2"
style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/>
<embed id="input3" type="native/view3"
style="width: 100%; height: 100px; margin: 30px; margin-top: 50px"/>
</body>
</html>
方式二:使用<object>标签(需注册)
<object id="input1" type="test/input"
style="width: 100%; height: 100px; margin: 30px; margin-top: 600px">
</object>
2. 应用侧配置
开启同层渲染权限
// module.json5
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
开启同层渲染开关
// xxx.ets
import { webview } from '@kit.ArkWeb';
@Entry
@Component
struct WebComponent {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({ src: 'www.example.com', controller: this.controller })
.enableNativeEmbedMode(true) // 开启同层渲染
.registerNativeEmbedRule("object", "test") // 注册object标签规则
}
}
}
3. 创建自定义组件
@Component
struct TextInputComponent {
@Prop params: Params
@State bkColor: Color = Color.White
build() {
Column() {
TextInput({
text: '',
placeholder: 'please input your word...'
})
.placeholderColor(Color.Gray)
.id(this.params?.elementId)
.placeholderFont({size: 13, weight: 400})
.caretColor(Color.Gray)
.width(this.params?.width)
.height(this.params?.height)
.fontSize(14)
.fontColor(Color.Black)
}
.width(this.params.width)
.height(this.params.height)
}
}
@Builder
function TextInputBuilder(params: Params) {
TextInputComponent({params: params})
.width(params.width)
.height(params.height)
.backgroundColor(Color.White)
}
4. 创建NodeController
class MyNodeController extends NodeController {
private rootNode: BuilderNode<[Params]> | undefined | null;
private embedId_: string = "";
private surfaceId_: string = "";
private renderType_: NodeRenderType = NodeRenderType.RENDER_TYPE_DISPLAY;
private width_: number = 0;
private height_: number = 0;
private type_: string = "";
private isDestroy_: boolean = false;
setRenderOption(params: NodeControllerParams) {
this.surfaceId_ = params.surfaceId;
this.renderType_ = params.renderType;
this.embedId_ = params.embedId;
this.width_ = params.width;
this.height_ = params.height;
this.type_ = params.type;
}
// 必须重写的方法
makeNode(uiContext: UIContext): FrameNode | null {
if (this.isDestroy_) {
return null;
}
if (!this.rootNode) {
this.rootNode = new BuilderNode(uiContext, {
surfaceId: this.surfaceId_,
type: this.renderType_
});
if (this.rootNode) {
this.rootNode.build(
wrapBuilder(TextInputBuilder),
{
textOne: "myTextInput",
width: this.width_,
height: this.height_
}
);
return this.rootNode.getFrameNode();
} else {
return null;
}
}
return this.rootNode.getFrameNode();
}
updateNode(arg: Object): void {
this.rootNode?.update(arg);
}
getEmbedId(): string {
return this.embedId_;
}
setDestroy(isDestroy: boolean): void {
this.isDestroy_ = isDestroy;
if (this.isDestroy_) {
this.rootNode = null;
}
}
postEvent(event: TouchEvent | undefined): boolean {
return this.rootNode?.postTouchEvent(event) as boolean;
}
}
5. 生命周期监听与事件处理
build() {
Row() {
Column() {
Stack() {
// 动态创建NodeContainer
ForEach(this.componentIdArr, (componentId: string) => {
NodeContainer(this.nodeControllerMap.get(componentId))
.position(this.positionMap.get(componentId))
.width(this.widthMap.get(componentId))
.height(this.heightMap.get(componentId))
}, (embedId: string) => embedId)
Web({ src: $rawfile("text.html"), controller: this.browserTabController })
.enableNativeEmbedMode(true)
.registerNativeEmbedRule("object", "test")
// 生命周期监听
.onNativeEmbedLifecycleChange((embed) => {
console.info("NativeEmbed surfaceId" + embed.surfaceId);
const componentId = embed.info?.id?.toString() as string;
if (embed.status == NativeEmbedStatus.CREATE) {
// 创建节点控制器
let nodeController = new MyNodeController();
nodeController.setRenderOption({
surfaceId: embed.surfaceId as string,
type: embed.info?.type as string,
renderType: NodeRenderType.RENDER_TYPE_TEXTURE,
embedId: embed.embedId as string,
width: this.uiContext.px2vp(embed.info?.width),
height: this.uiContext.px2vp(embed.info?.height)
});
// 存储节点信息
this.nodeControllerMap.set(componentId, nodeController);
this.componentIdArr.push(componentId);
} else if (embed.status == NativeEmbedStatus.UPDATE) {
// 更新节点
let nodeController = this.nodeControllerMap.get(componentId);
nodeController?.updateNode({
textOne: 'update',
width: this.uiContext.px2vp(embed.info?.width),
height: this.uiContext.px2vp(embed.info?.height)
} as ESObject);
} else if (embed.status == NativeEmbedStatus.DESTROY) {
// 销毁节点
let nodeController = this.nodeControllerMap.get(componentId);
nodeController?.setDestroy(true);
this.nodeControllerMap.delete(componentId);
this.componentIdArr = this.componentIdArr.filter(
(value: string) => value !== componentId
);
}
})
// 触摸事件监听
.onNativeEmbedGestureEvent((touch) => {
this.componentIdArr.forEach((componentId: string) => {
let nodeController = this.nodeControllerMap.get(componentId);
if (nodeController?.getEmbedId() == touch.embedId) {
let ret = nodeController?.postEvent(touch.touchEvent);
if (touch.result) {
touch.result.setGestureEventResult(ret);
}
}
});
})
// 鼠标事件监听
.onNativeEmbedMouseEvent((mouse) => {
this.componentIdArr.forEach((componentId: string) => {
let nodeController = this.nodeControllerMap.get(componentId);
if (nodeController?.getEmbedId() == mouse.embedId) {
let ret = nodeController?.postInputEvent(mouse.mouseEvent);
if (mouse.result) {
mouse.result.setMouseEventResult(ret);
}
}
});
})
}
}
}
}
七、控制同层标签层级
私有属性 arkwebnativestyle
该属性仅在开启同层渲染后的<embed>和<object>中生效:
| display取值 | 说明 |
|---|---|
| overlay | 设置同层标签层级高于其他Web元素 |
| overlay-infinity | 设置同层标签层级高于其他Web元素和设置overlay的同层标签 |
前端代码示例:
<!DOCTYPE html>
<html>
<body>
<div id="test" style="position: absolute; z-index: 9999; ...">
z-index: 9999
</div>
<!-- 最高层级 -->
<embed id="input1" type="native/view1"
arkwebnativestyle="display:overlay-infinity"
style="position: absolute; top: 60px; left: 50px; width: 300px; height: 100px">
<!-- 次高层级 -->
<embed id="input2" type="native/view2"
arkwebnativestyle="display:overlay"
style="position: absolute; top: 150px; left: 40px; width: 300px; height: 100px">
</body>
</html>
更多推荐




所有评论(0)