HarmonyOS 6.1 List/Grid空分支占位特性深度解析与实践
在现代移动应用开发中,长列表的渲染性能直接决定了应用的流畅度。HarmonyOS的ArkUI框架通过LazyForEach和Repeat组件实现了高效的虚拟滚动,但在过去,当列表项的条件分支返回空时(例如广告位隐藏),开发者不得不使用高度为0的ListItem或visibility(None)等“脏方案”来避免渲染引擎报错,这不仅增加了无效节点开销,还污染了UI结构。HarmonyOS NEXT 6.1 (API 23) 从框架层面解决了这一痛点:为List和Grid组件新增了supportEmptyBranchInLazyLoading特性。该特性允许在惰性加载中合法返回空分支,底层自动注入一个零尺寸虚拟代理对象(高度/宽度恒为0vp),并正确参与列表的排布计算,从而保证后续节点的坐标准确无误,滑动依然丝滑。
API使用方面,该接口挂载在ListAttribute和GridAttribute上:
supportEmptyBranchInLazyLoading(supported: boolean | undefined)
参数为true时启用空分支支持,false或undefined则回退旧版行为。需注意:这是一个“初次赋值不可变属性”,必须在第一帧写入后固定,后续无法通过状态变量动态切换。若需修改,只能销毁并重建组件树。
对于使用Native C-API的团队,ArkUI提供了对应的枚举值:
- List: NODE_LIST_SUPPORT_EMPTY_BRANCH_IN_LAZY_LOADING
- Grid: NODE_GRID_SUPPORT_EMPTY_BRANCH_IN_LAZY_LOADING
此外,该特性同样适用于开启了virtualScroll: true的Repeat组件,因为Repeat底层与LazyForEach共用一套虚拟滚动逻辑。若Repeat未开启虚拟滚动,则行为降级为普通ForEach,该配置不生效。
实战中,我们构建了一个动态广告位智能收缩面板Demo:一个包含100条数据的信息流,每逢5或7的倍数项模拟为过期广告位(需要隐藏)。在API 23下,我们只需在if条件中直接留空else分支,无需额外占位组件。代码如下:
import { router } from '@kit.ArkUI';
class SimpleDataSource implements IDataSource {
private list: number[] = [];
private listeners: DataChangeListener[] = [];
constructor(count: number) {
for (let i = 1; i <= count; i++) {
this.list.push(i);
}
}
totalCount(): number {
return this.list.length;
}
getData(index: number): number {
return this.list;
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
this.listeners.push(listener);
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
this.listeners.splice(pos, 1);
}
}
}
@Entry
@Component
struct LazyEmptyBranchDetail {
private dataSource: SimpleDataSource = new SimpleDataSource(100);
@State isEnableEmptyBranch: boolean = true;
@State currentLog: string = '核心管线就绪,等待观察0尺寸排版表现。';
isAdSlotNeedHide(val: number): boolean {
// 每5或7的倍数项隐藏
return val % 5 === 0 || val % 7 === 0;
}
build() {
Column() {
// 顶栏略...
List({ space: 12 }) {
LazyForEach(this.dataSource, (item: number) => {
if (!this.isAdSlotNeedHide(item)) {
ListItem() {
// 正常卡片内容
Row() {
Text(`流水项 ID: #${item}`).fontColor('#FFFFFF').fontSize(16);
Blank();
Text('ACTIVE').fontColor('#4CAF50').fontSize(12);
}
}
} else {
if (this.isEnableEmptyBranch) {
// 空分支:什么都不渲染,底层自动处理为0尺寸节点
} else {
// 兼容模式:需要占位节点
ListItem() {
Text(`[兼容层占位] ID #${item}`).fontColor('#666').fontSize(11);
}
}
}
}, (item: number) => item.toString())
}
.width('100%')
.layoutWeight(1)
// 正式启用时,取消注释以下属性
// .supportEmptyBranchInLazyLoading(true)
.edgeEffect(EdgeEffect.Spring)
.scrollBar(BarState.On)
}
.height('100%')
.width('100%')
.backgroundColor('#000000')
}
}
运行效果:开启空分支特性后,隐藏项的位置自动压缩为0高度,相邻卡片紧密衔接,滚动流畅;关闭后则出现多余的占位节点,破坏视觉一致性。
避坑指南:
1. 全局space属性可能导致连续空分支之间产生多余间距。例如连续3个空项且space=12,会形成36vp的空白区域。解法是将间距从父容器space改为每个ListItem的margin。
2. 初次赋值铁律:supportEmptyBranchInLazyLoading不能在运行时动态切换。若需从支持切换到不支持,必须销毁并重建List/Grid组件。建议在全局配置中写死为true。
总结:API 23的空分支支持从框架层面消除了条件渲染的脏逻辑,让长列表开发更符合直觉,显著提升了电商瀑布流、动态feed等场景的开发体验。鸿蒙引擎正在朝着精细化的排版哲学迈进。
Re: HarmonyOS 6.1 List/Grid空分支占位特性深度解析与实践
这个特性来得太及时了!之前为了处理列表项动态隐藏,要么塞个零高度ListItem,要么用visibility加上固定占位,不仅代码冗余,还经常因为布局计算问题导致滚动卡顿。支持空分支后,直接在if里留空else分支就能让框架自动注入零尺寸代理,确实清爽很多。 想请教一下楼主:这个特性在Grid的多列场景下表现如何?比如网格布局中某一行某个单元格隐藏,零尺寸节点是否会影响同一行其他单元格的排列?另外,代码里SimpleDataSource的getData方法return了this.list(整个数组)而不是this.list,可能是笔误,实际应该返回this.list才对。期待楼主后续分享更多实战案例!Re: HarmonyOS 6.1 List/Grid空分支占位特性深度解析与实践
这个新特性太实用了!以前做列表项条件隐藏,要么留个高度为0的ListItem占位,要么用visibility控制,总感觉不够优雅。现在ArkUI框架层面直接支持空分支,还能自动处理虚拟滚动排布,确实能减少无效节点开销。感谢楼主的详尽解析和实战Demo,代码示例很清晰,特别是指出“初次赋值不可变属性”这个坑,对实际开发帮助很大。顺便问一下,如果启用这个特性后,列表的滚动偏移计算会受影响吗?比如占位节点占0高度后,后续Item的定位和旧版用占位节点相比,会不会出现闪烁?Re: HarmonyOS 6.1 List/Grid空分支占位特性深度解析与实践
感谢楼主的深度解析!这个 `supportEmptyBranchInLazyLoading` 特性确实解决了之前用零高度占位或 visibility 隐藏带来的维护痛点,尤其是广告位动态隐藏的场景,代码干净了很多。想问一下,实际测试中相比旧方案,在大量空分支(比如隐藏比例较高)时,滑动的流畅度提升明显吗?另外,这个零尺寸虚拟代理在排版计算上会不会引入额外的性能开销?
页:
[1]