歌词是播放器类App必不可少的组件,而一般的歌词组件都需要做到歌词的显示与播放进度同步。我们知道,歌词是如下所示的文件:
lrc
[ti:原来爱情这么伤]
[ar:梁咏琪]
[al:给自己的情歌]
[00:00.55]梁咏琪 - 原来爱情这么伤
[00:05.43]作词:彭学斌
[00:06.68]作曲:彭学斌
[00:09.63]
[00:22.27]我睁开眼睛 却感觉不到天亮
[00:29.74]东西吃一半 莫名其妙哭一场
[00:37.06]我忍住不想 时间变得更漫长
[00:44.09]也与你有关 否则又开始胡思乱想
[00:53.81]我日月无光 忙得不知所以然
[00:59.96]找朋友交谈 其实全帮不上忙
[01:07.49]以为会习惯 有你在才是习惯
[01:14.62]你曾住在我心上 现在空了一个地方
[01:21.89]原来爱情这么伤 比想象中还难
[01:29.90]泪水总是不听话 幸福躲起来不声不响
[01:37.43]太多道理太牵强 道理全是一样
[01:44.34]说的时候很简单 爱上后却正巧打乱
[02:00.00]我日月无光 忙得不知所以然
[02:07.41]找朋友交谈 其实全帮不上忙
[02:15.07]以为会习惯 有你在才是习惯
[02:21.88]你曾住在我心上 现在空了一个地方
[02:29.38]原来爱情这么伤 比想象中还难
[02:36.60]泪水总是不听话 幸福躲起来不声不响
[02:44.22]太多道理太牵强 道理全是一样
[02:50.78]说的时候很简单 爱上后却正巧打乱
[03:00.32]只想变的坚强 强到能够去忘
[03:07.29]无所谓悲伤 只要学会抵抗
[03:14.19]原来爱情这么伤
[03:20.78]原来爱情是这样 这样峰回路转
[03:28.12]泪水明明流不干 瞎了眼还要再爱一趟
[03:35.83]有一天终于打完 思念的一场战
[03:43.45]回过头再看一看 原来爱情那么伤
[03:54.76]下次还会不会这样
[88:88.88]
我们需要读取以上歌词文件的每一行转换成成一个个歌词实体:
Java代码
packagecom.music.lyricsync;
publicclassLyricObject{
publicintbegintime;//开始时间
publicintendtime;//结束时间
publicinttimeline;//单句歌词用时
publicStringlrc;//单句歌词
}
可根据当前播放器的播放进度与每句歌词的开始时间,得到当前屏幕中央高亮显示的那句歌词。在UI线程中另起线程,通过回调函数 onDraw() 每隔100ms重新绘制屏幕,实现歌词平滑滚动的动画效果。MainActivity代码如下:
Java代码
packagecom.music.lyricsync;
importjava.io.IOException;
importandroid.app.Activity;
importandroid.media.MediaPlayer;
.Uri;
importandroid.os.Bundle;
importandroid.os.Environment;
importandroid.os.Handler;
importandroid.view.View;
importandroid.view.View.OnClickListener;
importandroid.widget.Button;
importandroid.widget.SeekBar;
importandroid.widget.SeekBar.OnSeekBarChangeListener;
publicclassMainActivityextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
privateLyricViewlyricView;
privateMediaPlayermediaPlayer;
privateButtonbutton;
privateSeekBarseekBar;
privateStringmp3Path;
privateintINTERVAL=45;//歌词每行的间隔
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
//this.requestWindowFeature(Window.FEATURE_NO_TITLE);
//getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
mp3Path=Environment.getExternalStorageDirectory().getAbsolutePath()+"/LyricSync/1.mp3";
lyricView=(LyricView)findViewById(R.id.mylrc);
mediaPlayer=newMediaPlayer();
//this.requestWindowFeature(Window.FEATURE_NO_TITLE);
ResetMusic(mp3Path);
SerchLrc();
lyricView.SetTextSize();
button=(Button)findViewById(R.id.button);
button.setText("播放");
seekBar=(SeekBar)findViewById(R.id.seekbarmusic);
seekBar.setOnSeekBarChangeListener(newOnSeekBarChangeListener(){
@Override
publicvoidonStopTrackingTouch(SeekBarseekBar){
//TODOAuto-generatedmethodstub
}
@Override
publicvoidonStartTrackingTouch(SeekBarseekBar){
//TODOAuto-generatedmethodstub
}
@Override
publicvoidonProgressChanged(SeekBarseekBar,intprogress,
booleanfromUser){
//TODOAuto-generatedmethodstub
if(fromUser){
mediaPlayer.seekTo(progress);
lyricView.setOffsetY(220-lyricView.SelectIndex(progress)
*(lyricView.getSIZEWORD()+INTERVAL-1));
}
}
});
button.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
//TODOAuto-generatedmethodstub
if(mediaPlayer.isPlaying()){
button.setText("播放");
mediaPlayer.pause();
}else{
button.setText("暂停");
mediaPlayer.start();
lyricView.setOffsetY(220-lyricView.SelectIndex(mediaPlayer.getCurrentPosition())
*(lyricView.getSIZEWORD()+INTERVAL-1));
}
}
});
mediaPlayer.setOnCompletionListener(newMediaPlayer.OnCompletionListener(){
@Override
publicvoidonCompletion(MediaPlayermp){
ResetMusic(mp3Path);
lyricView.SetTextSize();
lyricView.setOffsetY(200);
mediaPlayer.start();
}
});
seekBar.setMax(mediaPlayer.getDuration());
newThread(newrunable()).start();
}
publicvoidSerchLrc(){
Stringlrc=mp3Path;
lrc=lrc.substring(0,lrc.length()-4).trim()+".lrc".trim();
LyricView.read(lrc);
lyricView.SetTextSize();
lyricView.setOffsetY(350);
}
publicvoidResetMusic(Stringpath){
mediaPlayer.reset();
try{
mediaPlayer.setDataSource(mp3Path);
mediaPlayer.prepare();
}catch(IllegalArgumentExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}catch(IllegalStateExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}catch(IOExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
classrunableimplementsRunnable{
@Override
publicvoidrun(){
//TODOAuto-generatedmethodstub
while(true){
try{
Thread.sleep(100);
if(mediaPlayer.isPlaying()){
lyricView.setOffsetY(lyricView.getOffsetY()-lyricView.SpeedLrc());
lyricView.SelectIndex(mediaPlayer.getCurrentPosition());
seekBar.setProgress(mediaPlayer.getCurrentPosition());
mHandler.post(mUpdateResults);
}
}catch(InterruptedExceptione){
//TODOAuto-generatedcatchblock
e.printStackTrace();
}
}
}
}
HandlermHandler=newHandler();
RunnablemUpdateResults=newRunnable(){
publicvoidrun(){
lyricView.invalidate();//更新视图
}
};
}
歌词View的代码如下:
Java代码
packagecom.music.lyricsync;
importjava.io.BufferedReader;
importjava.io.File;
importjava.io.FileInputStream;
importjava.io.FileNotFoundException;
importjava.io.IOException;
importjava.io.InputStreamReader;
importjava.util.Iterator;
importjava.util.TreeMap;
importjava.util.regex.Matcher;
importjava.util.regex.Pattern;
importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.Paint;
importandroid.util.AttributeSet;
importandroid.util.Log;
importandroid.view.MotionEvent;
importandroid.view.View;
publicclassLyricViewextendsView{
privatestaticTreeMaplrc_map;
privatefloatmX;//屏幕X轴的中点,此值固定,保持歌词在X中间显示
privatefloatoffsetY;//歌词在Y轴上的偏移量,此值会根据歌词的滚动变小
privatestaticbooleanblLrc=false;
privatefloattouchY;//当触摸歌词View时,保存为当前触点的Y轴坐标
privatefloattouchX;
privatebooleanblScrollView=false;
privateintlrcIndex=0;//保存歌词TreeMap的下标
privateintSIZEWORD=0;//显示歌词文字的大小值
privateintINTERVAL=45;//歌词每行的间隔
Paintpaint=newPaint();//画笔,用于画不是高亮的歌词
PaintpaintHL=newPaint();//画笔,用于画高亮的歌词,即当前唱到这句歌词
publicLyricView(Contextcontext){
super(context);
init();
}
publicLyricView(Contextcontext,AttributeSetattrs){
super(context,attrs);
init();
}
/*(non-Javadoc)
*@seeandroid.view.View#onDraw(android.graphics.Canvas)
*/
@Override
protectedvoidonDraw(Canvascanvas){
if(blLrc){
paintHL.setTextSize(SIZEWORD);
paint.setTextSize(SIZEWORD);
LyricObjecttemp=lrc_map.get(lrcIndex);
canvas.drawText(temp.lrc,mX,offsetY+(SIZEWORD+INTERVAL)*lrcIndex,paintHL);
//画当前歌词之前的歌词
for(inti=lrcIndex-1;i>=0;i--){
temp=lrc_map.get(i);
if(offsetY+(SIZEWORD+INTERVAL)*i<0){
break;
}
canvas.drawText(temp.lrc,mX,offsetY+(SIZEWORD+INTERVAL)*i,paint);
}
//画当前歌词之后的歌词
for(inti=lrcIndex+1;i
temp=lrc_map.get(i);
if(offsetY+(SIZEWORD+INTERVAL)*i>600){
break;
}
canvas.drawText(temp.lrc,mX,offsetY+(SIZEWORD+INTERVAL)*i,paint);
}
}
else{
paint.setTextSize(25);
canvas.drawText("找不到歌词",mX,310,paint);
}
super.onDraw(canvas);
}
/*(non-Javadoc)
*@seeandroid.view.View#onTouchEvent(android.view.MotionEvent)
*/
@Override
publicbooleanonTouchEvent(MotionEventevent){
//TODOAuto-generatedmethodstub
System.out.println("bllll==="+blScrollView);
floattt=event.getY();
if(!blLrc){
//returnsuper.onTouchEvent(event);
returnsuper.onTouchEvent(event);
}
switch(event.getAction()){
caseMotionEvent.ACTION_DOWN:
touchX=event.getX();
break;
caseMotionEvent.ACTION_MOVE:
touchY=tt-touchY;
offsetY=offsetY+touchY;
break;
caseMotionEvent.ACTION_UP:
blScrollView=false;
break;
}
touchY=tt;
returntrue;
}
publicvoidinit(){
lrc_map=newTreeMap();
offsetY=320;
paint=newPaint();
paint.setTextAlign(Paint.Align.CENTER);
paint.setColor(Color.GREEN);
paint.setAntiAlias(true);
paint.setDither(true);
paint.setAlpha(180);
paintHL=newPaint();
paintHL.setTextAlign(Paint.Align.CENTER);
paintHL.setColor(Color.RED);
paintHL.setAntiAlias(true);
paintHL.setAlpha(255);
}
/**
*根据歌词里面最长的那句来确定歌词字体的大小
*/
publicvoidSetTextSize(){
if(!blLrc){
return;
}
intmax=lrc_map.get(0).lrc.length();
for(inti=1;i
LyricObjectlrcStrLength=lrc_map.get(i);
if(max
max=lrcStrLength.lrc.length();
}
}
SIZEWORD=320/max;
}
protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){
mX=w*0.5f;
super.onSizeChanged(w,h,oldw,oldh);
}
/**
*歌词滚动的速度
*
*@return返回歌词滚动的速度
*/
publicFloatSpeedLrc(){
floatspeed=0;
if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex>220){
speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20);
}elseif(offsetY+(SIZEWORD+INTERVAL)*lrcIndex
Log.i("speed","speedistoofast!!!");
speed=0;
}
//if(speed<0.2){
//speed=0.2f;
//}
returnspeed;
}
/**
*按当前的歌曲的播放时间,从歌词里面获得那一句
*@paramtime当前歌曲的播放时间
*@return返回当前歌词的索引值
*/
publicintSelectIndex(inttime){
if(!blLrc){
return0;
}
intindex=0;
for(inti=0;i
LyricObjecttemp=lrc_map.get(i);
if(temp.begintime
++index;
}
}
lrcIndex=index-1;
if(lrcIndex<0){
lrcIndex=0;
}
returnlrcIndex;
}
/**
*读取歌词文件
*@paramfile歌词的路径
*
*/
publicstaticvoidread(Stringfile){
TreeMaplrc_read=newTreeMap();
Stringdata="";
try{
FilesaveFile=newFile(file);
//System.out.println("是否有歌词文件"+saveFile.isFile());
if(!saveFile.isFile()){
blLrc=false;
return;
}
blLrc=true;
//System.out.println("bllrc==="+blLrc);
FileInputStreamstream=newFileInputStream(saveFile);//context.openFileInput(file);
BufferedReaderbr=newBufferedReader(newInputStreamReader(stream,"GB2312"));
inti=0;
Patternpattern=pile("\\d{2}");
while((data=br.readLine())!=null){
//System.out.println("++++++++++++>>"+data);
data=data.replace("[","");//将前面的替换成后面的
data=data.replace("]","@");
Stringsplitdata[]=data.split("@");//分隔
if(data.endsWith("@")){
for(intk=0;k
Stringstr=splitdata[k];
str=str.replace(":",".");
str=str.replace(".","@");
Stringtimedata[]=str.split("@");
Matchermatcher=pattern.matcher(timedata[0]);
if(timedata.length==3&&matcher.matches()){
intm=Integer.parseInt(timedata[0]);//分
ints=Integer.parseInt(timedata[1]);//秒
intms=Integer.parseInt(timedata[2]);//毫秒
intcurrTime=(m*60+s)*1000+ms*10;
LyricObjectitem1=newLyricObject();
item1.begintime=currTime;
item1.lrc="";
lrc_read.put(currTime,item1);
}
}
}
else{
StringlrcContenet=splitdata[splitdata.length-1];
for(intj=0;j
{
Stringtmpstr=splitdata[j];
tmpstr=tmpstr.replace(":",".");
tmpstr=tmpstr.replace(".","@");
Stringtimedata[]=tmpstr.split("@");
Matchermatcher=pattern.matcher(timedata[0]);
if(timedata.length==3&&matcher.matches()){
intm=Integer.parseInt(timedata[0]);//分
ints=Integer.parseInt(timedata[1]);//秒
intms=Integer.parseInt(timedata[2]);//毫秒
intcurrTime=(m*60+s)*1000+ms*10;
LyricObjectitem1=newLyricObject();
item1.begintime=currTime;
item1.lrc=lrcContenet;
lrc_read.put(currTime,item1);//将currTime当标签item1当数据插入TreeMap里
i++;
}
}
}
}
stream.close();
}
catch(FileNotFoundExceptione){
}
catch(IOExceptione){
}
/*
*遍历hashmap计算每句歌词所需要的时间
*/
lrc_map.clear();
data="";
Iteratoriterator=lrc_read.keySet().iterator();
LyricObjectoldval=null;
inti=0;
while(iterator.hasNext()){
Objectob=iterator.next();
LyricObjectval=(LyricObject)lrc_read.get(ob);
if(oldval==null)
oldval=val;
else
{
LyricObjectitem1=newLyricObject();
item1=oldval;
item1.timeline=val.begintime-oldval.begintime;
lrc_map.put(newInteger(i),item1);
i++;
oldval=val;
}
if(!iterator.hasNext()){
lrc_map.put(newInteger(i),val);
}
}
}
/**
*@returntheblLrc
*/
publicstaticbooleanisBlLrc(){
returnblLrc;
}
/**
*@returntheoffsetY
*/
publicfloatgetOffsetY(){
returnoffsetY;
}
/**
*@paramoffsetYtheoffsetYtoset
*/
publicvoidsetOffsetY(floatoffsetY){
this.offsetY=offsetY;
}
/**
*@return返回歌词文字的大小
*/
publicintgetSIZEWORD(){
returnSIZEWORD;
}
/**
*设置歌词文字的大小
*@paramsIZEWORDthesIZEWORDtoset
*/
publicvoidsetSIZEWORD(intsIZEWORD){
SIZEWORD=sIZEWORD;
}
}
xml布局文件如下:
Xml代码
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF">
android:id="@+id/mylrc"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginBottom="50dip"
android:layout_marginTop="50dip"/>
xmlns:android="/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
android:id="@+id/seekbarmusic"
android:layout_width="205px"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="5px"
android:progress="0"/>
如果觉得《android开发歌词滑动效果_Android 歌词同步滚动效果》对你有帮助,请点赞、收藏,并留下你的观点哦!