文章目录
项目源码地址: https://gitcode.com/HarmonyOS_Samples/DynamicComponent
广告显示出来只是第一步。

这个项目更值得学的是:点击关闭后,广告怎么从动态节点树上下去。它不是简单改个 Visibility.Hidden,而是回到 NodeController 里控制节点返回值。
这篇拆 CloseAdDialog、nodeMap、remove() 和 rebuild()。
关闭按钮先打开确认弹窗
广告右上角的关闭按钮在 upperText() 里:
Stack() {
Circle()
.height($r('app.string.close_icon_size'))
.width($r('app.string.close_icon_size'))
Text($r('app.string.close_icon_symbol'))
}
.onClick(() => {
closeAdDialogController.open();
})
点击关闭按钮,不会立刻移除广告,而是打开确认弹窗。
效果是这样:

这个逻辑很像真实广告业务:用户点关闭,先确认是不是要屏蔽。
CustomDialogController 怎么创建
在 AdComponent 里,有这样一段:
closeAdDialogController: CustomDialogController = new CustomDialogController({
builder: CloseAdDialog({
adId: this.params!.id
}),
backgroundBlurStyle: BlurStyle.COMPONENT_THICK,
});
这里创建了一个弹窗控制器。
重点是 CloseAdDialog 收到了 adId。这个 adId 后面会用来找到对应广告的 AdNodeController。
没有这个 id,弹窗不知道自己要关闭哪一个广告。
弹窗里的取消按钮很普通
取消按钮只关闭弹窗:
Button($r('app.string.text_dialog_cancel'))
.onClick(() => {
this.dialogController.close();
})
这没什么复杂的。
真正关键的是“屏蔽”按钮。
屏蔽按钮会找回 AdNodeController
屏蔽按钮代码如下:
Button($r('app.string.text_dialog_shield'))
.onClick(() => {
let node: AdNodeController | undefined = nodeMap.get(this.adId);
if (node !== undefined) {
node.remove();
node.rebuild();
}
this.dialogController.close();
})
这段代码非常重要。
它做了三件事:
- 通过
adId从nodeMap找到广告 Controller - 调用
node.remove()标记广告要移除 - 调用
node.rebuild()让容器重新获取节点
最后关闭弹窗。
remove 只是改标记
AdController.ets 里的 remove() 很短:
remove() {
this.isRemove = true;
}
它只是把 isRemove 改成 true。
注意,只调用 remove() 不代表 UI 一定马上消失。它只是告诉 Controller:下次 makeNode() 被调用时,不要再返回广告节点。
makeNode 根据 isRemove 返回 null
再看 makeNode():

makeNode(): FrameNode | null {
if (this.isRemove) {
return null;
}
if (this.rootNode != null) {
return this.rootNode;
}
return null;
}
当 isRemove 是 true,makeNode() 返回 null。
NodeContainer 拿不到 FrameNode,广告自然就下树了。
这就是动态节点关闭的核心。
rebuild 负责重新触发 makeNode
为什么还要调用:
node.rebuild();
因为你改了 isRemove,但 NodeContainer 不一定马上知道。
rebuild() 的作用就是通知框架:这个 Controller 的节点状态变了,请重新调用 makeNode()。
于是流程变成:
点击屏蔽
remove() 把 isRemove 改成 true
rebuild() 触发 makeNode()
makeNode() 发现 isRemove 为 true
返回 null
NodeContainer 不再显示广告
这比直接隐藏更符合动态节点的设计。
为什么不用 Visibility.Hidden
如果只是普通组件,当然可以用:
.visibility(Visibility.Hidden)
但这里广告是动态创建出来的节点,而且可能包含视频。
如果只是隐藏,节点还在,资源也可能还在。对于广告这种可以被彻底关闭的模块,让 makeNode() 返回 null 更干净。
小白可以先记住:普通 UI 隐藏用状态,动态节点移除用 Controller。
这个设计有什么业务价值
真实广告系统里,关闭广告往往不只是 UI 消失,还可能要做:
- 上报关闭事件
- 记录用户不感兴趣
- 停止视频播放
- 清理广告节点
- 避免当前页面再次展示同一个广告
这些逻辑都放到 Controller 这条线上,比散落在页面里更好维护。
收个尾
这篇把广告关闭完整串起来了。
最关键的一句话是:remove() 只是改状态,rebuild() 才会让 NodeContainer 重新调用 makeNode(),而 makeNode() 返回 null 后,广告才真正下树。
下一篇我们看一个容易被忽略的细节:普通卡片 CardComponent 是怎么配合列表流工作的。动态广告不是孤立存在的,它必须和普通内容一起稳定滚动。

132

被折叠的 条评论
为什么被折叠?



