一、LyricView
package com.ct.lrc; 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.R.integer; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; public class LyricView extends View{ private Context context; private float offsetY; //歌词在Y轴上的偏移量,此值会根据歌词的滚动变小 private static TreeMap<Integer, LyricObject> lrc_map; private static boolean blLrc=false; private float mX; //屏幕X轴的中点,此值固定,保持歌词在X中间显示 Paint paint = new Paint(); Paint paintHL = new Paint(); private int SIZEWORD=0;//显示歌词文字的大小值 private int INTERVAL=45;//歌词每行的间隔 private int lrcIndex=0; //保存歌词TreeMap的下标 private float touchY; //当触摸歌词View时,保存为当前触点的Y轴坐标 private float touchX; private boolean blScrollView=false; public LyricView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); // TODO Auto-generated constructor stub } public LyricView(Context context, AttributeSet attrs) { super(context, attrs); init(); // TODO Auto-generated constructor stub } public LyricView(Context context) { super(context); init(); // TODO Auto-generated constructor stub } private void init(){ lrc_map = new TreeMap<Integer, LyricObject>(); offsetY = 320; paint = new Paint(); paint.setAlpha(180); paint.setColor(Color.GREEN); paint.setTextAlign(Align.CENTER); paint.setDither(true); paint.setAntiAlias(true); paintHL = new Paint(); paintHL.setAlpha(255); paintHL.setColor(Color.RED); paintHL.setTextAlign(Align.CENTER); paintHL.setDither(true); paintHL.setAntiAlias(true); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub if(blLrc){//歌词存在的情况 paint.setTextSize(SIZEWORD); paintHL.setTextSize(SIZEWORD); LyricObject temp = lrc_map.get(lrcIndex); canvas.drawText(temp.getLrc(),mX, offsetY+(SIZEWORD+INTERVAL)*lrcIndex, paintHL); // 画当前歌词之前的歌词 for(int i = lrcIndex - 1;i>0;i--){ temp = lrc_map.get(i); if(offsetY+(SIZEWORD+INTERVAL)*i<0){ break; } canvas.drawText(temp.getLrc(), mX, offsetY+(SIZEWORD+INTERVAL)*i, paint); } for(int i = lrcIndex + 1;i<lrc_map.size();i++){ temp = lrc_map.get(i); if(offsetY+(SIZEWORD+INTERVAL)*i>600){ break; } canvas.drawText(temp.getLrc(),mX, offsetY+(SIZEWORD+INTERVAL)*i, paint); } }else { paint.setTextSize(25); canvas.drawText("找不到歌词", mX, 310, paint); } super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub float tt = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touchX = event.getX(); break; case MotionEvent.ACTION_MOVE: touchY = tt - touchY; offsetY = touchY + offsetY; break; case MotionEvent.ACTION_UP: blScrollView = false; break; default: break; } touchY = tt; return true; } /** * 根据歌词里面最长的那句来确定歌词字体的大小 * */ public void SetTextSize(){ if(!blLrc){ return; } int max = lrc_map.get(0).getLrc().length(); for(int i=1;i<lrc_map.size();i++){ LyricObject temp = lrc_map.get(i); int length = temp.getLrc().length(); if(max < length){ max = length; } } SIZEWORD=320/max; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // TODO Auto-generated method stub mX = w * 0.5f; super.onSizeChanged(w, h, oldw, oldh); } /** * 歌词滚动的速度 * * @return 返回歌词滚动的速度 */ public float SpeedLrc(){ float speed = 0; if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex>220){ speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20); } else if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex < 120){ Log.i("speed", "speed is too fast!!!"); speed = 0; } return speed; } /** * 按当前的歌曲的播放时间,从歌词里面获得那一句 * @param time 当前歌曲的播放时间 * @return 返回当前歌词的索引值 */ public int SelectIndex(int time){ if(!blLrc){ return 0; } int index = 0; for(int i=0;i<lrc_map.size();i++){ LyricObject temp =lrc_map.get(i); if(temp.getBegintime() < time){ ++index; } } lrcIndex = index -1; if(lrcIndex < 0){ lrcIndex = 0; } return lrcIndex; } /** * 读取歌词文件 * @param file 歌词的路径 * */ public static void read(String file) { TreeMap<Integer, LyricObject> lrc_read =new TreeMap<Integer, 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;k<splitdata.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.setBegintime(currTime); item1.setLrc(""); lrc_read.put(currTime,item1); } } } else{ String lrcContenet = splitdata[splitdata.length-1]; for (int j=0;j<splitdata.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.setBegintime(currTime); item1.setLrc(lrcContenet); lrc_read.put(currTime,item1);// 将currTime当标签 item1当数据 插入TreeMap里 i++; } } } } stream.close(); } catch (FileNotFoundException e) { } catch (IOException e) { } /* * 遍历hashmap 计算每句歌词所需要的时间 */ lrc_map.clear(); data =""; Iterator<Integer> 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; int delta = val.getBegintime() - oldval.getBegintime(); item1.setTimeline(delta); 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 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.ct.lrc.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>
三、mainactivty
package com.ct.lrc; import java.io.IOException; import android.app.Activity; import android.media.MediaPlayer; 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 miniStart; private SeekBar seekBar; private String mp3Path; private int INTERVAL=45;//歌词每行的间隔 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mp3Path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/lyq.mp3"; System.out.println("ct test------------->mp3Path "+mp3Path); lyricView = (LyricView) findViewById(R.id.mylrc); mediaPlayer = new MediaPlayer(); ResetMusic(mp3Path); SerchLrc(); lyricView.SetTextSize(); miniStart = (Button) findViewById(R.id.button); miniStart.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)); } } }); miniStart.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub if (mediaPlayer.isPlaying()) { miniStart.setText("播放"); mediaPlayer.pause(); } else { miniStart.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(); } 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(); // 更新视图 } }; private 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(); } } public void SerchLrc() { String lrc = mp3Path; lrc = lrc.substring(0, lrc.length() - 4).trim() + ".lrc".trim(); LyricView.read(lrc); lyricView.SetTextSize(); lyricView.setOffsetY(350); } }
四、LyricObject
package com.ct.lrc; public class LyricObject { public int begintime; // 开始时间 public int endtime; // 结束时间 public int timeline; // 单句歌词用时 public String lrc; // 单句歌词 }