news 2026/6/10 9:44:34

HarmonyOS6 PC 开发实战:手风琴式展开折叠动画,让页面告别“一坨文字“

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS6 PC 开发实战:手风琴式展开折叠动画,让页面告别“一坨文字“

做PC端应用的时候,有个问题一直困扰着我——屏幕大了,内容反而更容易堆成一坨。用户看到满屏的文字,第一反应就是关掉。

后来我想明白了,PC端的设置页、FAQ页、帮助文档这些场景,其实特别需要一种"按需展示"的交互方式。用户点一下标题,内容平滑地展开;再点一下,优雅地收回去。这就是我们常说的手风琴效果。

今天我们就来聊聊,在HarmonyOS6 PC端开发中,怎么用ArkUI的动画能力实现一个体验不错的展开折叠效果。

先看效果:点击即展开,再点即收起

我们要做的效果是这样的:页面上有几个卡片,每个卡片有一个标题栏。点击标题栏,下方的详细内容区域会以一个"从上往下滑入+淡入"的组合动画展开。再点击,内容区域以"向下滑出+淡出"的动画收起。

同时,标题栏右侧会显示"展开 ▼"或"收起 ▲"的文字提示,给用户明确的状态反馈。

这个效果在PC端的设置页面里非常常见。比如Windows的设置页、macOS的系统偏好设置,到处都是这种交互。HarmonyOS6 PC端的应用也该有。

核心思路:条件渲染 + 过渡动画

说白了,展开折叠的核心就两件事:

  1. 用一个布尔状态控制内容区域的显示/隐藏
  2. 在内容出现和消失的时候,加上过渡动画

ArkUI里有个非常方便的机制——if条件渲染配合.transition()修饰器,就能搞定这件事。当if条件从false变为true时,组件会被创建并执行"入场"过渡动画;从true变为false时,组件执行"出场"过渡动画然后被销毁。

我们再配合.animation()修饰器来让高度变化也有一个平滑过渡,整个效果就出来了。

状态管理:每个折叠项一个布尔值

先来看状态定义部分。

@Entry@Componentstruct AccordionDemo{@StateisExpanded1:boolean=false@StateisExpanded2:boolean=false@StateisExpanded3:boolean=false// ...}

三个折叠项,三个@State布尔值。简单粗暴但很管用。

说实话,如果折叠项很多(比如FAQ页面有二三十个问答),这种一个个定义的方式肯定不合适。那时候更好的做法是用一个数组来管理状态,或者把每个折叠项封装成独立的子组件,让它自己管理自己的展开状态。但作为学习示例,三个状态变量已经足够把原理讲清楚了。

我们还提供了两个辅助方法来读写状态:

_getState(index:number):boolean{if(index===0)returnthis.isExpanded1if(index===1)returnthis.isExpanded2returnthis.isExpanded3}_toggleState(index:number){if(index===0)this.isExpanded1=!this.isExpanded1elseif(index===1)this.isExpanded2=!this.isExpanded2elsethis.isExpanded3=!this.isExpanded3}

这样做的好处是把索引到状态的映射集中管理,在@Builder里调用起来很干净。

标题栏:点击切换的关键

标题栏部分没什么黑魔法,就是一个Row布局,左边放标题文字,右边放状态提示:

@BuilderExpandItem(title:string,index:number){Column(){Row(){Text(title).fontSize(14).fontWeight(FontWeight.Medium).layoutWeight(1)Text(this._getState(index)?'收起 ▲':'展开 ▼').fontSize(12).fontColor('#007DFF')}.width('100%').padding(12).onClick(()=>{this._toggleState(index)})// 内容区域在下面...}}

几个值得注意的细节:

  • layoutWeight(1)让标题文字占据剩余空间,状态提示自然靠右
  • 状态提示用三元表达式根据当前展开状态动态显示"收起 ▲"或"展开 ▼"
  • onClick直接调用_toggleState切换布尔值,ArkUI的响应式系统会自动触发UI更新

重头戏:TransitionEffect.asymmetric 非对称过渡

这是今天最核心的知识点。

我们来看看内容区域的代码:

if(this._getState(index)){Column(){Text(`这是${title}的详细内容区域。\n展开折叠动画可以让页面更具交互性,\n用户体验更加流畅。`).fontSize(12).fontColor('#999999').padding(12)}.width('100%').backgroundColor('#F5F6FA').borderRadius(8).animation({duration:300,curve:Curve.EaseInOut}).transition(TransitionEffect.asymmetric(// 入场动画:淡入 + 从上方滑入TransitionEffect.OPACITY.animation({duration:300}).combine(TransitionEffect.translate({y:-20}).animation({duration:300})),// 出场动画:淡出 + 向下方滑出TransitionEffect.OPACITY.animation({duration:200}).combine(TransitionEffect.translate({y:20}).animation({duration:200}))))}

这段代码信息量挺大的,我来拆解一下。

什么是 TransitionEffect?

TransitionEffect是ArkUI专门用来定义组件"出现"和"消失"时的动画效果的。当组件被if条件创建出来时,它会从 TransitionEffect 定义的"初始状态"过渡到"正常状态";当组件被销毁时,从"正常状态"过渡到 TransitionEffect 定义的"结束状态"。

为什么要用 asymmetric?

TransitionEffect.asymmetric()允许我们分别定义入场和出场的动画效果。这个设计非常合理——展开和收起本来就不该是完全相反的过程。

你看我们的实现:

  • 入场(展开):从上方20像素的位置滑下来,同时从透明变为不透明,耗时300ms
  • 出场(收起):向下方20像素的位置滑出去,同时从不透明变为透明,耗时200ms

为什么出场时间更短?这是一个UX细节。用户对"收起"的耐心比"展开"低——收起是个"结束"动作,快一点会让用户觉得更干脆、不拖泥带水。展开稍微慢一点,给用户一个"内容正在呈现"的感知过程。

combine 的作用

.combine()用来把多个过渡效果叠加在一起。我们的效果是"淡入+滑入"同时进行,所以需要把OPACITYtranslate组合起来。

如果不 combine,你就只能定义单一的过渡效果,比如只淡入不滑动,视觉上会单调很多。

.animation() 修饰器的配合

你可能注意到了,在.transition()之外,我们还加了一个.animation({ duration: 300, curve: Curve.EaseInOut })。这个修饰器的作用是让组件的属性变化(比如高度、宽度等布局属性的变化)也有平滑过渡。

坦白讲,transition管的是组件的出现/消失,animation管的是组件属性值的变化。两者配合起来,才能保证展开的时候不仅有淡入滑入效果,整个布局的重新排列也是平滑的,而不是"啪"的一下跳过去。

批量操作:全部展开/全部折叠

除了单个卡片的点击切换,我们还加了两个按钮来做批量操作:

Button('全部展开').width('100%').margin({top:8}).onClick(()=>{this.isExpanded1=truethis.isExpanded2=truethis.isExpanded3=true})Button('全部折叠').width('100%').margin({top:6}).onClick(()=>{this.isExpanded1=falsethis.isExpanded2=falsethis.isExpanded3=false})

点击"全部展开",三个布尔值同时置为true,三个内容区域几乎同时执行入场动画。因为有Curve.EaseInOut缓动曲线,视觉上看起来是整齐划一的展开效果。

这个功能在PC端其实挺实用的。想象一下,用户在设置页面想搜索某个选项,如果所有分组都折叠了,他得一个个点开找。给一个"全部展开"的入口,体验会好很多。

完整代码

把上面的片段组合起来,完整的页面结构如下:

@Entry@Componentstruct AccordionDemo{@StateisExpanded1:boolean=false@StateisExpanded2:boolean=false@StateisExpanded3:boolean=false@BuilderExpandItem(title:string,index:number){Column(){Row(){Text(title).fontSize(14).fontWeight(FontWeight.Medium).layoutWeight(1)Text(this._getState(index)?'收起 ▲':'展开 ▼').fontSize(12).fontColor('#007DFF')}.width('100%').padding(12).onClick(()=>{this._toggleState(index)})if(this._getState(index)){Column(){Text(`这是${title}的详细内容区域。\n展开折叠动画可以让页面更具交互性,\n用户体验更加流畅。`).fontSize(12).fontColor('#999999').padding(12)}.width('100%').backgroundColor('#F5F6FA').borderRadius(8).animation({duration:300,curve:Curve.EaseInOut}).transition(TransitionEffect.asymmetric(TransitionEffect.OPACITY.animation({duration:300}).combine(TransitionEffect.translate({y:-20}).animation({duration:300})),TransitionEffect.OPACITY.animation({duration:200}).combine(TransitionEffect.translate({y:20}).animation({duration:200}))))}}.width('100%').backgroundColor('#FFFFFF').borderRadius(8).margin({bottom:8})}_getState(index:number):boolean{if(index===0)returnthis.isExpanded1if(index===1)returnthis.isExpanded2returnthis.isExpanded3}_toggleState(index:number){if(index===0)this.isExpanded1=!this.isExpanded1elseif(index===1)this.isExpanded2=!this.isExpanded2elsethis.isExpanded3=!this.isExpanded3}build(){Column(){Scroll(){Column(){Text('展开折叠动画').fontSize(18).fontWeight(FontWeight.Bold).margin({bottom:8})Column(){this.ExpandItem('功能介绍',0)this.ExpandItem('使用说明',1)this.ExpandItem('配置选项',2)Button('全部展开').width('100%').margin({top:8}).onClick(()=>{this.isExpanded1=truethis.isExpanded2=truethis.isExpanded3=true})Button('全部折叠').width('100%').margin({top:6}).onClick(()=>{this.isExpanded1=falsethis.isExpanded2=falsethis.isExpanded3=false})}.width('100%')}.width('100%')}.layoutWeight(1)}.width('100%').height('100%').backgroundColor('#F5F6FA').padding(16)}}

扩展思考:在PC端的实际应用场景

展开折叠动画在PC端的应用场景比手机端多得多。

设置页面是最典型的。PC端应用的设置项通常很多——显示设置、通知设置、隐私设置、账户设置等等。每个分类下面可能有十几个选项。用折叠面板把不同分类收纳起来,用户需要时再展开,页面整洁、查找高效。

帮助文档/FAQ页面也很适合。用户在PC端看帮助文档的时候,往往是带着具体问题来的。一个问题对应一个折叠项,用户扫一眼标题就知道该不该点开。比把所有回答平铺在页面上强太多。

代码编辑器的文件树也是类似思路。文件夹展开/收起本质上就是手风琴效果。HarmonyOS6 PC端如果要做一个文件管理器,这个动画是基础中的基础。

还有一个场景——数据面板。Dashboard 里经常有多个数据模块,允许用户折叠暂时不关心的模块,把注意力集中在当前关注的数据上。在PC端大屏幕上看数据报表的时候,这个功能特别有用。

进阶优化:用数组管理折叠状态

前面用了三个@State变量来管理三个折叠项。如果折叠项是动态的(比如从服务器拉取的FAQ列表),我们可以把状态改成数组:

@StateexpandStates:boolean[]=[false,false,false]// 切换某个项的展开状态_toggleState(index:number){// 注意:直接修改数组元素不会触发UI更新// 需要创建新数组来触发响应式constnewStates=[...this.expandStates]newStates[index]=!newStates[index]this.expandStates=newStates}

这里有个坑要提醒一下。ArkUI的@State对数组的监听是"引用级别"的,直接修改this.expandStates[index]不会触发UI更新。必须替换整个数组引用才行。这个坑我踩过,排查了好一会儿。

用数组管理状态后,"全部展开"和"全部折叠"也可以写得更简洁:

// 全部展开this.expandStates=this.expandStates.map(()=>true)// 全部折叠this.expandStates=this.expandStates.map(()=>false)

踩坑记录:过渡动画不生效的几个原因

在调试过程中,我遇到了几个过渡动画不生效的情况,总结一下:

  1. 忘了加.animation()修饰器。如果你只写了.transition()但没有给父容器或相关组件加.animation(),布局变化可能不会有平滑过渡,直接"跳"过去了。

  2. if条件变化太快。如果在一个事件回调里连续多次切换状态,框架可能会"合并"这些更新,导致过渡动画根本没机会执行。

  3. translatey值太小。如果你设的y: -5,位移太小了肉眼根本看不出来。建议至少y: -15以上,动画才明显。

  4. duration设太长。过渡动画最好不要超过400ms。太长会让用户觉得页面"卡"了。展开300ms、收起200ms是个比较舒服的区间。

小结

展开折叠动画是个看起来简单、做起来有细节的交互效果。核心就是三样东西:

  • @State布尔值控制显隐
  • if条件渲染配合.transition()实现出入场动画
  • TransitionEffect.asymmetric()定义不同的入场和出场效果

在HarmonyOS6 PC端开发中,这个效果的使用频率会非常高。PC端屏幕大、内容多,"按需展示"是信息架构的基本功。把这个模式吃透了,设置页、FAQ页、帮助文档这些场景就都能搞定了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 9:41:10

166.各大品牌Bootloader解锁机制对比|华为/小米/OPPO/vivo/一加/苹果差异

摘要 本文系统阐述主流品牌手机刷机维修的底层原理与标准化操作流程。覆盖华为、小米、OPPO、vivo、一加及苹果六类设备,从Bootloader解锁、Recovery模式操作到固件刷写与底层数据恢复,提供可直接运行的Python脚本辅助校验固件完整性。文章严格遵循工程逻辑,所有步骤均经过…

作者头像 李华
网站建设 2026/6/10 9:35:04

安卓15:屏幕朗读怎么跳过网页里的广告?

现在很多手机都有“屏幕朗读”功能,它对于很多朋友来说非常实用。比如在通勤路上想“听”新闻,晚上睡前想“听”文章,或者对于视力不太好的用户,这个功能更是不可或缺的帮手。它能把手机屏幕上的文字内容用语音读出来,…

作者头像 李华
网站建设 2026/6/10 9:34:09

计算机毕业设计之基于大数据的食物营养分析可视化平台

近年来,科技飞速发展,在经济全球化的背景之下,大数据将进一步提高社会综合发展的效率和速度,大数据技术也会涉及到各个领域,而爬虫实现网站数据可视化在网站数据可视化背景下有着无法忽视的作用。管理信息系统的开发是…

作者头像 李华
网站建设 2026/6/10 9:31:03

个人开发了一个鸿蒙测光APP------照度仪

今天必须安利一个鸿蒙小众宝藏——照度仪📱之前给娃换台灯,贵的大牌也买了 结果娃还是越写越近、揉眼睛😮‍💨 我一直以为是坐姿问题…直到用这个 App 测了一下书桌照度——我人傻了。✨ 它到底是干嘛的? 简单说&#…

作者头像 李华