我们需要读取以上歌词文件的每一行转换成成一个个歌词实体:

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>  


Android 实现歌曲播放时歌词同步显示相关推荐

  1. android 歌词解析 依赖,Android实现歌曲播放时歌词同步显示具体思路

    我们需要读取以上歌词文件的每一行转换成成一个个歌词实体: 代码如下: public class LyricObject { public int begintime; // 开始时间 public i ...

  2. android简单歌词,《Android_MP3播放器(初学简单版_歌名、歌手、歌词同步显示)》.doc...

    Android_MP3播放器(初学简单版) --乐拐 这是我学习Android以来的第二个程序--MP3播放器(简单版),我的第一个程序是比较实用的通讯录(文档地址是:/view/d013f64fc8 ...

  3. android 音乐播放器-------歌词同步 lrc

    lrc格式 : [al:这首歌所在的唱片集 ] [ar:歌词作者 ] [by:本LRC文件的创建者 ] [offset:+/- 以毫秒为单位整体时间戳调整,+增加,-减小 ] [re:创建此LRC文件 ...

  4. android 本地lrc 歌词同步,android 音乐播放器-------歌词同步 lrc

    lrc格式 : [al:这首歌所在的唱片集 ] [ar:歌词作者 ] [by:本LRC文件的创建者 ] [offset:+/- 以毫秒为单位整体时间戳调整,+增加,-减小 ] [re:创建此LRC文件 ...

  5. javafx音乐播放器----歌词同步实时显示(包含获取酷我歌词方式,歌词同步方法)

    首先我是爬虫获取的酷我的音源,因此歌词也是通过爬虫获取的,下面这个方法可以获取到歌曲对应的歌词信息.简单说下,在搜索歌曲之后会返回一个歌曲列表,查看源代码是包含在li标签里面的,这个li标签里面就有请 ...

  6. Jplayer歌词同步显示插件

    http://blog.csdn.net/wk313753744/article/details/38758317 1.该插件是一个jquery的编写的跟jplayer实现歌词同步的插件,最终效果如图 ...

  7. Jplayer歌词同步显示插件(在以前别人基础上修改)

    1.该插件是一个jquery的编写的跟jplayer实现歌词同步的插件,最终效果如图: 2.首先引入jplayer的相关的js库和样式文件. <meta http-equiv="Con ...

  8. 网页中LRC歌词同步显示

    <html><head> <title>音乐歌词同步测试</title> <style> <!-- .div { width:460p ...

  9. Android 小技巧-- TextView与EditText 同步显示

    方法一.利用View.OnKeyListener"同步"显示 Java代码   EditText  myEdit = (EditText)findViewById(R.id.myE ...

最新文章

  1. 想自学stm32不知道怎么买板子?我来告诉你新手该买哪一个!
  2. npm i -g windows-build-tools安装出错解决方法
  3. 8.0强行转换后变成了7_【自学C#】|| 笔记 12 数据类型转换
  4. Sql养成一个好习惯是一笔财富
  5. 外伤导致色觉异常型复视(球后视神经炎+视神经萎缩)
  6. Orac and Medians CodeForces - 1350D(思维)
  7. DHCP服务器 出现的故障
  8. JS——实现短信验证码的倒计时功能(没有验证码,只有倒计时)
  9. 复制assert目录文件到私有目录_在电脑上复制目录的方法
  10. html表格输入框怎么左移动,当我向CSS中的文本框添加边框时,HTML表单输入会移动...
  11. 动态图相册 android,‎动态图相册 in de App Store
  12. Linux安全加固之账号密码安全策略
  13. smartdns使用指南_OpenWrt之SmartDNS 使用教程(PW版)
  14. Tags From Sina Micro-Blog
  15. 安卓脚本判断运作的是不是root,su
  16. 生活大爆炸第二季 那些精妙的台词翻译
  17. 百度地图API位置偏移的校准算法
  18. 【数据分析】电商平台订单报表分析思路及案例
  19. H5+css+js前端特效源代码:发光动画按钮:上传按钮
  20. 「小白学Python」Windows安装Python

热门文章

  1. Filter的生命周期
  2. 使用访问器属性模拟java中的私有变量
  3. 三行代码接入,社交软件打字时底下弹出的表情布局,自定义ViewPager+页面点标+各种功能的android小框架。...
  4. DBA工具——DMV——通过sys.dm_exec_procedure_stats查看存储过程执行信息
  5. 解析特殊locale的日期格式
  6. Fedora12下安装NCTUns6.0
  7. DynamicData for Asp.net Mvc留言本实例 中篇 新建.删除.数据验证
  8. 关于vue内只要html元素的代码
  9. 《趣学Python编程》——2.3 使用变量
  10. libSVM 参数选择