【 tulaoshi.com - 编程语言 】
                             
                            我们需要读取以上歌词文件的每一行转换成成一个个歌词实体: 
代码如下: 
public class LyricObject { 
public int begintime; // 开始时间 
public int endtime; // 结束时间 
public int timeline; // 单句歌词用时 
public String lrc; // 单句歌词 
}  
可根据当前播放器的播放进度与每句歌词的开始时间,得到当前屏幕中央高亮显示的那句歌词。在UI线程中另起线程,通过回调函数 onDraw() 每隔100ms重新绘制屏幕,实现歌词平滑滚动的动画效果。MainActivity代码如下: 
代码如下: 
import java.io.IOException; 
import android.app.Activity; 
import android.media.MediaPlayer; 
import android.net.Uri; 
import android.os.Bundle; 
import android.os.Environment; 
import android.os.Handler; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.widget.Button; 
import android.widget.SeekBar; 
import android.widget.SeekBar.OnSeekBarChangeListener; 
public class MainActivity extends Activity { 
/** Called when the activity is first created. */ 
private LyricView lyricView; 
private MediaPlayer mediaPlayer; 
private Button button; 
private SeekBar seekBar; 
private String mp3Path; 
private int INTERVAL=45;//歌词每行的间隔 
@Override 
public void onCreate(Bundle savedInstanceState) { 
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 = new MediaPlayer(); 
// 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(new OnSeekBarChangeListener() { 
@Override 
public void onStopTrackingTouch(SeekBar seekBar) { 
// TODO Auto-generated method stub 
} 
@Override 
public void onStartTrackingTouch(SeekBar seekBar) { 
// TODO Auto-generated method stub 
} 
@Override 
public void onProgressChanged(SeekBar seekBar, int progress, 
boolean fromUser) { 
// TODO Auto-generated method stub 
if (fromUser) { 
mediaPlayer.seekTo(progress); 
lyricView.setOffsetY(220 - lyricView.SelectIndex(progress) 
* (lyricView.getSIZEWORD() + INTERVAL-1)); 
} 
} 
}); 
button.setOnClickListener(new OnClickListener() { 
@Override 
public void onClick(View v) { 
// TODO Auto-generated method stub 
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(new MediaPlayer.OnCompletionListener() { 
@Override 
public void onCompletion(MediaPlayer mp) { 
ResetMusic(mp3Path); 
lyricView.SetTextSize(); 
lyricView.setOffsetY(200); 
mediaPlayer.start(); 
} 
}); 
seekBar.setMax(mediaPlayer.getDuration()); 
new Thread(new runable()).start(); 
} 
public void SerchLrc() { 
String lrc = mp3Path; 
lrc = lrc.substring(0, lrc.length() - 4).trim() + ".lrc".trim(); 
LyricView.read(lrc); 
lyricView.SetTextSize(); 
lyricView.setOffsetY(350); 
} 
public void ResetMusic(String path) { 
mediaPlayer.reset(); 
try { 
mediaPlayer.setDataSource(mp3Path); 
mediaPlayer.prepare(); 
} catch (IllegalArgumentException e) { 
// TODO Auto-generated catch block 
e.printStackTrace(); 
} catch (IllegalStateException e) { 
// TODO Auto-generated catch block 
e.printStackTrace(); 
} catch (IOException e) { 
// TODO Auto-generated catch block 
e.printStackTrace(); 
} 
} 
class runable implements Runnable { 
@Override 
public void run() { 
// TODO Auto-generated method stub 
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 (InterruptedException e) { 
// TODO Auto-generated catch block 
e.printStackTrace(); 
} 
} 
} 
} 
Handler mHandler = new Handler(); 
Runnable mUpdateResults = new Runnable() { 
public void run() { 
lyricView.invalidate(); // 更新视图 
} 
}; 
}  
歌词View的代码如下: 
代码如下: 
import java.io.BufferedReader; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStreamReader; 
import java.util.Iterator; 
import java.util.TreeMap; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 
import android.content.Context; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.MotionEvent; 
import android.view.View; 
public class LyricView extends View{ 
private static TreeMapInteger, LyricObject lrc_map; 
private float mX; //屏幕X轴的中点,此值固定,保持歌词在X中间显示 
private float offsetY; //歌词在Y轴上的偏移量,此值会根据歌词的滚动变小 
private static boolean blLrc=false; 
private float touchY; //当触摸歌词View时,保存为当前触点的Y轴坐标 
private float touchX; 
private boolean blScrollView=false; 
private int lrcIndex=0; //保存歌词TreeMap的下标 
private int SIZEWORD=0;//显示歌词文字的大小值 
private int INTERVAL=45;//歌词每行的间隔 
Paint paint=new Paint();//画笔,用于画不是高亮的歌词 
Paint paintHL=new Paint(); //画笔,用于画高亮的歌词,即当前唱到这句歌词 
public LyricView(Context context){ 
super(context); 
init(); 
} 
public LyricView(Context context, AttributeSet attrs) { 
super(context, attrs); 
init(); 
} 
/* (non-Javadoc) 
* @see android.view.View#onDraw(android.graphics.Canvas) 
*/ 
@Override 
protected void onDraw(Canvas canvas) { 
if(blLrc){ 
paintHL.setTextSize(SIZEWORD); 
paint.setTextSize(SIZEWORD); 
LyricObject temp=lrc_map.get(lrcIndex); 
canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*lrcIndex, paintHL); 
// 画当前歌词之前的歌词 
for(int i=lrcIndex-1;i=0;i--){ 
temp=lrc_map.get(i); 
if(offsetY+(SIZEWORD+INTERVAL)*i0){ 
break; 
} 
canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint); 
} 
// 画当前歌词之后的歌词 
for(int i=lrcIndex+1;ilrc_map.size();i++){ 
temp=lrc_map.get(i); 
if(offsetY+(SIZEWORD+INTERVAL)*i600){ 
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) 
* @see android.view.View#onTouchEvent(android.view.MotionEvent) 
*/ 
@Override 
public boolean onTouchEvent(MotionEvent event) { 
// TODO Auto-generated method stub 
System.out.println("bllll==="+blScrollView); 
float tt=event.getY(); 
if(!blLrc){ 
//return super.onTouchEvent(event); 
return super.onTouchEvent(event); 
} 
switch(event.getAction()){ 
case MotionEvent.ACTION_DOWN: 
touchX=event.getX(); 
break; 
case MotionEvent.ACTION_MOVE: 
touchY=tt-touchY; 
offsetY=offsetY+touchY; 
break; 
case MotionEvent.ACTION_UP: 
blScrollView=false; 
break; 
} 
touchY=tt; 
return true; 
} 
public void init(){ 
lrc_map = new TreeMapInteger, LyricObject(); 
offsetY=320; 
paint=new Paint(); 
paint.setTextAlign(Paint.Align.CENTER); 
paint.setColor(Color.GREEN); 
paint.setAntiAlias(true); 
paint.setDither(true); 
paint.setAlpha(180); 
paintHL=new Paint(); 
paintHL.setTextAlign(Paint.Align.CENTER); 
paintHL.setColor(Color.RED); 
paintHL.setAntiAlias(true); 
paintHL.setAlpha(255); 
} 
/** 
* 根据歌词里面最长的那句来确定歌词字体的大小 
*/ 
public void SetTextSize(){ 
if(!blLrc){ 
return; 
} 
int max=lrc_map.get(0).lrc.length(); 
for(int i=1;ilrc_map.size();i++){ 
LyricObject lrcStrLength=lrc_map.get(i); 
if(maxlrcStrLength.lrc.length()){ 
max=lrcStrLength.lrc.length(); 
} 
} 
SIZEWORD=320/max; 
} 
protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
mX = w * 0.5f; 
super.onSizeChanged(w, h, oldw, oldh); 
} 
/** 
* 歌词滚动的速度 
* 
* @return 返回歌词滚动的速度 
*/ 
public Float SpeedLrc(){ 
float speed=0; 
if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex220){ 
speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20); 
} else if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex  120){ 
Log.i("speed", "speed is too fast!!!"); 
speed = 0; 
} 
// if(speed0.2){ 
// speed=0.2f; 
// } 
return speed; 
} 
/** 
* 按当前的歌曲的播放时间,从歌词里面获得那一句 
* @param time 当前歌曲的播放时间 
* @return 返回当前歌词的索引值 
*/ 
public int SelectIndex(int time){ 
if(!blLrc){ 
return 0; 
} 
int index=0; 
for(int i=0;ilrc_map.size();i++){ 
LyricObject temp=lrc_map.get(i); 
if(temp.begintimetime){ 
++index; 
} 
} 
lrcIndex=index-1; 
if(lrcIndex0){ 
lrcIndex=0; 
} 
return lrcIndex; 
} 
/** 
* 读取歌词文件 
* @param file 歌词的路径 
* 
*/ 
public static void read(String file) { 
TreeMapInteger, LyricObject lrc_read =new TreeMapInteger, LyricObject(); 
String data = ""; 
try { 
File saveFile=new File(file); 
// System.out.println("是否有歌词文件"+saveFile.isFile()); 
if(!saveFile.isFile()){ 
blLrc=false; 
return; 
} 
blLrc=true; 
//System.out.println("bllrc==="+blLrc); 
FileInputStream stream = new FileInputStream(saveFile);// context.openFileInput(file); 
BufferedReader br = new BufferedReader(new InputStreamReader(stream,"GB2312")); 
int i = 0; 
Pattern pattern = Pattern.compile("d{2}"); 
while ((data = br.readLine()) != null) { 
// System.out.println("++++++++++++"+data); 
data = data.replace("[","");//将前面的替换成后面的 
data = data.replace("]","@"); 
String splitdata[] =data.split("@");//分隔 
if(data.endsWith("@")){ 
for(int k=0;ksplitdata.length;k++){ 
String str=splitdata[k]; 
str = str.replace(":","."); 
str = str.replace(".","@"); 
String timedata[] =str.split("@"); 
Matcher matcher = pattern.matcher(timedata[0]); 
if(timedata.length==3 && matcher.matches()){ 
int m = Integer.parseInt(timedata[0]); //分 
int s = Integer.parseInt(timedata[1]); //秒 
int ms = Integer.parseInt(timedata[2]); //毫秒 
int currTime = (m*60+s)*1000+ms*10; 
LyricObject item1= new LyricObject(); 
item1.begintime = currTime; 
item1.lrc = ""; 
lrc_read.put(currTime,item1); 
} 
} 
} 
else{ 
String lrcContenet = splitdata[splitdata.length-1]; 
for (int j=0;jsplitdata.length-1;j++) 
{ 
String tmpstr = splitdata[j]; 
tmpstr = tmpstr.replace(":","."); 
tmpstr = tmpstr.replace(".","@"); 
String timedata[] =tmpstr.split("@"); 
Matcher matcher = pattern.matcher(timedata[0]); 
if(timedata.length==3 && matcher.matches()){ 
int m = Integer.parseInt(timedata[0]); //分 
int s = Integer.parseInt(timedata[1]); //秒 
int ms = Integer.parseInt(timedata[2]); //毫秒 
int currTime = (m*60+s)*1000+ms*10; 
LyricObject item1= new LyricObject(); 
item1.begintime = currTime; 
item1.lrc = lrcContenet; 
lrc_read.put(currTime,item1);// 将currTime当标签 item1当数据 插入TreeMap里 
i++; 
} 
} 
} 
} 
stream.close(); 
} 
catch (FileNotFoundException e) { 
} 
catch (IOException e) { 
} 
/* 
* 遍历hashmap 计算每句歌词所需要的时间 
*/ 
lrc_map.clear(); 
data =""; 
IteratorInteger iterator = lrc_read.keySet().iterator(); 
LyricObject oldval = null; 
int i =0; 
while(iterator.hasNext()) { 
Object ob =iterator.next(); 
LyricObject val = (LyricObject)lrc_read.get(ob); 
if (oldval==null) 
oldval = val; 
else 
{ 
LyricObject item1= new LyricObject(); 
item1 = oldval; 
item1.timeline = val.begintime-oldval.begintime; 
lrc_map.put(new Integer(i), item1); 
i++; 
oldval = val; 
} 
if (!iterator.hasNext()) { 
lrc_map.put(new Integer(i), val); 
} 
} 
} 
/** 
* @return the blLrc 
*/ 
public static boolean isBlLrc() { 
return blLrc; 
} 
/** 
* @return the offsetY 
*/ 
public float getOffsetY() { 
return offsetY; 
} 
/** 
* @param offsetY the offsetY to set 
*/ 
public void setOffsetY(float offsetY) { 
this.offsetY = offsetY; 
} 
/** 
* @return 返回歌词文字的大小 
*/ 
public int getSIZEWORD() { 
return SIZEWORD; 
} 
/** 
* 设置歌词文字的大小 
* @param sIZEWORD the sIZEWORD to set 
*/ 
public void setSIZEWORD(int sIZEWORD) { 
SIZEWORD = sIZEWORD; 
} 
}  
xml布局文件如下: 
代码如下: 
SPAN style="FONT-SIZE: 18px"STRONG?xml version="1.0" encoding="utf-8"? 
RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="fill_parent" 
android:layout_height="fill_parent" 
android:background="#FFFFFF"  
com.music.lyricsync.LyricView 
android:id="@+id/mylrc" 
android:layout_width="fill_parent" 
android:layout_height="fill_parent" 
android:layout_marginBottom="50dip" 
android:layout_marginTop="50dip" / 
LinearLayout 
xmlns:android="http://schemas.android.com/apk/res/android" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:layout_alignParentBottom="true" 
android:orientation="horizontal"  
Button 
android:id="@+id/button" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" / 
SeekBar 
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" / 
/LinearLayout 
/RelativeLayout /STRONG/SPAN