☰
歌词滚动效果实现详解
2026/9/28 20:19:58 网站建设 项目流程

歌词滚动效果实现详解

1. 引入:音乐播放器的歌词是怎么滚动的?

用过音乐播放器的都知道,歌词会随着歌曲进度自动滚动,当前唱的那一句高亮显示,而且始终保持在屏幕中间。

┌─────────────────────┐ │ 后来 我总算学会了 │ │ 如何去爱 │ │ ► 可惜你早已远去 ◄ │ ← 当前歌词,高亮居中 │ 消失在人海 │ │ 后来 终于在眼泪中 │ └─────────────────────┘

这个效果是怎么做的?核心用到两个东西:

技术作用
RecyclerView显示歌词列表
LinearSmoothScroller让指定歌词平滑滚动到中间

2. 整体思路

1. 用RecyclerView显示所有歌词 2. 监听歌曲播放进度 3. 根据进度找到当前应该唱的那句歌词 4. 用LinearSmoothScroller把这句歌词滚到屏幕中间 5. 高亮显示当前歌词,其他歌词正常显示

3. 歌词数据解析

3.1 歌词文件格式

[00:00.00] 后来 [00:05.32] 我总算学会了 [00:10.15] 如何去爱 [00:15.42] 可惜你早已远去 [00:20.18] 消失在人海

3.2 解析歌词

publicclassLyric{privatelongtime;// 歌词开始时间(毫秒)privateStringcontent;// 歌词内容publicLyric(longtime,Stringcontent){this.time=time;this.content=content;}publiclonggetTime(){returntime;}publicStringgetContent(){returncontent;}}// 解析歌词publicList<Lyric>parseLyric(StringlyricText){List<Lyric>lyrics=newArrayList<>();String[]lines=lyricText.split("\n");for(Stringline:lines){// 匹配 [00:05.32]歌词内容Patternpattern=Pattern.compile("\\[(\\d{2}):(\\d{2})\\.(\\d{2})\\](.*)");Matchermatcher=pattern.matcher(line);if(matcher.find()){intminute=Integer.parseInt(matcher.group(1));intsecond=Integer.parseInt(matcher.group(2));intmillis=Integer.parseInt(matcher.group(3));Stringcontent=matcher.group(4).trim();longtime=minute*60*1000+second*1000+millis*10;lyrics.add(newLyric(time,content));}}returnlyrics;}

4. RecyclerView显示歌词

4.1 布局文件

<androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_lyric"android:layout_width="match_parent"android:layout_height="match_parent"android:overScrollMode="never"android:scrollbars="none"/>

4.2 歌词Item布局

<TextViewandroid:id="@+id/tv_lyric"android:layout_width="match_parent"android:layout_height="60dp"android:gravity="center"android:textSize="16sp"android:textColor="#999999"android:maxLines="1"android:ellipsize="end"/>

4.3 Adapter

publicclassLyricAdapterextendsRecyclerView.Adapter<LyricAdapter.LyricHolder>{privateList<Lyric>lyricList;privateintcurrentPosition=-1;// 当前歌词位置publicLyricAdapter(List<Lyric>lyricList){this.lyricList=lyricList;}// 更新当前歌词位置publicvoidsetCurrentPosition(intposition){intoldPosition=currentPosition;currentPosition=position;// 刷新旧的和新的(只刷新两个Item,性能好)if(oldPosition>=0){notifyItemChanged(oldPosition);}if(currentPosition>=0){notifyItemChanged(currentPosition);}}@OverridepublicLyricHolderonCreateViewHolder(ViewGroupparent,intviewType){Viewview=LayoutInflater.from(parent.getContext()).inflate(R.layout.item_lyric,parent,false);returnnewLyricHolder(view);}@OverridepublicvoidonBindViewHolder(LyricHolderholder,intposition){Lyriclyric=lyricList.get(position);holder.tvLyric.setText(lyric.getContent());// 当前歌词高亮if(position==currentPosition){holder.tvLyric.setTextColor(Color.WHITE);holder.tvLyric.setTextSize(18);}else{holder.tvLyric.setTextColor(Color.parseColor("#999999"));holder.tvLyric.setTextSize(16);}}@OverridepublicintgetItemCount(){returnlyricList.size();}staticclassLyricHolderextendsRecyclerView.ViewHolder{TextViewtvLyric;publicLyricHolder(ViewitemView){super(itemView);tvLyric=itemView.findViewById(R.id.tv_lyric);}}}

5. 核心:让歌词滚到中间

5.1 问题:smoothScrollToPosition的局限

// 这样滚动,目标Item只会滚到屏幕边缘或可见位置recyclerView.smoothScrollToPosition(position);

问题:smoothScrollToPosition()只是让目标Item可见,不保证在屏幕正中间。

5.2 解决方案:自定义LinearSmoothScroller

重写LinearSmoothScroller,让它把目标Item居中显示:

publicclassCenterSmoothScrollerextendsLinearSmoothScroller{publicCenterSmoothScroller(Contextcontext){super(context);}// 关键:计算目标位置偏移量@OverridepublicintcalculateDtToFit(intviewStart,intviewEnd,intboxStart,intboxEnd,intsnapPreference){// 返回当前View中心到父容器中心的距离return(boxStart+boxEnd)/2-(viewStart+viewEnd)/2;}}

calculateDtToFit解释:

参数说明
viewStart目标View的起始坐标
viewEnd目标View的结束坐标
boxStartRecyclerView的起始坐标
boxEndRecyclerView的结束坐标
snapPreference对齐偏好

核心公式:

偏移量 = (RecyclerView中心) - (目标View中心)

这样滚动时,目标Item就会居中对齐。

5.3 使用自定义SmoothScroller

// 创建LayoutManagerLinearLayoutManagerlayoutManager=newLinearLayoutManager(this){@OverridepublicvoidsmoothScrollToPosition(RecyclerViewrecyclerView,RecyclerView.Statestate,intposition){// 使用自定义的SmoothScrollerCenterSmoothScrollerscroller=newCenterSmoothScroller(recyclerView.getContext());scroller.setTargetPosition(position);startSmoothScroll(scroller);}};recyclerView.setLayoutManager(layoutManager);

5.4 滚动到指定位置

// 滚动到当前歌词位置(会居中显示)recyclerView.smoothScrollToPosition(currentPosition);

6. 完整实现流程

6.1 Activity/Fragment中初始化

publicclassLyricActivityextendsAppCompatActivity{privateRecyclerViewrvLyric;privateLyricAdapteradapter;privateList<Lyric>lyricList;privateLinearLayoutManagerlayoutManager;privateintcurrentPosition=0;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_lyric);rvLyric=findViewById(R.id.rv_lyric);// 1. 解析歌词lyricList=parseLyric(lyricText);// 2. 设置Adapteradapter=newLyricAdapter(lyricList);rvLyric.setAdapter(adapter);// 3. 设置LayoutManager(带居中滚动)layoutManager=newLinearLayoutManager(this){@OverridepublicvoidsmoothScrollToPosition(RecyclerViewrecyclerView,RecyclerView.Statestate,intposition){CenterSmoothScrollerscroller=newCenterSmoothScroller(recyclerView.getContext());scroller.setTargetPosition(position);startSmoothScroll(scroller);}};rvLyric.setLayoutManager(layoutManager);}}

6.2 监听歌曲进度

// 假设用MediaPlayer播放mediaPlayer.setOnCompletionListener(mp->{// 播放完成});// 用Handler定期检查播放进度privateHandlerhandler=newHandler();privateRunnableupdateLyricRunnable=newRunnable(){@Overridepublicvoidrun(){if(mediaPlayer!=null&&mediaPlayer.isPlaying()){// 获取当前播放位置intcurrentTime=mediaPlayer.getCurrentPosition();// 找到当前应该唱的那句歌词intnewPosition=findCurrentLyric(currentTime);if(newPosition!=currentPosition){currentPosition=newPosition;// 更新歌词高亮adapter.setCurrentPosition(currentPosition);// 滚动到中间rvLyric.smoothScrollToPosition(currentPosition);}// 每200毫秒更新一次handler.postDelayed(this,200);}}};// 根据时间找到对应的歌词位置privateintfindCurrentLyric(longtime){for(inti=lyricList.size()-1;i>=0;i--){if(time>=lyricList.get(i).getTime()){returni;}}return0;}// 开始更新handler.post(updateLyricRunnable);// 停止更新// handler.removeCallbacks(updateLyricRunnable);

7. 优化技巧

7.1 避免频繁滚动

// 只在歌词变化时才滚动if(newPosition!=currentPosition){currentPosition=newPosition;adapter.setCurrentPosition(currentPosition);rvLyric.smoothScrollToPosition(currentPosition);}

7.2 用户手动滑动时暂停自动滚动

booleanisUserScrolling=false;rvLyric.addOnScrollListener(newRecyclerView.OnScrollListener(){@OverridepublicvoidonScrollStateChanged(RecyclerViewrecyclerView,intnewState){if(newState==RecyclerView.SCROLL_STATE_DRAGGING){// 用户正在拖动isUserScrolling=true;}elseif(newState==RecyclerView.SCROLL_STATE_IDLE){// 停止拖动,延迟几秒后恢复自动滚动newHandler().postDelayed(()->isUserScrolling=false,3000);}}});// 更新歌词时判断if(!isUserScrolling&&newPosition!=currentPosition){rvLyric.smoothScrollToPosition(newPosition);}

7.3 调整滚动速度

publicclassCenterSmoothScrollerextendsLinearSmoothScroller{@OverrideprotectedfloatcalculateSpeedPerPixel(DisplayMetricsdisplayMetrics){// 返回值越小,滚动越快return50f/displayMetrics.densityDpi;}}

展示:


8. 核心API总结

API作用
LinearSmoothScroller平滑滚动器
calculateDtToFit()计算目标位置偏移量(居中关键)
setTargetPosition()设置目标位置
startSmoothScroll()开始平滑滚动
calculateSpeedPerPixel()控制滚动速度

9. 易错点总结

易错点说明
直接调用smoothScrollToPosition不会居中,只会滚到可见位置
忘记重写smoothScrollToPosition自定义Scroller不生效
频繁调用smoothScrollToPosition导致卡顿,应只在歌词变化时调用
用户滑动时仍自动滚动体验差,应暂停自动滚动
高亮刷新用notifyDataSetChanged性能差,应只刷新变化的两个Item

10. 一句话总结

歌词滚动的核心就是:RecyclerView显示歌词 + 重写LinearSmoothScroller的calculateDtToFit方法让目标歌词居中 + 监听播放进度定时滚动。其中calculateDtToFit是关键,它通过计算目标View中心与RecyclerView中心的偏移量,实现居中滚动效果。

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

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

立即咨询