边播放歌曲边滚动歌词

一、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; // 单句歌词  
}
原文地址:https://www.cnblogs.com/ct732003684/p/2862977.html