Androidで楽曲再生時の歌詞同時表示を実現


上記の歌詞ファイルの各行を1つの歌詞エンティティに変換する必要があります.
public class LyricObject {  
    public int begintime; //       
    public int endtime; //       
    public int timeline; //         
    public String lrc; //       
}  

現在のプレーヤーの再生進捗と各歌詞の開始時間に基づいて、現在の画面中央にハイライト表示されている歌詞を得ることができます.UIスレッドにスレッドを追加し、コールバック関数onDraw()で100 msごとに画面を再描画し、歌詞をスムーズにスクロールさせるアニメーション効果を実現します.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>