Android自定义歌词同步控件

悠悠我心 贡献于2012-04-27

作者 lanhuageng  创建于2012-03-17 06:11:00   修改者samsung  修改于2012-03-17 06:51:00字数8300

文档摘要:Android音乐播放器中,播放音乐的类(即MediaPlayer)在播放音乐的时候,通过MediaPlayer的getCurrentPosition方法可以得到当前音乐播放的流进度,通过getDuration可以得到当前音乐总的流大小。因此,我们可以通过这两个方法来判断同步的音乐歌词播放进度。下面,该文档将为大家实现歌词同步,其他音乐播放的东西一概不涉及。
关键词:

 Android自定义歌词同步控件 Android音乐播放器中,播放音乐的类(即MediaPlayer)在播放音乐的时候,通过MediaPlayer的getCurrentPosition方法可以得到当前音乐播放的流进度,通过getDuration可以得到当前音乐总的流大小。因此,我们可以通过这两个方法来判断同步的音乐歌词播放进度。下面,该文档将为大家实现歌词同步,其他音乐播放的东西一概不涉及。 歌词同步相关两个类: SongLyric(歌词对象) 歌词对象实例化即可使用,但必须保证该歌词对象验证通过,即歌词文件存在,切正确实例化。 LyricView(Android自定义歌词控件) 该歌词控件不可以写在xml配置文件中,必须使用一个layout布局控件存放,使用的时候先从Activity中得到layout,然后再将该歌词控件通过layout的getContext的参数实例化,最后添加到layout中,并且将对应的歌词对象SongLyric设置到歌词控件中。最后,歌词控件要做到与音乐同步的效果,还得时时刷新歌词控件,这样就有了歌词同步以及滚动的效果。具体实现方法下面讲到,先看看这两个类源码: SongLyric类,该对象和网上大部分歌词对象一样,这里为了和歌词控件LyricView配套,多了一部分方法。 package cn.zuxia.android.widget; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 歌词对象 * * @author samsung * */ public final class SongLyric { // 歌名 private String title = ""; // 歌手名 private String artist = ""; // 专辑名 private String album = ""; // 偏移时间 private long offset = 0; // 最大时间 private long maxTime = 0; // 歌词内容 private Map lrcs = new HashMap(); // 验证是否通过 private boolean valid = false; public SongLyric(String url) { File file = new File(url); if (file.exists()) { try { // 构建读取器 BufferedReader br = new BufferedReader(new InputStreamReader( new FileInputStream(file), "gbk")); String line = null; while ((line = br.readLine()) != null) { dealLine(line); } valid = true; } catch (Exception e) { System.out.println("Exception"); } } } public String getTitle() { return this.title; } public String getArtist() { return this.artist; } public String getAlbum() { return this.album; } public boolean isValid() { return this.valid; } public long getMaxTime() { return this.maxTime; } public void setMaxTime(long time) { this.maxTime = time; } /** * 获取该时间应当显示的歌词 * * @param ls * @return */ public String get(long ls) { long time = ls + offset; Long curr = -1l; for (Long l : lrcs.keySet()) { curr = l > time ? curr : l < curr ? curr : l; } return lrcs.get(curr); } /** * 获取该时间所要显示的歌词初始时间的索引 * * @param ls * @return */ public int getIndex(long ls) { Long[] ts = getAllTimes(); for (int i = 0; i < ts.length - 1; i++) { if (ls + offset >= ts[i] && ls + offset < ts[i + 1]) { return i; } } return ts.length - 1; } /** * 获取该时间与歌词初始时间差值 * * @param ls * @return */ public int getOffset(long ls) { Long[] ts = getAllTimes(); int index = getIndex(ls); if (index < ts.length && index >= 0) { return (int) (ls + offset - ts[index]); } return 0; } /** * 获取该时间段播放的歌词共播放时间 * * @param ls * @return */ public int getNextTime(long ls) { Long[] ts = getAllTimes(); int index = getIndex(ls); if (index < ts.length - 1) { return (int) (ts[index + 1] - ts[index]); } return 0; } /** * 处理歌词行 * * @param line */ private void dealLine(String line) { if (line != null && !line.equals("")) { if (line.startsWith("[ti:")) {// 标题 title = line.substring(4, line.length() - 1); } else if (line.startsWith("[ar:")) {// 歌手 artist = line.substring(4, line.length() - 1); } else if (line.startsWith("[al:")) {// 专辑 album = line.substring(4, line.length() - 1); } else if (line.startsWith("[offset:")) {// 专辑 offset = Long.parseLong(line.substring(8, line.length() - 1)); } else { // 该行歌词内容 Pattern ptn = Pattern.compile("\\[(\\d{2}:\\d{2}\\.\\d{2})\\]"); Matcher mth = ptn.matcher(line); while (mth.find()) { // 得到时间点 long time = strToLong(mth.group(1)); // 得到时间点后的内容 String[] str = ptn.split(line); String lrc = str.length > 0 ? str[str.length - 1] : ""; lrcs.put(time, lrc); maxTime = maxTime > time ? maxTime : time; } } } } /** * 将00:00.00格式的歌词时间转换为long * * @param timeStr * @return */ public static long strToLong(String timeStr) { String[] s = timeStr.split(":"); int min = Integer.parseInt(s[0]); String[] ss = s[1].split("\\."); int sec = Integer.parseInt(ss[0]); int mill = Integer.parseInt(ss[1]); return min * 60 * 1000 + sec * 1000 + mill * 10; } /** * 处理毫秒数,以00:00的方式返回 * * @param ts * @return */ public static String longToString(long ts) { int time = (int) ts / 1000; int ms = time % 60; int ss = time / 60; ss = ss > 99 ? 99 : ss; StringBuffer str = new StringBuffer(); str.append(ss < 10 ? "0" + ss + ":" : ss + ":"); str.append(ms < 10 ? "0" + ms : ms + ""); return str.toString(); } /** * 获取顺序时间数组对象 * * @return */ public Long[] getAllTimes() { Long[] ts = new Long[lrcs.size()]; int index = 0; for (Long l : lrcs.keySet()) { ts[index++] = l; } for (int i = 0; i < ts.length - 1; i++) { for (int j = i; j < ts.length; j++) { if (ts[i] > ts[j]) { Long tmp = ts[i]; ts[i] = ts[j]; ts[j] = tmp; } } } return ts; } } LyricView控件源码(该类为Android自定义控件,可以看到构造方法只有一个,需得传一个Context,该Context即为存放该歌词控件的layout) package cn.zuxia.android.widget; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.view.View; /** * 歌词视图控件 * * @author samsung * */ public final class LyricView extends View { // 歌词对象 private SongLyric lrc = null; // 当前播放时间 private long time = 0l; // 字体画笔 private Paint fontPaint = null; // 当前歌词字体画笔 private Paint lrcPaint = null; // 字体颜色 private int fontColor = Color.WHITE; // 当前歌词字体颜色 private int lrcColor = Color.RED; // 字体大小 private int fontSize = 14; public LyricView(Context context) { super(context); } /** * 设置歌词对象 * * @param lrc */ public void setLyric(SongLyric lrc) { this.lrc = lrc; } /** * 设置当前时间 * * @param ms */ public void setTime(long ms) { this.time = ms; } /** * 设置歌词字体颜色 * * @param color */ public void setFontColor(int color) { this.fontColor = color; } /** * 设置当前歌词字体颜色 * * @param color */ public void setLyricColor(int color) { this.lrcColor = color; } /** * 设置字体大小 * * @param size */ public void setFontSize(int size) { this.fontSize = size; } /** * 重绘视图 */ @Override protected void onDraw(Canvas c) { super.onDraw(c); if (lrc != null) { try { if (fontPaint == null) { fontPaint = new Paint(); } if (lrcPaint == null) { lrcPaint = new Paint(); } fontPaint.setColor(fontColor); fontPaint.setTextSize(fontSize); lrcPaint.setColor(lrcColor); lrcPaint.setTextSize(fontSize); // 获取当前要播放歌词的索引 int cIndex = lrc.getIndex(time); // 计算绘制歌词y坐标 int h = getHeight() / 2 - cIndex * fontSize * 3 / 2 - (int) ((fontSize * 3 / 2) * (lrc.getOffset(time) / (float) lrc .getNextTime(time))); Long[] ts = lrc.getAllTimes(); // 循环绘制每一行歌词,当前播放歌词特殊绘制 for (Long l : ts) { c.drawText(lrc.get(l), 0, h, lrc.getIndex(l) == cIndex ? lrcPaint : fontPaint); h += fontSize * 3 / 2; } } catch (Exception e) { } } } } 为了大家更了解该歌词控件,下面有一个小小的例子,该例子只是模拟了MediaPlayer播放显示的歌词同步效果,只需要用一个handler不停更新LyricView的setTime方法既可以由歌词滚动效果: package cn.zuxia.android; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import cn.zuxia.android.widget.LyricView; import cn.zuxia.android.widget.SongLyric; public class MainActivity extends Activity { // 歌词对象 private SongLyric lrc; // 音乐进度滚动条 private SeekBar skOpera; // 歌词控件 private LyricView vwLrc; // 相关变量(isRelase为是否用户拖动滚动条,end为是否退出程序) private boolean isRelase = false, end = false; // 模拟的音乐进度毫秒值 private long time = 0; /** * 注意,该Activity为模拟的效果,真正使用的时候将MediaPlayer的getCurrentPosition * 传给歌词控件的setTime中,刷新的时候只用该方法即可。 */ // 刷新歌词控件的handler private Handler hand = new Handler() { public void handleMessage(Message msg) { skOpera.setProgress((int) ((time / (float) lrc.getMaxTime()) * 100)); // 设置歌词当前播放时间 vwLrc.setTime(time); vwLrc.postInvalidate(); setTitle(SongLyric.longToString((int) time) + "/" + SongLyric.longToString((int) lrc.getMaxTime())); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // 实例化歌词对象 lrc = new SongLyric("sdcard/Music/fc.lrc"); // 设置歌词播放总时间,假如该歌词要播放18W毫秒,即3分钟 lrc.setMaxTime(180000); skOpera = (SeekBar) findViewById(R.id.skOpera); // 用LinearLayout存放歌词控件 LinearLayout layout = (LinearLayout) findViewById(R.id.lyoutView); vwLrc = new LyricView(layout.getContext()); vwLrc.setBackgroundColor(Color.GREEN); layout.addView(vwLrc); // 为歌词控件初始化,设置歌词 vwLrc.setLyric(lrc); // 模拟定位播放效果 skOpera.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { public void onStopTrackingTouch(SeekBar seekBar) { isRelase = false; } public void onStartTrackingTouch(SeekBar seekBar) { isRelase = true; } public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { time = lrc.getMaxTime() * skOpera.getProgress() / 100; } } }); // 该线程时时更新歌词控件 new Thread() { public void run() { while (!end) { hand.sendMessage(hand.obtainMessage()); try { sleep(100); } catch (Exception e) { } } }; }.start(); // 该线程模拟音乐播放,控制歌词进度 new Thread() { public void run() { long max = lrc.getMaxTime(); while (!end) { if (time < max) time += 10; try { sleep(10); } catch (Exception e) { } } }; }.start(); } @Override protected void onDestroy() { super.onDestroy(); end = true; } } Xml文件如下: 很简单的例子,唯一不足的就是没有真正用到MediaPlayer这个类,不过后来我在做音乐播放器的时候还是用到了,看看下面的效果(该效果非上面例子的效果,而是我做的播放器主界面效果….虽然不是很好看):

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 4 人已下载

下载文档