自定義View強勢來襲,用自定義View實現(xiàn)歌詞顯示控件下篇之自定義LyricView的實現(xiàn)

在上篇中,我與大家分享了關于如何進行*.lrc歌詞文件的解析,以及將解析完成后的歌詞展示在鑲嵌在ScrollView中的TextView上,就這樣而言,一個簡單的歌詞顯示功能也就實現(xiàn)了。 但是,如何才能夠讓自己寫的音樂播放器在歌詞顯示模塊能夠顯得高大上,并且能夠像很多當前應用市場上流行的音樂播放器那樣,實現(xiàn)當前播放高亮顯示、歌詞回彈效果、歌詞淡入淡出效果以及滑動歌詞快速播放等功能呢? 請接著往下讀.....

本篇,我想要和大家分享的便是如何通過自定義View實現(xiàn)一款炫酷的LyricView歌詞顯示控件??催^上篇的朋友想必應該都還記得,我在上篇中有提及到在早些前我有用ScrollView嵌套TextView的方式實現(xiàn)過自定義LyricView,但是,由于體驗效果和功能拓展上的不足,我并沒有公開分享。既然通過ScrollView嵌套TextView的方式不能滿足我們的設計需求,那是不是能夠通過自定義View的方式實現(xiàn)LyricView,既有如TextView那樣的LineHeigh(行高)、LineCount(總行數(shù))的概念,也有如ScrollView那樣的ScrollY(Y方向的偏移量)的概念。那是必須的,說干就干。

解析*.lrc歌詞文件,生成歌詞集合列表,獲得行總數(shù)

解析歌詞在本篇中我就不設篇幅進行描述了,如果還不清楚的可以翻看我的上一篇文章《自定義View強勢來襲,用自定義View實現(xiàn)歌詞顯示控件上篇之實現(xiàn)歌詞文件解析》。而在LyricView中,我們需要做的是將逐行解析出來的歌詞信息添加到集合mLyricInfo中,而總行數(shù)mLineCount也就等于List集合的大小mLineCount = mLyricInfo.song_lines.size()。

計算歌詞單行高度,獲得歌詞繪制區(qū)域總高度

寫過自定義View的朋友應該都會知道,在自定義View中如果涉及文字的繪制,為了能夠精準的繪制文字的位置,我們需要獲取需要繪制的文字的矩形區(qū)域,通過畫筆PaintgetTextBounds(String text, int start, int end, Rect bounds)方法則可以幫助我們輕松獲得一個需要繪制文字的一個矩形。當然,繪制文字矩形區(qū)域的大小還與文字的大小相關,我們還可以通過畫筆PaintsetTextSize(float textSize)方法設置繪制文字大小,也就是常說的TextSize。 當然,在LyricView中,行高可不僅僅就只是文字矩形區(qū)域的高度,行高還包括兩行之間的行間距,如下圖所示。既然歌詞總行數(shù)和歌詞單行高度我們都已取得,那么獲取歌詞繪制區(qū)域的總高度也就so easy了。

源碼截圖 - 計算行高度
定義scrollY,并通過當前歌曲播放進度的時間戳計算scrollY

既然LyricView能夠實現(xiàn)滑動功能,那么引入scrollY值記錄滑動偏移量,并控制視圖繪制效果也就順理成章。* 需要明確一點,當偏移量scrollY的值為零的時候,歌詞的首行將顯示在整個LyricView的正中間 *。在上篇中,我們也知道每一句歌詞中都包含著開始時間,而我們也就可以通過當前歌曲播放進度匹配當前播放的行數(shù) mCurrentPlayLine,并通過當前播放所在行,計算偏移量scrollY的值,控制歌詞播放滾動和當前播放位置的高亮顯示。

源碼截圖 - 匹配當前播放行數(shù) mCurrentPlayLine
源碼截圖 - 計算偏移量scrollY
理論基礎已經(jīng)實現(xiàn),初步嘗試繪圖 onDraw
for(int i = 0, size = mLineCount; i < size; i ++) {
    float x = getMeasuredWidth() * 0.5f;
    float y = getMeasuredHeight() * 0.5f + (i + 0.5f) * mLineHeight - 6 - mLineSpace * 0.5f - mScrollY;
    if(y + mLineHeight * 0.5f < 0) {
        continue;
    }
    if(y - mLineHeight * 0.5f > getMeasuredHeight()) {
        break;
    }
    if(i == mCurrentPlayLine - 1) {
        mTextPaint.setColor(mHighLightColor);
    } else {
        if(mIndicatorShow && i == mCurrentShowLine - 1) {
            mTextPaint.setColor(mCurrentShowColor);
        }else {
            mTextPaint.setColor(mDefaultColor);
        }
    }
    if(y > getMeasuredHeight() - mShaderWidth || y < mShaderWidth) {
        if(y < mShaderWidth) {
             mTextPaint.setAlpha(26 + (int) (23000.0f * y / mShaderWidth * 0.01f));
        } else {
             mTextPaint.setAlpha(26 + (int) (23000.0f * (getMeasuredHeight() - y) / mShaderWidth * 0.01f));
        }
    } else {
            mTextPaint.setAlpha(255);
    }
    canvas.drawText(mLyricInfo.song_lines.get(i).content, x, y, mTextPaint);
}

Bingo ! 歌詞確實能夠在屏幕上繪制出來,細心的朋友也許會發(fā)現(xiàn)其中的幾個特別的地方,分別是當前播放位置高亮顯示和歌詞淡入淡出效果實現(xiàn)的代碼:

 //    實現(xiàn)當前播放位置高亮顯示  
 if (i == mCurrentPlayLine - 1)  {
      mTextPaint.setColor(mHighLightColor); 
 }
 //  歌詞淡入淡出效果實現(xiàn)
 if (y > getMeasuredHeight() - mShaderWidth || y < mShaderWidth)  {    
      if(y < mShaderWidth)  {       
            mTextPaint.setAlpha(26 + (int) (23000.0f * y / mShaderWidth * 0.01f));
      } else  {        
            mTextPaint.setAlpha(26 + (int) (23000.0f * (getMeasuredHeight() - y) / mShaderWidth * 0.01f));   
      }
  }  else {    
      mTextPaint.setAlpha(255);
  }

但是,僅僅只是實現(xiàn)顯示功能,并且超出范圍的歌詞內容還不能通過滑動來查看。哈哈~ 別著急啊,騷年,坐下來和我涼茶,聽我細細道來。

重寫onTouchEvent,實現(xiàn)歌詞滑動查看,并實現(xiàn)overScroll回彈效果

僅僅需要實現(xiàn)滑動視圖的功能的話,說實話,非常簡單,只需要記錄ACTION_DOWN時的y值,并比較ACTION_MOVE過程中的y值計算兩者的差值,生成新的偏移量scrollY,再刷新視圖,就可以搞定 ! 要是就這么簡簡單單了事的話,怎么也不符合我個人對完美設計的要求。要是我們無限滑動的話,整個歌詞內容區(qū)域就會滑動出我們的可視區(qū)域,也就是常說的overScroll,如果不加以限制將會是一種非常差的用戶體驗。當然,不同的開發(fā)對解決這個問題有不同的方法,有些播放器的歌詞顯示控件,當滑動事件出現(xiàn)overScroll時,將不再視圖繼續(xù)滑動。當然,也有當滑動事件出現(xiàn)overScroll時,視圖依舊能夠繼續(xù)滑動,但與正?;瑒訒r有所區(qū)別,這個時候的滑動會有一種阻尼效果,也就是實際滑動距離和視圖的滾動距離并不相等,而且隨著overScroll的值越大,阻力越大,滑動越艱難,并在用戶手指離開屏幕后回到overScroll的值為零的位置。當然,我本人更喜歡后者的用戶體驗,為了實現(xiàn)這個功能,那么就必須要在重寫onTouchEvent的方法中"做點手腳"了。

源碼截圖 - ACTION_MOVE
源碼截圖 - 阻尼大小計算

通過我一次一次對代碼的細化,只要這么簡單的兩個方法,就完成了滑動時偏移量scrollY的計算,包括overScroll和非overScroll,是的,只要這么兩個方法。
到了這一步,歌詞的顯示、滑動查看都已經(jīng)完成。但這還沒完,我是不是還說過我的LyricView能夠實現(xiàn)像網(wǎng)易云音樂歌詞顯示控件那樣的指示器效果,哈哈哈 ~ 對于我這個完美主義者而言,這個功能必須實現(xiàn)。

歌詞指示器效果圖
實現(xiàn)歌詞指示器效果,"屌絲"蛻變"高富帥"

其實,指示器效果實現(xiàn)起來也不是很難,其實指示器左側的按鈕完全可以用繪制Bitmap的方式其實現(xiàn),但是,考慮到LyricView的靈活性,同時,我們程序猿不都是能夠用代碼繪制的決不在工程中添加圖片的嘛 !更何況就一個簡單的播放按鈕,隨便畫畫,哈哈 ~ 至于,右側的時間指示,則是通過當前偏移量scrollY的值計算得來的當前控件正中間位置顯示歌詞的開始時間。

源碼截圖 - 繪制指示器左側播放按鈕
源碼截圖 - 繪制指示器分割線和時間

既然設計播放按鈕,當然播放按鈕就要實現(xiàn)點擊事件?。?/p>

源碼截圖 - 播放按鈕點擊位置判斷
源碼截圖 - 播放按鈕點擊事件相應

到這一步,我們的自定義LyricView設計介紹也就告一段落咯 ! 當然,功能遠不止這些,還有設置字體大小、設置行間距、以及結合速度追蹤器實現(xiàn)滑行效果等等。所謂"授人以魚不如授人以漁",我想要和大家分享的是我的一個設計思路,大家可以根據(jù)需求設計不通的功能,因此在這里我也不做過多介紹,對小阿飛的LyricView感興趣的朋友可以去我的gitHub下載研究。

overScroll效果展示
字體顏色設置效果展示
字體大小設置效果展示
行間距設置效果展示
指示器和播放按鈕效果展示

作者申明:如果文中有表述不當或闡述錯誤的地方,還望正在看文章的您可以幫忙指出,有疑惑也可以在評論區(qū)提問或者私信,期待您的意見和建議,歡迎關注交流。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容