zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    ES6入门详解(二) 解构赋值
    python 入门简述
    webpack4x 简述
    ES6入门详解(一) let const
    关于HTML的简述
    按照in条件排序
    Oracle 优化效率
    input输入框校验
    <a>标签操作
    svn安装
  • 原文地址:https://www.cnblogs.com/lenve/p/5989990.html
Copyright © 2011-2022 走看看