
文章目录前言真实页面会关心什么列表、状态和反馈关键片段片段 1get currentLineIndex(): number {片段 2get progress(): number {使用方式完整代码可扩展点前言如果刚开始写 HarmonyOS7 页面我建议多拆这种案例。代码量不夸张但能把声明式 UI 的味道摸清楚。它的主线是歌词滚动播放器细节落在歌词同步高亮、进度控制与翻译切换。这些细节刚好能把页面状态、用户操作和组件刷新串起来。真实页面会关心什么LyricPlayerPage不是一个只展示静态内容的页面。它会根据用户点击、输入、切换或计时来改变界面所以阅读代码时可以先抓三条线观察点在这个案例里的表现页面主题歌词滚动播放器主要文案晴天,周杰伦,叶惠美,故事的小黄花,从出生那年就飘着,童年的荡秋千常用组件Column,ForEach,Row,Scroll,Slider,Stack,Text适合练习状态驱动 UI、条件样式、列表渲染、事件回调列表、状态和反馈ArkUI 的页面刷新依赖状态变化。下面这些State字段就是页面的“开关”和“数据源”不用手动找 DOM也不用自己通知某个控件刷新。状态字段类型我会怎么理解它currentTimenumber当前选中项决定高亮和内容切换isPlayingboolean布尔开关控制显示隐藏或模式切换|showTranslation|boolean| 布尔开关控制显示隐藏或模式切换 ||fontSize|number| 记录页面当前选择、输入或展示状态 ||timerHandle|number| 记录页面当前选择、输入或展示状态 |一个经验先把State看完再看build()页面逻辑会清楚很多。否则很容易被一长串布局代码带偏。关键片段片段 1get currentLineIndex(): number {这段代码承担的是页面里的“判断”或“动作”把它从布局里抽出来后build()会清爽很多。后面要加新条件也不会到处翻 UI 代码。getcurrentLineIndex():number{letidx0for(leti0;ithis.lyrics.length;i){if(this.currentTimethis.lyrics[i].time){idxi}}returnidx}片段 2get progress(): number {这段代码承担的是页面里的“判断”或“动作”把它从布局里抽出来后build()会清爽很多。后面要加新条件也不会到处翻 UI 代码。getprogress():number{returnthis.currentTime/this.song.duration}使用方式把下面代码放进 ArkTS 页面文件中即可运行。用于正式项目时我会继续把数据模型、卡片 Builder 和页面事件拆出去页面文件只负责组合。建议练习顺序先改状态字段 - 再改交互事件 - 最后调整 UI 样式 检查重点点击是否刷新、列表 key 是否稳定、条件样式是否集中管理这份代码可以继续扩展的方向把模拟数据替换成接口数据抽出复用卡片组件增加空状态和异常状态补充深色模式或主题色适配对输入类场景增加校验提示完整代码// LyricPlayerPage - 歌词滚动播放器 - 歌词同步高亮、进度控制与翻译切换interfaceLyricLine{time:numbertext:stringtranslation:string}interfaceSongInfo_lnjm{title:stringartist:stringalbum:stringduration:number}EntryComponentstruct LyricPlayerPage{StatecurrentTime:number45StateisPlaying:booleanfalseStateshowTranslation:booleantrueStatefontSize:number16StatetimerHandle:number-1privatesong:SongInfo_lnjm{title:晴天,artist:周杰伦,album:叶惠美,duration:269,}privatelyrics:LyricLine[][{time:0,text:故事的小黄花,translation:The little dandelion of the story},{time:5,text:从出生那年就飘着,translation:Has been floating since the year of birth},{time:10,text:童年的荡秋千,translation:Swinging on the childhood swing},{time:15,text:随记忆一直晃到现在,translation:Swaying along with memories until now},{time:20,text:Rui Bing说他不明白,translation:Rui Bing says he doesn\t understand},{time:25,text:为什么我吉他总在半夜弹,translation:Why I always play guitar at midnight},{time:30,text:她说她喜欢男生弹吉他的样子,translation:She said she likes how boys look playing guitar},{time:35,text:我说这简单,translation:I said it\s simple},{time:40,text:等你学会,translation:Wait till you learn},{time:45,text:我再好好说清楚,translation:I\ll explain it properly},{time:50,text:那等等,translation:Then wait a moment},{time:55,text:我先把歌唱完,translation:Let me finish the song first},{time:60,text:你今天哭了吗,translation:Did you cry today},{time:65,text:不管有没有眼泪,translation:Whether or not there are tears},{time:70,text:在你脸上来回,translation:Running down your face},{time:75,text:你笑了吗,translation:Did you smile},{time:80,text:不管有没有阳光,translation:Whether or not there is sunlight},{time:85,text:那个秋天的童年,translation:That childhood autumn},{time:90,text:印象太深刻了,translation:The impression is too deep},{time:95,text:不知不觉开始忘了,translation:Gradually started to forget},{time:100,text:晴天,translation:Sunny day},{time:108,text:盼望着晴天,translation:Hoping for a sunny day},]getcurrentLineIndex():number{letidx0for(leti0;ithis.lyrics.length;i){if(this.currentTimethis.lyrics[i].time){idxi}}returnidx}getprogress():number{returnthis.currentTime/this.song.duration}formatTime(seconds:number):string{constmMath.floor(seconds/60)constsMath.floor(seconds%60)return${m.toString().padStart(2,0)}:${s.toString().padStart(2,0)}}togglePlay(){this.isPlaying!this.isPlayingif(this.isPlaying){this.timerHandlesetInterval((){if(this.currentTimethis.song.duration){this.currentTime}else{this.currentTime0clearInterval(this.timerHandle)this.isPlayingfalse}},1000)}else{clearInterval(this.timerHandle)}}aboutToDisappear(){if(this.timerHandle0)clearInterval(this.timerHandle)}build(){Column({space:0}){// 顶部区域Column({space:16}){// 专辑封面Stack(){Column().width(160).height(160).borderRadius(80).linearGradient({angle:135,colors:[[#667eea,0],[#764ba2,0.5],[#f64f59,1.0]]}).shadow({radius:20,color:#667eea50,offsetX:0,offsetY:8}).rotate({x:0,y:0,z:1,angle:this.isPlaying?360:0}).animation({duration:8000,curve:Curve.Linear,iterations:-1,playMode:PlayMode.Normal})Circle().width(40).height(40).fill(#ffffff)Circle().width(16).height(16).fill(#333333)}Column({space:4}){Text(this.song.title).fontSize(22).fontWeight(FontWeight.Bold).fontColor(#1a1a1a)Text(${this.song.artist}·${this.song.album}).fontSize(14).fontColor(#888888)}.alignItems(HorizontalAlign.Center)}.width(100%).padding({top:24,bottom:20}).backgroundColor(#ffffff).alignItems(HorizontalAlign.Center)// 歌词显示区域Column({space:0}){// 工具栏Row(){Text(A-).fontSize(14).fontColor(#666666).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(#f0f0f0).borderRadius(16).onClick((){if(this.fontSize12)this.fontSize-2})Text(${this.fontSize}px).fontSize(13).fontColor(#999999).margin({left:8,right:8})Text(A).fontSize(14).fontColor(#666666).padding({left:12,right:12,top:6,bottom:6}).backgroundColor(#f0f0f0).borderRadius(16).onClick((){if(this.fontSize22)this.fontSize2})Blank()Row({space:6}){Text(译).fontSize(13).fontColor(this.showTranslation?#ffffff:#666666).backgroundColor(this.showTranslation?#6c5ce7:#f0f0f0).padding({left:12,right:12,top:6,bottom:6}).borderRadius(16).onClick((){this.showTranslation!this.showTranslation})}}.padding({left:16,right:16,top:10,bottom:10}).backgroundColor(#ffffff)// 歌词列表Scroll(){Column({space:0}){ForEach(this.lyrics,(line:LyricLine,idx:number){Column({space:4}){Text(line.text).fontSize(idxthis.currentLineIndex?this.fontSize2:this.fontSize).fontWeight(idxthis.currentLineIndex?FontWeight.Bold:FontWeight.Normal).fontColor(idxthis.currentLineIndex?#6c5ce7:idxthis.currentLineIndex?#bbbbbb:#444444).textAlign(TextAlign.Center).width(100%).animation({duration:300,curve:Curve.EaseOut})if(this.showTranslation){Text(line.translation).fontSize(this.fontSize-3).fontColor(idxthis.currentLineIndex?#a29bfe:#cccccc).textAlign(TextAlign.Center).width(100%)}}.padding({top:10,bottom:10,left:24,right:24}).backgroundColor(idxthis.currentLineIndex?#f5f2ff:transparent).borderRadius(8).margin({left:8,right:8})})Column().height(40)}.padding({top:16})}.layoutWeight(1).backgroundColor(#fafafa)}.layoutWeight(1)// 播放控制区Column({space:16}){// 进度条Column({space:6}){Slider({value:this.currentTime,min:0,max:this.song.duration,step:1,style:SliderStyle.OutSet,}).width(100%).trackColor(#e0e0e0).selectedColor(#6c5ce7).onChange((v:number){this.currentTimeMath.round(v)})Row(){Text(this.formatTime(this.currentTime)).fontSize(12).fontColor(#999999)Blank()Text(this.formatTime(this.song.duration)).fontSize(12).fontColor(#999999)}.width(100%)}// 播放按钮Row({space:32}){Text(⏮).fontSize(28).fontColor(#444444)Stack(){Circle().width(60).height(60).fill(#6c5ce7)Text(this.isPlaying?⏸:▶).fontSize(22).fontColor(#ffffff)}.onClick(()this.togglePlay())Text(⏭).fontSize(28).fontColor(#444444)}.width(100%).justifyContent(FlexAlign.Center).alignItems(VerticalAlign.Center)}.padding({left:24,right:24,top:16,bottom:32}).backgroundColor(#ffffff)}.width(100%).height(100%).backgroundColor(#ffffff)}}可扩展点这个案例可以当成一个小模板先把页面会变的东西放进状态再用函数或 Builder 处理重复逻辑最后让布局只关心展示。写 HarmonyOS7 页面时这个顺序通常比一上来堆 UI 更稳。