你们好,咱们是爱学啊,今天给你们带来一篇关于LRC歌词原理和在Android上如何实现歌词逐行滚动的效果,本文来自【Android开发项目实战个人云音乐】课程;逐字滚动下一篇文章讲解。java
相信你们都懂一张图赛过千言万语。canvas
效果和如今市面上大部分播放器差很少,固然若是要运用到商业项目中,确定还须要进行一些优化,例如:滚动效果有弹性,字体大小,字体颜色等。微信
LRC是英文Lyric(歌词)的缩写,经常使用做逐行歌词扩展名。他是纯文本文件,格式简单,能实现歌词逐行滚动;固然目前业界大部分播放器都是在他的基础上定制了,但基本原理同样,当学完咱们这篇文章后,你们也能够根据本身的需求定制。架构
在实现歌词功能前,确定须要搞明白LRC歌词格式,例如:咱们找了一段LRC歌词:ide
[ti:爱的代价] [ar:李宗盛] [al:滚石香港黄金十年 李宗盛精选] [ly:李宗盛] [mu:李宗盛] [ma:] [pu:] [by:ttpod] [total:272073] [offset:0] [00:00.300]爱的代价 - 李宗盛 [00:01.979]做词:李宗盛 [00:03.312]做曲:李宗盛 [00:06.429] [00:16.282]还记得年少时的梦吗 [00:20.575]像朵永远不调零的花 [00:24.115]陪我通过那风吹雨打 [00:27.921]看世事无常 [00:29.653]看沧桑变化 [00:32.576]那些为爱所付出的代价 [00:36.279]是永远都难忘的啊 [00:40.485]全部真心的痴心的话
能够看到内容是用换行符分割的,若是这些数据是经过接口返回,而不是直接返回一个LRC文件,那么这里面的换行符应该变为n换行符,这一点咱们也在课程中讲解到了。学习
每一行是一句歌词;每一行歌词又分为两部分,中括号里面是当前这行歌词的开始时间,格式为分:秒:毫秒,有些歌词可能没有毫秒,只有秒;歌词开头因为部分数据称为LRC元数据,他是用来描述这个歌词的,部分字段解释以下:字体
ti:title,标题,一般是歌曲名称 ar:artist,艺人名 al:album,专辑名 by:歌词建立人,这里是ttpod,指的是每天动听 total:整首歌曲时长,单位毫秒 offset:时间补偿值,单位毫秒,正值表示总体提早,负值相反
前面这些字段根据不一样的播放器可能用的位置不同,咱们课程中虽然解析了这些字段,但也没有用到。优化
将每行歌词前面的时间解析后,转为毫秒,这样播放器在播放的时候能够获取到播放时间,而后拿着时间查找当前时间对应哪一行歌词,而后在界面上高亮这一行歌词,或者作更多的处理,例如:字体增大等操做;就实现了歌词逐行高亮;至于滚动不一样的平台不同,滚动思路是:获取到当前时间所对应哪一行,而后咱们确定能算出每一行歌词高度,因此行*每一行高度就是滚动的高度。动画
不一样的语言语法不同,咱们这里先说思路,咱们的实现是Java语言。spa
读取该文件每一行,而后用]拆分,第二部分就是歌词,第一部分继续用:拆分,而后将三部分转为毫秒;最后将这些信息保存到对象上。
固然为了之后更好的扩展,由于歌词格式不少,能够进行一些架构:
String[] strings = content.split("\n"); lyric = new Lyric(); TreeMap<Integer, Line> lyrics = new TreeMap<>(); Map<String, Object> tags = new HashMap<>(); String lineInfo=null; int lineNumber = 0; for (int i = 0; i < strings.length; i++) { try { lineInfo=strings[i]; Line line = parserLine(tags, lineInfo); if (line != null) { lyrics.put(lineNumber, line); lineNumber++; } } catch (Exception var9) { var9.printStackTrace(); } } lyric.setLyrics(lyrics); lyric.setTags(tags); /** * 解析每一行歌词 */ private Line parserLine(Map<String, Object> tags, String lineInfo) { if (lineInfo.startsWith("[0")) { //歌词开始了 Line line = new Line(); int leftIndex = 1; int rightIndex = lineInfo.length(); String[] lineComments = lineInfo.substring(leftIndex, rightIndex).split("]", -1); //开始时间 String startTimeStr = lineComments[0]; int startTime = TimeUtil.parseInteger(startTimeStr); line.setStartTime(startTime); //歌词 String lineLyricsStr = lineComments[1]; line.setLineLyrics(lineLyricsStr); return line; } return null; }
不一样的平台也不同,咱们这里是Android,因此绘制用Canvas。咱们这里的思路是:歌词View的高度是固定的,因为咱们但愿当前行歌词始终显示到歌词View中间,因此先算出View的中心高度,而后在该位置绘制当前行歌词,这一步根据不一样的歌词处理的逻辑也不同,但歌词可分为两类,一类是逐行,一类是逐字,对于逐行来讲就直接绘制就好了,只是颜色,大小不同而已;逐字下一节讲解;而后从当前行歌词位置像前绘制歌词,直到超出View顶部为止,在从当前行歌词向下歌词绘制,直到超出View底部为止;当前你可使用LinearLayout添加全部歌词当前容器内,而后滚动。
private void drawLyricText(Canvas canvas) { //在当前位置绘制正在演唱的歌词 Line line = lyricsLines.get(lineNumber); //当前歌词的宽高 float textWidth = getTextWidth(backgroundTextPaint, line.getLineLyrics()); float textHeight = getTextHeight(backgroundTextPaint); float centerY = (getMeasuredHeight() - textHeight) / 2 + lineNumber * getLineHeight(backgroundTextPaint) - offsetY; float x = (getMeasuredWidth() - textWidth) / 2; float y = centerY; //当前歌词高亮 if (lyric.isAccurate()) { //TODO 精确到字,歌词,下一节讲解 } else { //精确到行 canvas.drawText(line.getLineLyrics(), x, y, foregroundTextPaint); } //绘制前面的歌词 for (int i = lineNumber - 1; i > 0; i--) { //从当前行的上一行开始绘制 line = lyricsLines.get(i); //当前歌词的宽高 textWidth = getTextWidth(backgroundTextPaint, line.getLineLyrics()); textHeight = getTextHeight(backgroundTextPaint); x = (getMeasuredWidth() - textWidth) / 2; y = centerY - (lineNumber - i) * getLineHeight(backgroundTextPaint); if (y < getLineHeight(backgroundTextPaint)) { //超出了View顶部,再也不绘制 break; } canvas.drawText(line.getLineLyrics(), x, y, backgroundTextPaint); } //绘制后面的歌词 for (int i = lineNumber + 1; i < lyricsLines.size(); i++) { //从当前行的下一行开始绘制 line = lyricsLines.get(i); //当前歌词的宽高 textWidth = getTextWidth(backgroundTextPaint, line.getLineLyrics()); textHeight = getTextHeight(backgroundTextPaint); x = (getMeasuredWidth() - textWidth) / 2; y = centerY + (i - lineNumber) * getLineHeight(backgroundTextPaint); if (y + getLineHeight(backgroundTextPaint) > getHeight()) { //超出了View底部,再也不绘制 break; } canvas.drawText(line.getLineLyrics(), x, y, backgroundTextPaint); } }
Android中不一样的实现方法滚动方式也不同,若是是直接绘制,那么滚动其实就是绘制不一样行歌词,给人的感受就是滚动了;若是是将全部歌词添加到容器中,那么就可使用容器默认的滚动;对于咱们这里的实现滚动其实就是更改lineNumber值,例如;当前lineNumber为5,表示当前播放的是第5行歌词,经过用户滚动的距离就能计算出当前滚动距离是哪一行,由于咱们知道每一行高度因此能够计算出当前位置,滚动到的位置,而后使用属性动画滚动:
if (valueAnimator != null && valueAnimator.isRunning()) { valueAnimator.cancel(); } valueAnimator = ValueAnimator.ofFloat(offsetY, distanceY); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { offsetY = (float) valueAnimator.getAnimatedValue(); invalidate(); } }); valueAnimator.setDuration(200); valueAnimator.setInterpolator(new DecelerateInterpolator()); valueAnimator.start();
到这里LRC歌词View核心功能基本就实现完成了,若是要深刻学习能够查看咱们的【Android开发项目实战个人云音乐】课程,或者在线电子书【电子书】;同时你们也能够关注咱们的微信公众号【ixuea666】和Android开发交流群:702321063。