做 HarmonyOS6 PC 端开发的时候显示/隐藏可能是最常见的 UI 交互之一。展开一个折叠面板、弹出一个通知条、切换一个详情区域——这些场景都需要组件从不存在变成存在或者反过来。如果你直接用if条件渲染组件就是砰地一下出现、砰地一下消失体验非常生硬。ArkUI 提供了一个专门针对这种场景的方案TransitionEffect。它跟if/else条件渲染配合使用让组件在创建和销毁时自带过渡动画。这篇文章就来把 TransitionEffect 彻底讲清楚顺便聊聊它跟.transition()的区别——这个问题我当初也搞了好一阵。先看效果这个 Demo 实现了一个可显示/隐藏的内容区域用到了TransitionEffect.OPACITY和TransitionEffect.scale的组合过渡。EntryComponentstruct TransitionDemo{Statevisible:booleantrueStateselectedIndex:number0build(){Column(){Text(显示隐藏过渡动画).fontSize(18).fontWeight(FontWeight.Bold).margin({bottom:8})Column(){if(this.visible){Column().width(100%).height(120).backgroundColor(this.getColor(this.selectedIndex%8)).borderRadius(16).transition(TransitionEffect.OPACITY.animation({duration:400,curve:Curve.EaseInOut}).combine(TransitionEffect.scale({x:0,y:0}).animation({duration:400})))}else{Column().width(100%).height(120).backgroundColor(#E8E8E8).borderRadius(16).justifyContent(FlexAlign.Center).transition(TransitionEffect.OPACITY.animation({duration:300}))}Row({space:10}){Button(this.visible?隐藏:显示).onClick((){this.visible!this.visible})Button(切换内容).onClick((){this.selectedIndexthis.visibletrue})Button(滑入显示).onClick((){this.visibletrue})Button(淡出隐藏).onClick((){this.visiblefalse})}.width(100%).justifyContent(FlexAlign.SpaceEvenly).margin({top:16})}.width(100%).backgroundColor(#FFFFFF).borderRadius(12).padding(16)}.width(100%).height(100%).backgroundColor(#F5F6FA).padding(16)}getColor(index:number):string{constcolors[#FF6B6B,#FFA500,#FFD93D,#6BCB77,#4ECDC4,#4D96FF,#9B59B6,#FF6B9D]returncolors[index]}}TransitionEffect 的工作原理TransitionEffect 的触发条件非常简单组件因为 if/else 条件变化而被创建或销毁时。当this.visible从true变成falseif (this.visible)分支下的组件要被销毁了框架检测到这个组件上有.transition()修饰器不立即销毁而是先执行离场过渡动画动画结束后才真正移除组件当this.visible从false变成trueif (this.visible)分支下的组件要被创建了组件创建后立即执行进场过渡动画从过渡起始状态动画变化到正常状态这个机制跟animateTo()完全不同。animateTo 是组件始终存在只是属性在变TransitionEffect 是组件本身在创建和销毁过渡动画是这个生命周期的附带效果。TransitionEffect 有哪些可用效果ArkUI 内置了这些 TransitionEffect 类型// 基础效果TransitionEffect.OPACITY// 透明度0 → 1进场/ 1 → 0离场TransitionEffect.scale(value)// 缩放指定值 → 1进场/ 1 → 指定值离场TransitionEffect.translate(value)// 位移指定偏移 → 0进场/ 0 → 指定偏移离场TransitionEffect.rotate(value)// 旋转指定角度 → 0进场/ 0 → 指定角度离场// 组合方法.combine(otherEffect)// 组合两个效果同时执行.asymmetric(appear,disappear)// 进场和离场使用不同的效果说实话光这几个基础效果加上组合已经能覆盖 90% 的显示/隐藏动画需求了。组合过渡OPACITY scaleDemo 里的核心代码.transition(TransitionEffect.OPACITY.animation({duration:400,curve:Curve.EaseInOut}).combine(TransitionEffect.scale({x:0,y:0}).animation({duration:400})))这段代码做了什么进场动画visible 从 false 变 true组件从完全透明 缩放为零过渡到完全不透明 正常大小。视觉上就是一个从无到有、从小到大弹出来的效果。离场动画visible 从 true 变 false反过来从正常大小 不透明过渡到缩放为零 完全透明。组件缩小到消失。.combine()方法让两个效果同时执行。每个效果可以有自己的.animation()配置这意味着你可以让透明度和缩放的时长不一样// 透明度 300ms 就完成缩放 500ms 慢慢来TransitionEffect.OPACITY.animation({duration:300}).combine(TransitionEffect.scale({x:0.5,y:0.5}).animation({duration:500}))这种不同步的组合过渡有时候反而更有设计感。非对称过渡进场和离场不同效果有些场景下你希望组件飞进来但淡出去——进场和离场不是简单的反向关系。TransitionEffect.asymmetric()就是为这个设计的.transition(TransitionEffect.asymmetric(// 进场效果从左侧滑入TransitionEffect.translate({x:-200}).animation({duration:400,curve:Curve.EaseOut}),// 离场效果淡出TransitionEffect.OPACITY.animation({duration:300,curve:Curve.EaseIn})))进场时组件从左边 200px 的位置滑入离场时原地淡出。两个方向的效果完全不同。这种非对称过渡在 HarmonyOS6 PC 端的侧边栏、抽屉菜单等场景中特别实用。侧边栏从左侧滑入进场但关闭时可能是淡出滑回左侧离场可以跟进场反向也可以完全不同。Demo 里的两个分支都有 transition注意 Demo 里if和else两个分支的组件上都有.transition()if(this.visible){Column()// ... 彩色内容区域.transition(TransitionEffect.OPACITY.animation({duration:400,curve:Curve.EaseInOut}).combine(TransitionEffect.scale({x:0,y:0}).animation({duration:400})))}else{Column()// ... 灰色占位区域.transition(TransitionEffect.OPACITY.animation({duration:300}))}这是为什么呢因为if/else条件渲染在切换时旧的组件被销毁新的组件被创建。两个组件都有独立的 transition 配置。当 visible 从 true 变 false彩色组件开始执行离场动画缩小淡出400ms同时灰色组件被创建执行进场动画淡入300ms当 visible 从 false 变 true灰色组件开始执行离场动画淡出300ms同时彩色组件被创建执行进场动画放大淡入400ms两个组件的过渡动画是并行执行的——一个在消失另一个在出现。这就形成了流畅的交叉淡入淡出效果。.transition() vs .transitionEffect()到底有什么区别这个问题在社区里被问过无数次了我来理清楚。.transition()这是一个组件修饰器直接挂在组件上。它接收一个 TransitionEffect 参数Column().transition(TransitionEffect.OPACITY.animation({duration:400}))当组件因为if/else被创建/销毁时这个 transition 自动生效。.transitionEffect()这是一个更新的 APIAPI 11功能更强大。它也可以挂在组件上但支持更细粒度的控制Column().transitionEffect(TransitionEffect.OPACITY.animation({duration:400}))两者的核心区别触发方式.transition()只能由 if/else 条件渲染触发.transitionEffect()除了 if/else 外还支持通过transition()全局函数主动触发组合能力.transitionEffect()支持更灵活的链式调用和组合API 版本.transition()从 API 7 就有了.transitionEffect()是 API 11 新增的对于 HarmonyOS6 PC 开发API 版本通常不是问题HarmonyOS6 的 API 版本足够高。建议新项目统一用.transitionEffect()老项目如果已经在用.transition()也不用急着换。实战用 TransitionEffect 实现几种常见的 PC 端过渡下面扩展几种在 HarmonyOS6 PC 端项目中常用的过渡效果。顶部通知条从顶部滑入的通知条适合用在系统消息、操作反馈等场景if(this.showNotification){Row(){Text(操作成功).fontColor(#FFFFFF).fontSize(14)}.width(100%).height(48).backgroundColor(#6BCB77).borderRadius(8).transition(TransitionEffect.translate({y:-60}).animation({duration:350,curve:Curve.EaseOut}))}进场时从上方 60px 的位置滑下来离场时原路滑回去。底部操作栏从底部弹出的操作栏适合用在浮动工具栏、批量操作面板if(this.showActionBar){Row(){Button(删除).backgroundColor(#FF6B6B)Button(标记).backgroundColor(#4D96FF)Button(取消)}.width(100%).height(56).justifyContent(FlexAlign.SpaceEvenly).backgroundColor(#FFFFFF).shadow(ShadowStyle.OUTER_DEFAULT_SM).transition(TransitionEffect.translate({y:80}).combine(TransitionEffect.OPACITY).animation({duration:300,curve:Curve.EaseOut}))}从底部 80px 完全透明 → 原位 不透明。右侧详情面板HarmonyOS6 PC 端常见的右侧详情面板从右滑入if(this.showDetail){Column(){// 详情内容...}.width(320).height(100%).backgroundColor(#FFFFFF).transition(TransitionEffect.asymmetric(TransitionEffect.translate({x:320}).animation({duration:400,curve:Curve.EaseOut}),TransitionEffect.translate({x:320}).combine(TransitionEffect.OPACITY).animation({duration:300,curve:Curve.EaseIn})))}进场时从右侧滑入EaseOut快入慢出离场时滑出淡出EaseIn慢入快出。进场 400ms 离场 300ms离场更快是因为用户已经决定关闭了不想等太久。缩放弹出的对话框if(this.showDialog){Column(){Text(确认删除).fontSize(18).fontWeight(FontWeight.Bold)Text(此操作不可恢复).fontSize(14).fontColor(#999999)Row({space:12}){Button(取消).onClick((){this.showDialogfalse})Button(确认).backgroundColor(#FF6B6B).onClick((){this.showDialogfalse})}.margin({top:16})}.width(300).padding(24).backgroundColor(#FFFFFF).borderRadius(16).shadow(ShadowStyle.OUTER_DEFAULT_SM).transition(TransitionEffect.scale({x:0.8,y:0.8}).combine(TransitionEffect.OPACITY).animation({duration:300,curve:Curve.EaseOut}))}经典的弹窗效果——从 80% 大小 透明弹出到正常大小 不透明。scale 起始值用 0.8 而不是 0是因为 0 的话弹窗会从一个点开始放大看着很突兀。0.8 只是稍微小一点弹出来更自然。一个容易踩的坑TransitionEffect 不触发的情况用 TransitionEffect 最常见的坑就是——动画没触发组件直接闪现/闪消。原因通常有这么几个1. 没有用 if/else 条件渲染TransitionEffect 只在组件被创建/销毁时触发。如果你用的是.opacity(0)或.visibility(Visibility.None)来隐藏组件组件本身一直在组件树里transition 不会触发。// 错误组件始终存在只是透明度变了Column().opacity(this.visible?1:0).transition(TransitionEffect.OPACITY)// 不会触发// 正确用 if 条件渲染if(this.visible){Column().transition(TransitionEffect.OPACITY)// 会触发}2. 状态变更在 animateTo 闭包内如果改变 visible 的赋值操作在 animateTo 闭包里可能跟 TransitionEffect 的动画机制冲突。建议直接赋值不要包在 animateTo 里// 可能有问题animateTo({duration:400},(){this.visibletrue// 在 animateTo 里改 visible})// 推荐this.visibletrue// 直接赋值transition 自己处理动画3. ForEach 中的组件在 ForEach 循环里使用 TransitionEffect需要确保 key 生成器是正确的。如果 key 不稳定比如用 index 做 key框架可能认为组件没变而不触发过渡。HarmonyOS6 PC 端的过渡动画设计建议PC 端和手机端在过渡动画上有几个关键差异1. 过渡距离更长PC 端屏幕大组件滑入/滑出的距离要相应增加。手机上 translate 60px 就够了PC 端可能需要 100-200px 才有从屏幕外飞入的感觉。2. 阴影和层级的配合PC 端用户更习惯多层级 UI面板叠面板。TransitionEffect 配合.shadow()使用效果更好——面板滑入时自带阴影层级感更明确。3. 不要滥用过渡PC 端的操作频率比手机端高鼠标点击比手指触摸快得多。如果每次显示/隐藏都有 400ms 的过渡动画用户会觉得怎么这么慢。高频操作的组件比如 tooltip、hover 弹出层建议把过渡时长压到 150-200ms甚至直接不用过渡。4. 键盘用户的体验PC 端有很多键盘操作Tab 切换、Enter 确认、Esc 关闭。这些操作的响应预期比鼠标点击更快对应的过渡动画也应该更短更快。小结TransitionEffect 是 ArkUI 里做组件显示/隐藏过渡的标准方案。核心用法就三步用if/else条件渲染控制组件的存在与否在组件上用.transition()或.transitionEffect()配置过渡效果用.combine()组合多个效果用.asymmetric()实现进场/离场不同效果记住一句话animateTo 管属性变化TransitionEffect 管生死过渡。两者的适用场景不同别搞混了。