• LRC歌词原理和实现高仿Android网易云音乐


    大家好,我们是爱学啊,今天给大家带来一篇关于LRC歌词原理和在Android上如何实现歌词逐行滚动的效果,本文来自【Android开发项目实战我的云音乐】课程;逐字滚动下一篇文章讲解。

    效果图

    相信大家都懂一张图胜过千言万语。

    效果和现在市面上大部分播放器差不多,当然如果要运用到商业项目中,肯定还需要进行一些优化,例如:滚动效果有弹性,字体大小,字体颜色等。

    什么是LRC歌词

    LRC是英文Lyric(歌词)的缩写,常用作逐行歌词扩展名。他是纯文本文件,格式简单,能实现歌词逐行滚动;当然目前业界大部分播放器都是在他的基础上定制了,但基本原理一样,当学完我们这篇文章后,大家也可以根据自己的需求定制。

    LRC歌词格式

    在实现歌词功能前,肯定需要搞明白LRC歌词格式,例如:我们找了一段LRC歌词:

    [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文件,那么这里面的换行符应该变为 换行符,这一点我们也在课程中讲解到了。

    每一行是一句歌词;每一行歌词又分为两部分,中括号里面是当前这行歌词的开始时间,格式为分:秒:毫秒,有些歌词可能没有毫秒,只有秒;歌词开头由于部分数据称为LRC元数据,他是用来描述这个歌词的,部分字段解释如下:

    ti:title,标题,通常是歌曲名称
    ar:artist,艺人名
    al:album,专辑名
    by:歌词创建人,这里是ttpod,指的是天天动听
    total:整首歌曲时长,单位毫秒
    offset:时间补偿值,单位毫秒,正值表示整体提前,负值相反
    

    前面这些字段根据不同的播放器可能用的位置不一样,我们课程中虽然解析了这些字段,但也没有用到。

    歌词滚动原理

    将每行歌词前面的时间解析后,转为毫秒,这样播放器在播放的时候可以获取到播放时间,然后拿着时间查找当前时间对应哪一行歌词,然后在界面上高亮这一行歌词,或者做更多的处理,例如:字体增大等操作;就实现了歌词逐行高亮;至于滚动不同的平台不一样,滚动思路是:获取到当前时间所对应哪一行,然后我们肯定能算出每一行歌词高度,所以行*每一行高度就是滚动的高度。

    歌词解析

    不同的语言语法不一样,我们这里先说思路,我们的实现是Java语言。

    读取该文件每一行,然后用]拆分,第二部分就是歌词,第一部分继续用:拆分,然后将三部分转为毫秒;最后将这些信息保存到对象上。

    当然为了以后更好的扩展,因为歌词格式很多,可以进行一些架构:

    String[] strings = content.split("
    ");
    
    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。

  • 相关阅读:
    爬取网页图片
    python 猜数字游戏
    位移运算
    生成随机的名字
    不截半个汉字
    一致性hash的实现
    安装前端脚手架
    什么是快速排序?
    HTML5有趣的标签
    stopPropagation / stopImmediatePropagation
  • 原文地址:https://www.cnblogs.com/ixuea/p/9798285.html
Copyright © 2020-2023  润新知