• Android自定义View,高仿QQ音乐歌词滚动控件!


    最近在以QQ音乐为样板做一个手机音乐播放器,源码下篇博文放出。今天我想聊的是这个QQ音乐播放器中歌词显示控件的问题,和小伙伴们一起来探讨怎么实现这个歌词滚动的效果。OK,废话不多说,先来看看效果图:

    好,接下来我们就来看看怎么实现这样一个效果。本文主要包括如下几方面内容:


    1.歌词文件格式分析及解析

    2.歌词显示控件绘制

    3.关于卡拉OK模式

    4.使用方式


    好,那就开始吧。

    1.歌词文件格式分析及解析

    首先,小伙伴们需要明白歌词文件的格式都是固定的,是什么样子的呢,我们来看看下图:


    我们一个歌词文件打开都是这种格式,前面  []  中的是该行歌词显示的时间,后面一行是歌词,只有时间没有歌词的行就是伴奏时间。了解了这固定的歌词格式,剩下的就简单了,解析这段文本就行了。我创建一个LrcBean用来存放每一行的数据,这个LrcBean中包括三个属性,分别是一句歌词,该歌词开始唱的时间,该歌词唱完的时间,咦,有的小伙伴可能有疑问,唱完是什麽时候呢?就是下一句的开始时间呗。OK,那我们来看看实体类:

    public class LrcBean {
        private String lrc;
        private long start;
        private long end;
    
        public LrcBean() {
        }
    
        public LrcBean(String text, long start, long end) {
            this.lrc = text;
            this.start = start;
            this.end = end;
        }
    
        public String getLrc() {
            return lrc;
        }
    
        public void setLrc(String lrc) {
            this.lrc = lrc;
        }
    
        public long getStart() {
            return start;
        }
    
        public void setStart(long start) {
            this.start = start;
        }
    
        public long getEnd() {
            return end;
        }
    
        public void setEnd(long end) {
            this.end = end;
        }
    }

    OK,实体类有了,接下来我们来看看实体类怎么解析歌词文本,解析过程分为两步:

    1.考虑到歌词文本中可能有转义字符,我们需要先把转义字符还原

    2.然后按照换行符将文本拆分,再通过字符串截取将每一行的数据提取出来。代码如下(由于转义字符显示不出来,所以我这里贴一张代码图,源码文末可以下载):


    OK,通过以上方式我们就把歌词文件解析成了一个List集合,该集合中的每一项就是一句歌词,另外,在伴奏的时间段,我显示一句music。

    2.歌词显示控件绘制

    歌词解析完了,接下来我们就可以绘制歌词View了。绘制的整体思路是这样:

    1.首先获取当前播放的时间

    2.根据当前播放时间,遍历歌词的List集合,判断出当前正在播放的是List集合中的哪一句,找到该句的下标

    3.遍历歌词List集合,绘制所有歌词,绘制的过程中,如果该句是正在播放的歌词,则使用高亮的画笔来绘制,否则使用普通画笔绘制。

    4.判断当前是否已经换行了,如果是,则调用setScrollY方法让屏幕滚动一行。关于setScrollY方法如果小伙伴们还不太了解可以参考这篇文章View绘制详解(五),draw方法细节详解之View的滚动/滑动问题

    5.每隔100毫秒重绘View。

    OK,整个流程就是这样,接下来我们来看看代码实现:

        @Override
        protected void onDraw(Canvas canvas) {
            if (width == 0 || height == 0) {
                width = getMeasuredWidth();
                height = getMeasuredHeight();
            }
            if (list == null || list.size() == 0) {
                canvas.drawText("暂无歌词", width / 2, height / 2, gPaint);
                return;
            }
    
            getCurrentPosition();
    
            int currentMillis = player.getCurrentPosition();
            drawLrc2(canvas);
            long start = list.get(currentPosition).getStart();
            float v = (currentMillis - start) > 500 ? currentPosition * 80 : lastPosition * 80 + (currentPosition - lastPosition) * 80 * ((currentMillis - start) / 500f);
            setScrollY((int) v);
            if (getScrollY() == currentPosition * 80) {
                lastPosition = currentPosition;
            }
            postInvalidateDelayed(100);
        }
    
        private void drawLrc2(Canvas canvas) {
            for (int i = 0; i < list.size(); i++) {
                if (i == currentPosition) {
                    canvas.drawText(list.get(i).getText(), width / 2, height / 2 + 80 * i, hPaint);
                } else {
                    canvas.drawText(list.get(i).getText(), width / 2, height / 2 + 80 * i, gPaint);
                }
            }
        }
    
        private void getCurrentPosition() {
            try {
                int currentMillis = player.getCurrentPosition();
                if (currentMillis < list.get(0).getStart()) {
                    currentPosition = 0;
                    return;
                }
                if (currentMillis > list.get(list.size() - 1).getStart()) {
                    currentPosition = list.size() - 1;
                    return;
                }
                for (int i = 0; i < list.size(); i++) {
                    if (currentMillis >= list.get(i).getStart() && currentMillis < list.get(i).getEnd()) {
                        currentPosition = i;
                        return;
                    }
                }
            } catch (Exception e) {
    //            e.printStackTrace();
                postInvalidateDelayed(100);
            }
        }

    OK,这里给出一个核心代码,完整代码小伙伴们在文末可以自行下载。

    3.关于卡拉OK模式

    OK,经过第二个步骤之后,我们这个歌词控件已经可以根据当前播放的时间来显示高亮的歌词,同时进行歌词的滚动。有的小伙伴可能还想实现一种类似于KTV里边的那种播放效果,我们也来看一看怎么实现。还是先来说说思路吧。

    1.把所有的歌词都用普通的画笔画出来

    2.为当前正在播放的歌词生成一个Bitmap

    3.根据当前播放时间,计算出该句歌词播放的比例,然后根据这个比例绘制第二步生成的Bitmap。

    OK,根据上述的思路,我贴出核心代码如下:

    for (int i = 0; i < list.size(); i++) {
                    canvas.drawText(list.get(i).getLrc(), width / 2, height / 2 + 80 * i, gPaint);
                }
                String highLineLrc = list.get(currentPosition).getLrc();
                int highLineWidth = (int) gPaint.measureText(highLineLrc);
                int leftOffset = (width - highLineWidth) / 2;
                LrcBean lrcBean = list.get(currentPosition);
                long start = lrcBean.getStart();
                long end = lrcBean.getEnd();
                int i = (int) ((currentMillis - start) * 1.0f / (end - start) * highLineWidth);
                if (i > 0) {
                    Bitmap textBitmap = Bitmap.createBitmap(i, 80, Bitmap.Config.ARGB_8888);
                    Canvas textCanvas = new Canvas(textBitmap);
                    textCanvas.drawText(highLineLrc, highLineWidth / 2, 80, hPaint);
                    canvas.drawBitmap(textBitmap, leftOffset, height / 2 + 80 * (currentPosition - 1), null);
                }

    4.使用方式

    OK,控件做好了,最后我们再来看看使用方式。很简单,引入这个View 的类库(文末会给出下载地址),然后传入歌词的文本,开启绘制即可,如下:

    lrcView.setLrc(lrcStr);
            lrcView.setPlayer(PlayUtil.player);
            lrcView.init();

    简单三行代码,就可以开始使用了。


    项目地址https://github.com/lenve/LrcView



  • 相关阅读:
    MSDN Magazine搞错了
    Visual Studio 2005中设置调试符号(Debug Symbols)
    BCB 6的问题
    吴裕雄天生自然Spring Boot使用Spring Data JPA实现人与身份证的一对一关系映射
    吴裕雄天生自然Spring BootSpring Data JPA
    吴裕雄天生自然Spring BootSpring Boot对JSP的支持
    吴裕雄天生自然Spring BootSpring Boot的异常统一处理
    吴裕雄天生自然Spring Boot使用Spring Data JPA实现Author与Article的一对多关系映射
    吴裕雄天生自然Spring Boot解决 Error creating bean with name 'entityManagerFactory' defined in class path resource
    吴裕雄天生自然Spring Boot@ExceptionHandler注解和@ControllerAdvice注解
  • 原文地址:https://www.cnblogs.com/qitian1/p/6461658.html
Copyright © 2020-2023  润新知