您的位置:首页 > 移动开发 > Android开发

Android 实现歌曲播放时歌词同步显示

2013-05-17 15:11 686 查看
我们需要读取以上歌词文件的每一行转换成成一个个歌词实体:

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 TreeMap<Integer, 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)*i<0){  
                    break;  
                }  
                canvas.drawText(temp.lrc, 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.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 TreeMap<Integer, 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;i<lrc_map.size();i++){  
            LyricObject lrcStrLength=lrc_map.get(i);  
            if(max<lrcStrLength.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)*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;  
        }  
//      if(speed<0.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;i<lrc_map.size();i++){  
            LyricObject temp=lrc_map.get(i);  
            if(temp.begintime<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.begintime = currTime;  
                            item1.lrc       = "";  
                            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.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 ="";  
        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;  
                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布局文件如下:

<?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>  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: