鸿蒙 自定义UI之占位节点
本文介绍了ArkUI中的两种占位节点:NodeContainer和ContentSlot。NodeContainer是容器节点,支持通用属性,采用类似Stack的布局;ContentSlot是语法节点,继承父容器布局。文章详细解析了NodeController的生命周期管理、自定义节点类型及创建方式,并通过示例展示了基本使用流程和节点移动的正确做法。特别强调了节点唯一性原则,指出一个节点只能有一个
本文同步发表于 微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
一、占位节点
-
用于在声明式节点树上为自定义节点树预留位置的节点
-
主要类型:NodeContainer 和 ContentSlot
为什么需要占位节点
-
页面的主树采用声明式节点树(通过ArkTS组件描述)
-
自定义节点使用命令式方式创建
-
只有通过占位节点,才能将命令式创建的自定义节点挂载到声明式节点树上
二、两种占位节点对比
| 特性 | NodeContainer | ContentSlot |
|---|---|---|
| 节点类型 | 容器节点(UI节点) | 语法节点 |
| 通用属性 | 支持(如width、height等) | 不支持 |
| 布局参与 | 参与布局 | 不参与布局 |
| 布局规则 | 类似左上角对齐的Stack组件 | 子节点按父容器布局规则排列 |
| 适用场景 | 一般自定义节点场景 | 混合模式开发(ArkTS容器 + Native子组件) |
| 子节点布局 | 不受父容器布局约束 | 受父容器布局约束 |
三、NodeContainer 解析
1. 特性
-
是系统组件,用于显示自定义节点树
-
支持所有通用属性
-
布局行为参考左上角对齐的Stack组件
-
默认布局方式:子节点堆叠在左上角
2. 使用方式
通过NodeController控制NodeContainer的内容
核心接口:NodeController
class MyNodeController extends NodeController {
makeNode(uiContext: UIContext): FrameNode | null
aboutToAppear()
aboutToDisappear()
aboutToResize(size: Size)
onTouchEvent(event: TouchEvent)
rebuild()
}
生命周期回调
| 回调方法 | 触发时机 | 用途 |
|---|---|---|
makeNode |
需要创建节点时 | 返回要挂载的自定义节点树根节点 |
aboutToAppear |
NodeContainer即将显示时 | 准备显示逻辑 |
aboutToDisappear |
NodeContainer即将消失时 | 清理资源 |
aboutToResize |
NodeContainer尺寸变化时 | 响应尺寸变化 |
onTouchEvent |
触摸事件发生时 | 处理触摸交互 |
rebuild |
需要重新构建节点时 | 手动触发重新构建 |
四、自定义节点类型
1. 支持的自定义节点
| 节点类型 | 描述 | 创建方式 |
|---|---|---|
| FrameNode | 自定义组件节点 | new FrameNode(uiContext) |
| RenderNode | 自定义渲染节点 | new RenderNode(uiContext) |
| BuilderNode | 自定义声明式节点 | new BuilderNode<T>(uiContext) |
| ComponentContent | 组件内容节点 | 通过接口获取 |
2. BuilderNode 示例
// 1. 定义Builder函数
@Builder
function buttonBuilder(params: Params) {
Column() {
Button(params.text)
.fontSize(12)
.borderRadius(8)
.borderWidth(2)
.backgroundColor(Color.Orange)
}
}
// 2. 创建BuilderNode
let buttonNode = new BuilderNode<[Params]>(uiContext);
buttonNode.build(wrapBuilder(buttonBuilder), { text: "This is a Button" });
// 3. 获取FrameNode
let frameNode = buttonNode.getFrameNode();
五、使用流程
示例1:基本使用
// 1. 创建NodeController子类
class MyNodeController extends NodeController {
private isShow: boolean = false;
// 构造函数
constructor(isShow: boolean) {
super();
this.isShow = isShow;
}
// 创建节点
makeNode(uiContext: UIContext): FrameNode | null {
if (!this.isShow) {
return null;
}
// 创建或获取自定义节点
let frameNode = getOrCreateNode(uiContext)?.getFrameNode();
return frameNode ? frameNode : null;
}
// 生命周期回调
aboutToAppear() {
console.log('NodeContainer即将显示');
}
aboutToDisappear() {
console.log('NodeContainer即将消失');
}
aboutToResize(size: Size) {
console.log(`尺寸变化: ${size.width} x ${size.height}`);
}
// 控制方法
toShow() {
this.isShow = true;
this.rebuild(); // 触发重新构建
}
toHide() {
this.isShow = false;
this.rebuild(); // 触发重新构建
}
}
// 2. 在声明式组件中使用
@Entry
@Component
struct Index {
private myNodeController: MyNodeController = new MyNodeController(true);
build() {
Column() {
NodeContainer(this.myNodeController)
.width("100%")
.height("40%")
.backgroundColor(Color.Brown)
Button("切换显示")
.onClick(() => {
this.myNodeController.toHide();
})
}
}
}
示例2:节点移动(避免重复挂载)
Button("移动按钮位置")
.onClick(() => {
// 正确做法:先下树,再上树
this.myNodeController1.toHide(); // 从原位置移除
this.myNodeController2.toShow(); // 添加到新位置
})
六、ContentSlot 使用示例
ContentSlot特点
-
只是语法节点,无通用属性
-
子节点按父容器规则布局
-
适用于混合模式开发
@Entry
@Component
struct Index {
controller = new NodeContentCtrl(this.getUIContext());
build() {
Column() {
// ContentSlot中的节点会按Column布局排列
ContentSlot(this.controller.GetContent())
Button("添加到Slot")
.onClick(() => {
this.controller.AddNode();
})
}
}
}
七、注意事项
1. 节点挂载限制
// NodeContainer只能挂载以下节点:
// 1. 自定义的FrameNode节点
// 2. BuilderNode创建的组件树的根节点
// 3. 不支持代理节点挂载(API Version 12+)
2. 节点唯一性原则
-
一个节点只能有一个父节点
-
如果将同一个节点挂载到多个NodeContainer:
-
仅一个容器会显示节点
-
多个容器的属性更新都会影响该节点
-
可能导致显示异常或功能异常
-
//错误示例:重复挂载同一节点
let sharedNode = createNode(uiContext);
controller1.setNode(sharedNode);
controller2.setNode(sharedNode); // 会导致问题
//正确做法:每个容器创建独立节点
controller1.setNode(createNode(uiContext));
controller2.setNode(createNode(uiContext));
3. 特殊场景处理
| 场景 | 处理方式 |
|---|---|
| 页面路由 | 确保节点正确挂载/卸载 |
| 动效场景 | 避免节点共享导致的动画异常 |
| 动态显示 | 使用rebuild()方法刷新 |
4. API Version 12+ 变化
-
可以通过FrameNode查询接口获取系统组件的代理节点
-
代理节点不能作为makeNode的返回值
-
代理节点无法成功挂载到组件树
八、布局差异对比
// ContentSlot示例:子节点按Column布局
Column() {
ContentSlot(content) // 子节点会垂直排列
// 添加的Text节点会按Column规则布局
}
// NodeContainer示例:子节点按Stack布局
Column() {
NodeContainer(controller) // 子节点堆叠在左上角
// 添加的Text节点会重叠在一起
}
总结
-
占位节点是桥梁:连接声明式UI和命令式自定义节点
-
两种选择:
-
NodeContainer:完整容器,Stack布局
-
ContentSlot:语法占位,继承父布局
-
-
核心控制器:NodeController管理节点生命周期
-
节点唯一性:一个节点只能属于一个父节点
-
适用场景:
-
复杂自定义UI组件
-
动态节点管理
-
混合开发模式
-
通过使用NodeContainer和ContentSlot,可以在ArkUI中灵活地结合声明式和命令式编程模式,实现复杂的自定义UI效果。
更多推荐



所有评论(0)