天天看點

Android自定義View,高仿QQ音樂歌詞滾動控件!1.歌詞檔案格式分析及解析2.歌詞顯示控件繪制3.關于卡拉OK模式4.使用方式

最近在以QQ音樂為樣闆做一個手機音樂播放器,源碼下篇博文放出。今天我想聊的是這個QQ音樂播放器中歌詞顯示控件的問題,和小夥伴們一起來探讨怎麼實作這個歌詞滾動的效果。OK,廢話不多說,先來看看效果圖:

Android自定義View,高仿QQ音樂歌詞滾動控件!1.歌詞檔案格式分析及解析2.歌詞顯示控件繪制3.關于卡拉OK模式4.使用方式

好,接下來我們就來看看怎麼實作這樣一個效果。本文主要包括如下幾方面内容:

1.歌詞檔案格式分析及解析

2.歌詞顯示控件繪制

3.關于卡拉OK模式

4.使用方式

好,那就開始吧。

1.歌詞檔案格式分析及解析

首先,小夥伴們需要明白歌詞檔案的格式都是固定的,是什麼樣子的呢,我們來看看下圖:

Android自定義View,高仿QQ音樂歌詞滾動控件!1.歌詞檔案格式分析及解析2.歌詞顯示控件繪制3.關于卡拉OK模式4.使用方式

我們一個歌詞檔案打開都是這種格式,前面  []  中的是該行歌詞顯示的時間,後面一行是歌詞,隻有時間沒有歌詞的行就是伴奏時間。了解了這固定的歌詞格式,剩下的就簡單了,解析這段文本就行了。我建立一個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.然後按照換行符将文本拆分,再通過字元串截取将每一行的資料提取出來。代碼如下(由于轉義字元顯示不出來,是以我這裡貼一張代碼圖,源碼文末可以下載下傳):

Android自定義View,高仿QQ音樂歌詞滾動控件!1.歌詞檔案格式分析及解析2.歌詞顯示控件繪制3.關于卡拉OK模式4.使用方式

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