当前位置: 首页 > news >正文

鸿蒙 ArkTS 实战:打造丝滑的共享元素转场动画

文章目录

    • 前言
        • 📜 完整代码结构预览
        • 🧩 第一部分:数据模型与状态管理
        • 🛠️ 第二部分:自定义卡片组件 (@Builder)
        • ✨ 第三部分:动画与路由逻辑 (animateTo + router)
        • 📝 第四部分:页面主体构建 (build)
        • 🎨 第五部分:视觉设计与色彩搭配
        • 📸 页面展示
        • 📌 总结与实战建议

前言

在上一期的音乐播放器实战中,我们掌握了复杂的页面布局。今天,我们将挑战一个更高级的交互效果——共享元素转场动画

这个效果在现代 App 中非常常见:当你点击列表中的某个卡片时,它会以一个流畅的动画“变形”并过渡到下一个页面的对应元素上,为用户带来无缝、连贯的视觉体验。

这个项目虽然代码量不大,但含金量极高,涵盖了以下核心知识点:

  • 页面路由传参:使用router.pushUrl在页面间传递复杂数据。
  • 显式动画:利用animateTo实现点击后的退出动画。
  • 状态驱动 UI:通过@State变量精确控制单个组件的动画状态。
  • 动态样式绑定:根据状态动态改变组件的scale(缩放)和opacity(透明度)。

下面,我们就对这段实现丝滑动画的代码进行一次深度解析。


📜 完整代码结构预览

首先,让我们从整体上把握代码结构。它定义了一个Index入口组件,核心是列表的渲染、点击事件的处理以及CardItem的自定义构建。

importrouterfrom'@ohos.router'interfaceCardData{id:stringtitle:stringsubtitle:stringcolor:stringimage:string}@Entry@Componentstruct Index{// 1. 数据与状态定义@StatecardList:CardData[]=[...]@StateexitCardId:string=''@StateexitCardScale:number=1@StateexitCardOpacity:number=1// 2. 页面主体构建build(){...}// 3. 动画与路由逻辑privatestartExitAnimation(card:CardData):void{...}// 4. 自定义卡片组件@BuilderCardItem(card:CardData){...}}

🧩 第一部分:数据模型与状态管理

代码的开头定义了一个CardData接口和几个关键的@State变量,它们是驱动整个动画的核心。

interfaceCardData{id:stringtitle:stringsubtitle:stringcolor:stringimage:string}@StatecardList:CardData[]=[{id:'1',title:'探索之旅',subtitle:'开启一段奇妙的冒险',color:'#667EEA',image:'...'},// ... 其他卡片数据]@StateexitCardId:string=''@StateexitCardScale:number=1@StateexitCardOpacity:number=1
  • CardData接口:定义了卡片的数据结构,包括唯一的id、标题、副标题、背景色和图片链接。
  • cardList:一个CardData类型的数组,作为列表的数据源。这里的图片链接指向一个可以根据文本描述生成图片的 API,非常巧妙。
  • exitCardId:这是动画的“开关”。它存储了当前正在执行退出动画的卡片的id。通过判断其他卡片的id是否与它相等,来决定是否应用动画效果。
  • exitCardScale/exitCardOpacity:这两个变量分别控制动画过程中的缩放比例和透明度。它们的值会在动画执行时被改变,从而驱动 UI 变化。

🛠️ 第二部分:自定义卡片组件 (@Builder)

@Builder装饰器将卡片的 UI 封装成一个独立的函数CardItem,使build方法更加简洁。

@BuilderCardItem(card:CardData){Stack({alignContent:Alignment.Center}){// 背景卡片Column().width('100%').height(180).backgroundColor(card.color).borderRadius(20)// 内容区域Row({space:16}){Image(card.image).width(120).height(140).borderRadius(16).objectFit(ImageFit.Cover)// 动态绑定缩放和透明度.scale({x:this.exitCardId===card.id?this.exitCardScale:1,y:this.exitCardId===card.id?this.exitCardScale:1}).opacity(this.exitCardId===card.id?this.exitCardOpacity:1)Column({space:8}){Text(card.title).fontSize(24).fontWeight(FontWeight.Bold).fontColor('#FFFFFF')// 标题也绑定动画.scale({x:this.exitCardId===card.id?this.exitCardScale:1,y:this.exitCardId===card.id?this.exitCardScale:1}).opacity(this.exitCardId===card.id?this.exitCardOpacity:1)// ... 其他文本}.layoutWeight(1)}.width('100').padding(20)}.width('100%').height(180).shadow({radius:15,color:card.color+'40',offsetX:0,offsetY:8})// 整个卡片容器也绑定动画,实现整体缩小.scale({x:this.exitCardId===card.id?this.exitCardScale:1,y:this.exitCardId===card.id?this.exitCardScale:1}).opacity(this.exitCardId===card.id?this.exitCardOpacity:1)}
  • 布局结构:使用Stack作为容器,内部是一个带圆角和阴影的Column作为卡片背景,再上面是一个Row来水平排列图片和文字信息。
  • 动态样式绑定:这是实现动画的关键!
    • .scale(...).opacity(...)修饰符的值不再是固定的,而是通过三元运算符this.exitCardId === card.id ? ... : ...进行动态判断。
    • 只有当卡片的idexitCardId匹配时,才会应用exitCardScaleexitCardOpacity的值,否则保持默认值(缩放为1,透明度为1)。这确保了动画只作用于被点击的那一个卡片。
  • 阴影效果.shadow()修饰符为卡片添加了柔和的阴影,card.color + '40'的写法巧妙地根据卡片背景色生成了半透明的阴影颜色,提升了视觉质感。

✨ 第三部分:动画与路由逻辑 (animateTo + router)

这是整个交互的灵魂所在。startExitAnimation函数处理了从点击到页面跳转的全过程。

privatestartExitAnimation(card:CardData):void{this.exitCardId=card.id// 1. 标记当前要动画的卡片// 2. 执行显式动画animateTo({duration:300,curve:Curve.Friction,onFinish:()=>{// 3. 动画结束后,进行页面跳转router.pushUrl({url:'pages/Detail',params:{cardData:JSON.stringify(card)}})// 4. 跳转后,重置状态,为下一次动画做准备setTimeout(()=>{this.exitCardId=''this.exitCardScale=1this.exitCardOpacity=1},100)}},()=>{// 5. 动画内容:改变状态变量的值this.exitCardScale=0.8this.exitCardOpacity=0})}
  • this.exitCardId = card.id:首先,记录下被点击卡片的id。这个操作会触发 UI 的重新渲染,CardItem中的动态样式绑定会检测到这个变化。
  • animateTo:这是鸿蒙提供的显式动画 API。它会将第二个回调函数中状态变量的变化,以动画的形式呈现出来。
    • duration: 300:动画持续时间为 300 毫秒。
    • curve: Curve.Friction:使用摩擦曲线,让动画有自然的减速感。
    • 在动画回调中,我们将exitCardScale改为0.8exitCardOpacity改为0。这会驱动被选中的卡片在 300ms 内缩小到 80% 并淡出。
  • onFinish:动画结束后的回调。
    • router.pushUrl:使用路由跳转到详情页。关键点在于params,我们将整个card对象通过JSON.stringify序列化成字符串后传递过去。详情页可以通过router.getParams()获取并反序列化,从而实现数据的传递。
    • setTimeout:在跳转后,使用一个短暂的延时来重置所有状态变量。这是为了在用户从详情页返回时,列表能恢复到初始状态,避免动画错乱。

📝 第四部分:页面主体构建 (build)

build函数负责搭建页面的基本骨架,结构非常清晰。

build(){Column(){Text('共享元素动效').fontSize(32).fontWeight(FontWeight.Bold).fontColor('#FFFFFF').margin({top:60,bottom:30})List({space:20}){ForEach(this.cardList,(card:CardData)=>{ListItem(){this.CardItem(card)}.onClick(()=>{this.startExitAnimation(card)// 绑定点击事件})})}.width('100%').padding({left:20,right:20}).layoutWeight(1)}.width('100%').height('100%').backgroundColor('#1A1A2E')}
  • ListForEach:使用List组件来高效地渲染长列表,并通过ForEach循环cardList数据源,为每一项生成一个CardItem
  • .onClick:为每个ListItem绑定点击事件,触发我们之前定义的startExitAnimation函数,并将当前卡片的数据card作为参数传入。

🎨 第五部分:视觉设计与色彩搭配

整个页面采用了深邃的暗色背景,与卡片鲜艳的色彩形成强烈对比,突出了内容本身。

视觉区域颜色代码设计意图
整体背景#1A1A2E深蓝色背景,营造沉静、专注的氛围,让卡片更突出。
卡片背景#667EEA,#F093FB每张卡片使用不同的渐变色或亮色,充满活力,区分不同主题。
文字颜色#FFFFFF白色文字在深色背景上保证了极佳的可读性。
阴影颜色card.color + '40'动态生成与卡片同色系的半透明阴影,细节感满满。

📸 页面展示


  • 列表页:展示四个不同主题的卡片,布局清晰,色彩鲜明。
  • 点击动画:点击任一卡片,该卡片会平滑地缩小并淡出,随后跳转到详情页。

📌 总结与实战建议

通过这个共享元素转场动画的实战,我们掌握了以下 ArkTS 高阶技能:

  1. 显式动画animateTo:学会了如何使用animateTo包裹状态变化,轻松实现复杂的属性动画。
  2. 精细化状态控制:通过一个id状态变量,精确地控制列表中单个元素的样式和行为,这是处理列表交互的常用技巧。
  3. 页面间通信:掌握了使用router.pushUrlparams参数进行页面间数据传递的方法,特别是对象的序列化与反序列化。
  4. 动态样式绑定:深刻理解了如何将组件的样式属性(如scale,opacity)与状态变量绑定,实现数据驱动 UI 的强大能力。
http://www.jsqmd.com/news/1132913/

相关文章:

  • Visual C++ 运行库AI智能修复方案:企业级部署架构设计与性能优化指南
  • SELinux 深度解析:从核心原理到运维实战的完整指南
  • 构建企业级微信自动化系统的技术架构与实践
  • TikTok评论数据采集:3步获取完整用户反馈,无需编程经验
  • 3步搞定B站4K大会员视频下载:开源工具完全指南
  • Chatbox桌面AI助手实战指南:构建个人智能工作站的最佳实践
  • “HyFormer: Revisiting the Roles of Sequence Modeling and Feature Interaction in CTR Prediction“ 论文笔记
  • Win11上面安装多个Keil版本,但是需要指定默认的版本,如果Win11自己的指定方式不成功,可以用下面的方法
  • 063、超分评价指标详解:PSNR、SSIM、LPIPS 与 NIQE 的计算与对比
  • 如何在macOS上完美使用Xbox控制器:360Controller驱动终极解决方案
  • 用自然语言让 AI Agent 卸载软件 —— 以卸载 Visual Studio 2026 为例
  • 英雄联盟自动化工具箱:三分钟告别繁琐操作,专注游戏核心体验
  • 那些年,我们买过的“电子垃圾”:科技产品的真实寿命
  • IoU-Aware分类损失:把定位质量纳入分类评分,NMS误删率直降的秘籍
  • Agent开发本质是CRUD编排:状态建模与执行层工程实践
  • Vin象棋:基于YOLOv5的智能中国象棋AI辅助工具,告别手动摆棋的烦恼
  • C语言用双向链表实现单调递减(递增)队列
  • Layerdivider:如何用AI在5分钟内将任何插画转换为可编辑PSD图层
  • 从零构建搜索引擎:Python 异步爬虫 + 倒排索引 + Sanic 前后端实战
  • 2026论文全流程终极榜单:10款降AI率平台,查重降重+降AIGC一次通关
  • Linux 5.15 网口驱动调试:从 PHY 初始化到 DMA 异常的 5 步硬件排查法
  • 暗黑破坏神2存档修改终极指南:免费Web编辑器d2s-editor完全解析
  • AIOps 自动修复边界:能自动做,不代表该自动做
  • 如何做仿真?
  • 061、自定义数据集训练:如何将自己的图像和视频数据用于超分模型
  • 5分钟解锁Wand高级功能:开源增强工具完整指南
  • Spek频谱分析器终极指南:5分钟掌握音频可视化分析完整教程
  • 人体骨骼时序动态感知模型 头肢活跃度量化+实时情绪推演核心算法专项解析
  • 3分钟免费解锁B站缓存视频:m4s-converter终极完整指南
  • 130、共享卷积 Head:分类和回归分支共享前三层卷积的参数共享策略与效果