鸿蒙原生 ArkTS 布局深度解析:List 空状态占位 emptyState 实战
2026/6/26 17:43:12 网站建设 项目流程

鸿蒙原生 ArkTS 布局深度解析:List 空状态占位 emptyState 实战




一、引言:为什么「空状态」如此重要?

在移动应用开发中,空状态(Empty State)是指列表、搜索结果等数据容器在没有任何内容时呈现的界面。很多开发者容易忽视这个边界场景,直接将空白页面丢给用户——这会给体验带来明显降级。

1.1 空状态的三种常见形态

类型说明示例
首次使用用户刚安装应用,尚无数据待办清单首次打开
清空/完成用户主动将数据消耗完毕收件箱全部归档
无结果搜索或筛选没有命中数据搜索「XYZ」无匹配项

1.2 优秀空状态设计四原则

  • 引导性:告诉用户这里应该有什么、可以做什么
  • 情感化:通过图形、文案传递友好态度,降低挫败感
  • 可操作性:提供明确的下一步入口(新建、添加、刷新)
  • 品牌一致性:配色与字体与 App 整体调性统一

二、HarmonyOS NEXT API 24 的 List + emptyState 方案

在早期 SDK 中,开发者实现空状态需借助if/else条件渲染手动切换。当多个列表各自需要空状态时,模板判断代码重复度高。

API 24(SDK 7.x)引入了List组件的.emptyState()属性,这是 ArkUI 内置的声明式空状态解决方案。

2.1 核心 API

/** * 当 List 子组件数量为 0 时,自动展示占位 UI, * 数据恢复后自动隐藏。 */emptyState(value:CustomBuilder):ListAttribute

2.2 与传统方案对比

维度if/else 旧方案emptyState API 24
代码量每个 List 需额外 if 分支一行链式调用的
可维护性多列表时重复判断声明式绑定,关注点分离
语义清晰度需阅读逻辑分支命名即语义

三、场景设计:待办清单 App

3.1 功能需求

  • 展示待办事项列表(Checkbox + 内容 + 删除按钮)
  • 完成态自动添加删除线
  • 列表为空时显示友好占位提示
  • 提供「清空列表」和「恢复示例数据」用于状态切换
  • 单条删除时若列表全部清空弹出反馈

3.2 数据模型

interfaceTodoItem{id:number;content:string;isDone:boolean;}

四、完整代码实现

以下代码基于 HarmonyOS NEXT API 24,使用List.emptyState()原生 API。

/** * 鸿蒙 ArkTS —— List + emptyState 空状态占位示例 * 适用:HarmonyOS NEXT API 24 (SDK 7.x) */import{promptAction}from'@kit.ArkUI';import{hilog}from'@kit.PerformanceAnalysisKit';interfaceTodoItem{id:number;content:string;isDone:boolean;}@Entry@Componentstruct TodoListPage{@StateprivatetodoList:TodoItem[]=[{id:1,content:'学习鸿蒙 ArkTS 语法',isDone:true},{id:2,content:'掌握 List.emptyState API',isDone:false},{id:3,content:'编写完整示例应用',isDone:false},];/** 空状态占位 UI 构建器 */@BuilderemptyStateBuilder(){Column(){SymbolGlyph($r('sys.symbol.inbox')).fontSize(72).fontColor(['#BBBBBB'])Blank()Text('暂无待办事项').fontSize(18).fontColor('#666666').fontWeight(FontWeight.Medium).margin({top:16})Text('点击下方按钮添加一条新的待办吧').fontSize(14).fontColor('#999999').margin({top:8})Button('添加示例数据').type(ButtonType.Capsule).height(40).width(160).margin({top:24}).onClick(()=>{this.loadSampleData();})}.alignItems(HorizontalAlign.Center).justifyContent(FlexAlign.Center).width('100%').height('100%')}/** 列表项卡片构建器 */@BuildertodoItemBuilder(item:TodoItem,index:number){Row(){Checkbox().select(item.isDone).shape(CheckBoxShape.CIRCLE).size({width:22,height:22}).onChange((v:boolean)=>{this.todoList[index].isDone=v;})Text(item.content).fontSize(16).fontColor(item.isDone?'#BBBBBB':'#333333').decoration({type:item.isDone?TextDecorationType.LineThrough:TextDecorationType.None}).margin({left:12}).flexGrow(1)Button({type:ButtonType.Circle,stateEffect:true}){Text('✕').fontSize(16).fontColor('#FF6B6B')}.width(32).height(32).backgroundColor('rgba(255,107,107,0.1)').onClick(()=>{this.deleteItem(index);})}.width('100%').height(56).padding({left:16,right:12}).alignItems(VerticalAlign.Center).backgroundColor(Color.White).borderRadius(12)}build(){Column(){// 标题栏Column(){Text('📋 我的待办').fontSize(22).fontWeight(FontWeight.Bold).fontColor('#333333')Text('List + emptyState 示例').fontSize(12).fontColor('#999999').margin({top:4})}.width('100%').padding({top:24,bottom:12,left:20,right:20})// ===== 核心:List + emptyState =====List({space:10}){ForEach(this.todoList,(item:TodoItem,index?:number)=>{ListItem(){this.todoItemBuilder(item,indexasnumber)}},(item:TodoItem)=>item.id.toString())}.width('100%').layoutWeight(1).padding({left:16,right:16,top:8}).backgroundColor('#F5F5F5').emptyState(this.emptyStateBuilder)// 绑定空状态占位// 底部操作栏Row({space:16}){Button('清空列表').type(ButtonType.Outlined).height(44).layoutWeight(1).fontSize(15).onClick(()=>{this.clearList();})Button('恢复示例数据').type(ButtonType.Capsule).height(44).layoutWeight(1).fontSize(15).onClick(()=>{this.loadSampleData();})}.width('100%').padding(16).backgroundColor(Color.White)}.width('100%').height('100%').backgroundColor('#F5F5F5')}privateshowToast(msg:string):void{try{promptAction.showToast({message:msg,duration:1500});}catch(err){hilog.error(0x0001,'Page','showToast failed: %{public}s',JSON.stringify(err));}}privateclearList():void{this.todoList=[];this.showToast('列表已清空,空状态已触发');}privateloadSampleData():void{constnow=Date.now();this.todoList=[{id:now+1,content:'学习鸿蒙 ArkTS 语法',isDone:true},{id:now+2,content:'掌握 List.emptyState API',isDone:false},{id:now+3,content:'编写完整示例应用',isDone:false},];}privatedeleteItem(index:number):void{this.todoList.splice(index,1);if(this.todoList.length===0)this.showToast('全部清空 🎯');}}

五、代码分层解析

5.1 状态层:@State todoList

@State装饰的todoList是整个页面的数据核心。数组内容变化时,ArkUI 自动触发 UI 重渲染:

  • this.todoList = []→ 清空 →ForEach无数据 →emptyState激活
  • this.todoList = [...]→ 恢复 →ForEach有数据 →emptyState隐去

5.2 视图层:两个@Builder

构建器渲染条件用途
emptyStateBuilder列表为空图标 + 提示文字 + 操作按钮
todoItemBuilder列表有数据复选框 + 文本 + 删除按钮

这种拆分让build()函数极其干净——List 只需关心「容器」角色。

5.3 控制层:.emptyState(this.emptyStateBuilder)

这是 API 24 的关键能力。emptyState是一个布林条件属性:

  • 条件 true(列表无数据):框架调用 builder 生成占位节点
  • 条件 false(列表有数据):框架销毁占位节点,正常渲染列表

开发者无需任何 if/else即可获得完整的空状态管理。

5.4 交互层:状态切换驱动

两个底部按钮分别触发clearList()loadSampleData()。空状态 UI 内部也放置了「添加示例数据」按钮,让用户不需要滚动到底部即可恢复数据——这是移动端空状态设计的黄金法则。


六、运行时效果预览

初始态(有数据):

┌─────────────────────────────┐ │ 📋 我的待办 │ ├─────────────────────────────┤ │ ○ 学习鸿蒙 ArkTS 语法 ✕ │ ← 已完成(灰色+删除线) │ ● 掌握 List.emptyState ✕ │ ← 未完成 │ ● 编写完整示例应用 ✕ │ ← 未完成 ├─────────────────────────────┤ │ [ 清空列表 ] [ 恢复示例数据 ] │ └─────────────────────────────┘

空状态(清空后):

┌─────────────────────────────┐ │ 📋 我的待办 │ ├─────────────────────────────┤ │ 📭 │ ← SymbolGlyph 图标 │ 暂无待办事项 │ ← 主提示 │ 点击下方按钮添加新的待办吧 │ ← 副提示 │ [ 添加示例数据 ] │ ← 操作入口 ├─────────────────────────────┤ │ [ 清空列表 ] [ 恢复示例数据 ] │ └─────────────────────────────┘

占位 UI 居于 List 区域正中央,视觉聚焦、层次分明。


七、进阶技巧与最佳实践

7.1 配合 LazyForEach

大数据量时应使用LazyForEach支持按需加载。emptyStateLazyForEach同样生效——当totalCount为 0 时自动触发。

List({space:10}){LazyForEach(this.dataSource,(item:TodoItem)=>{ListItem(){...}},(item:TodoItem)=>item.id.toString())}.emptyState(this.emptyStateBuilder)

7.2 空状态动效过渡

通过.transition()为 emptyState 的进出添加微动效:

.emptyState(this.emptyStateBuilder).transition(TransitionEffect.opacity(0.3))

7.3 多 List 独立空状态

页面中有多个List时,各自绑定自己的@Builder即可互不干扰:

Column(){List(...){...}.emptyState(this.categoryEmpty)List(...){...}.emptyState(this.todayEmpty)}

7.4 嵌入更多交互元素

空状态中可放置 Refresh 组件、图像动画、推荐词链接等,增强引导性。

7.5 多语言 / 主题适配

使用$r('app.string.xxx')引用资源文件,使空状态随系统语言和主题自动切换:

Text($r('app.string.empty_todo_title')).fontColor($r('sys.color.ohos_id_color_text_primary'))

八、性能注意事项

  1. 避免占位内包裹大图片:空状态触发时需快速渲染,建议使用矢量图标(SymbolGlyph / Text)
  2. @Builder 应为无参函数emptyState绑定的 builder 应为无参,如需动态数据通过@State间接获取
  3. 无需嵌套 Scroll:占位内容通常不滚动,保持精简即可
  4. 检查数据源类型:确保ForEach/LazyForEach的 dataSource 正确绑定,避免非预期触发

九、常见问题 FAQ

Q:emptyState 在有静态子组件时如何工作?

A:emptyState仅根据ForEach/LazyForEach绑定的数据源判断。List 中的静态ListItem不受影响。

Q:为什么我的 emptyState 不显示?

A:检查三点:

  • API 版本 ≥ 24
  • @Builder不带参数,通过this.xxxBuilder引用
  • ForEach的数据源确实为[]且绑定的是@State变量

Q:可以在 emptyState 中使用路由跳转吗?

A:可以。@Builder内部支持所有标准事件处理,包括router.pushUrl()NavPathStack跳转。


十、总结

HarmonyOS NEXT API 24 的List.emptyState()是 ArkUI 在声明式编程方向上的重要进化。它让开发者以一行代码的增量,获得原本需要多层模板判断才能实现的空状态管理能力。

核心收益:

维度收益
代码可读性语义化命名,一目了然
开发效率零模板代码,关注点分离
维护成本统一管理,一处修改全局生效
用户体验一致性占位设计,专为交互优化

更重要的是,emptyState体现出「状态驱动 UI」的设计哲学——开发者只需关心what(空状态长什么样),框架自动处理when(何时显示)和how(如何过渡与回收)。

希望本文能帮助你深入理解List + emptyState的使用方式,并在自己的 HarmonyOS NEXT 项目中落地这一优雅的设计模式。


本文由 AtomCode 撰写,发布于 2026 年 6 月。示例代码基于 HarmonyOS NEXT API 24(SDK 7.x),兼容 stage 模型。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询