您好,登錄后才能下訂單哦!
本篇內容介紹了“Android如何自定義View歌詞控件”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
前言
一、 歌詞解析
1.歌詞實體類LrcBean
2. 解析歌詞工具類LrcUtil
二、歌詞繪制
1.設置自定View屬性,在代碼中設置默認值
2. 初始化兩支畫筆
3. 重復執行onDraw方法
1.獲得控件的測量后的寬高
2. 得到當前歌詞的位置
4. 歌詞同步滑動
5.不斷重繪
三 、使用
最近重構了之前的音樂播放器,添加了許多功能,比如歌詞,下載功能等。這篇文章就讓我們聊聊歌詞控件的實現,先上效果圖,如果感覺海星,就繼續瞧下去!
看到這里,估計你對這個控件還有點感興趣的吧,那接下來就讓我們來瞧瞧實現這個歌詞控件需要做些什么!
首先,我們得知道正常的歌詞格式是怎樣的,大概是長這個樣子:
1[ti:喜歡你]
2[ar:.]
3[al:]
4[by:]
5[offset:0]
6[00:00.10]喜歡你 - G.E.M. 鄧紫棋 (Gem Tang)
7[00:00.20]詞:黃家駒
8[00:00.30]曲:黃家駒
9[00:00.40]編曲:Lupo Groinig
10[00:00.50]
11[00:12.65]細雨帶風濕透黃昏的街道
12[00:18.61]抹去雨水雙眼無故地仰望
13[00:24.04]望向孤單的晚燈
14[00:26.91]
15[00:27.44]是那傷感的記憶
16[00:30.52]
17[00:34.12]再次泛起心里無數的思念
18[00:39.28]
19[00:40.10]以往片刻歡笑仍掛在臉上
20[00:45.49]愿你此刻可會知
21[00:48.23]
22[00:48.95]是我衷心的說聲
23[00:53.06]
24[00:54.35]喜歡你 那雙眼動人
25[00:59.35]
26[01:00.10]笑聲更迷人
27[01:02.37]
28[01:03.15]愿再可 輕撫你
29[01:08.56]
30[01:09.35]那可愛面容
31[01:12.40]挽手說夢話
32[01:14.78]
33[01:15.48]像昨天 你共我
34[01:20.84]
35[01:26.32]滿帶理想的我曾經多沖動
36[01:32.45]屢怨與她相愛難有自由
37[01:37.82]愿你此刻可會知
38[01:40.40]
39[01:41.25]是我衷心的說聲
40[01:44.81]
41[01:46.39]喜歡你 那雙眼動人
42[01:51.72]
43[01:52.42]笑聲更迷人
44[01:54.75]
45[01:55.48]愿再可 輕撫你
46[02:00.93]
47[02:01.68]那可愛面容
48[02:03.99]
49[02:04.73]挽手說夢話
50[02:07.13]
51[02:07.82]像昨天 你共我
52[02:14.53]
53[02:25.54]每晚夜里自我獨行
54[02:29.30]隨處蕩 多冰冷
55[02:35.40]
56[02:37.83]以往為了自我掙扎
57[02:41.62]從不知 她的痛苦
58[02:52.02]
59[02:54.11]喜歡你 那雙眼動人
60[03:00.13]笑聲更迷人
61[03:02.38]
62[03:03.14]愿再可 輕撫你
63[03:08.77]
64[03:09.33]那可愛面容
65[03:11.71]
66[03:12.41]挽手說夢話
67[03:14.61]
68[03:15.45]像昨天 你共我
從上面可以看出這種格式前面是開始時間,從左往右一一對應分,秒,毫秒,后面就是歌詞。所以我們要創建一個實體類來保存每一句的歌詞信息。
1public class LrcBean { 2 private String lrc;//歌詞 3 private long start;//開始時間 4 private long end;//結束時間 5 6 public String getLrc() { 7 return lrc; 8 } 9 10 public void setLrc(String lrc) { 11 this.lrc = lrc; 12 } 13 14 public long getStart() { 15 return start; 16 } 17 18 public void setStart(long start) { 19 this.start = start; 20 } 21 22 public long getEnd() { 23 return end; 24 } 25 26 public void setEnd(long end) { 27 this.end = end; 28 } 29}
每句歌詞,我們需要開始時間,結束時間和歌詞這些信息,那么你就會有疑問了?上面提到的歌詞格式好像只有歌詞開始時間,那我們怎么知道結束時間呢?其實很簡單,這一句歌詞的開始時間就是上一句歌詞的結束時間。有了歌詞實體類,我們就得開始對歌詞進行解析了!
1public class LrcUtil { 2 3 /** 4 * 解析歌詞,將字符串歌詞封裝成LrcBean的集合 5 * @param lrcStr 字符串的歌詞,歌詞有固定的格式,一般為 6 * [ti:喜歡你] 7 * [ar:.] 8 * [al:] 9 * [by:] 10 * [offset:0] 11 * [00:00.10]喜歡你 - G.E.M. 鄧紫棋 (Gem Tang) 12 * [00:00.20]詞:黃家駒 13 * [00:00.30]曲:黃家駒 14 * [00:00.40]編曲:Lupo Groinig 15 * @return 歌詞集合 16 */ 17 public static List<LrcBean> parseStr2List(String lrcStr){ 18 List<LrcBean> res = new ArrayList<>(); 19 //根據轉行字符對字符串進行分割 20 String[] subLrc = lrcStr.split("\n"); 21 //跳過前四行,從第五行開始,因為前四行的歌詞我們并不需要 22 for (int i = 5; i < subLrc.length; i++) { 23 String lineLrc = subLrc[i]; 24 //[00:00.10]喜歡你 - G.E.M. 鄧紫棋 (Gem Tang) 25 String min = lineLrc.substring(lineLrc.indexOf("[")+1,lineLrc.indexOf("[")+3); 26 String sec = lineLrc.substring(lineLrc.indexOf(":")+1,lineLrc.indexOf(":")+3); 27 String mills = lineLrc.substring(lineLrc.indexOf(".")+1,lineLrc.indexOf(".")+3); 28 //進制轉化,轉化成毫秒形式的時間 29 long startTime = getTime(min,sec,mills); 30 //歌詞 31 String lrcText = lineLrc.substring(lineLrc.indexOf("]")+1); 32 //有可能是某個時間段是沒有歌詞,則跳過下面 33 if(lrcText.equals("")) continue; 34 //在第一句歌詞中有可能是很長的,我們只截取一部分,即歌曲加演唱者 35 //比如 光年之外 (《太空旅客(Passengers)》電影中國區主題曲) - G.E.M. 鄧紫棋 (Gem Tang) 36 if (i == 5) { 37 int lineIndex = lrcText.indexOf("-"); 38 int first = lrcText.indexOf("("); 39 if(first<lineIndex&&first!=-1){ 40 lrcText = lrcText.substring(0,first)+lrcText.substring(lineIndex); 41 } 42 LrcBean lrcBean = new LrcBean(); 43 lrcBean.setStart(startTime); 44 lrcBean.setLrc(lrcText); 45 res.add(lrcBean); 46 continue; 47 } 48 //添加到歌詞集合中 49 LrcBean lrcBean = new LrcBean(); 50 lrcBean.setStart(startTime); 51 lrcBean.setLrc(lrcText); 52 res.add(lrcBean); 53 //如果是最后一句歌詞,其結束時間是不知道的,我們將人為的設置為開始時間加上100s 54 if(i == subLrc.length-1){ 55 res.get(res.size()-1).setEnd(startTime+100000); 56 }else if(res.size()>1){ 57 //當集合數目大于1時,這句的歌詞的開始時間就是上一句歌詞的結束時間 58 res.get(res.size()-2).setEnd(startTime); 59 } 60 61 } 62 return res; 63 } 64 65 /** 66 * 根據時分秒獲得總時間 67 * @param min 分鐘 68 * @param sec 秒 69 * @param mills 毫秒 70 * @return 總時間 71 */ 72 private static long getTime(String min,String sec,String mills){ 73 return Long.valueOf(min)*60*1000+Long.valueOf(sec)*1000+Long.valueOf(mills); 74 } 75}
相信上面的代碼和注釋已經將這個歌詞解析解釋的挺明白了,需要注意的是上面對i=5,也就是歌詞真正開始的第一句做了特殊處理,因為i=5這句有可能是很長的,假設i=5是“光年之外
(《太空旅客(Passengers)》電影中國區主題曲) - G.E.M. 鄧紫棋 (Gem
Tang)”這句歌詞,如果我們不做特殊處理,在后面繪制的時候,就會發現這句歌詞會超過屏幕大小,很影響美觀,所以我們只截取歌曲名和演唱者,有些說明直接省略掉了。解析好了歌詞,接下來就是重頭戲-歌詞繪制!
歌詞繪制就涉及到了自定義View的知識,所以還未接觸自定義View的小伙伴需要先去看看自定View的基礎知識。歌詞繪制的主要工作主要由下面幾部分構成:
為歌詞控件設置自定義屬性,在構造方法中獲取并設置自定義屬性的默認值
初始化兩支畫筆。分別是歌詞普通畫筆,歌詞高亮畫筆。
獲取當前播放歌詞的位置
畫歌詞,根據當前播放歌詞的位置來決定用哪支畫筆畫
歌詞隨歌曲播放同步滑動
重新繪制
在res文件中的values中新建一個attrs.xml文件,然后定義歌詞的自定義View屬性
1<?xml version="1.0" encoding="utf-8"?> 2<resources> 3 <declare-styleable name="LrcView"> 4 <attr name="highLineTextColor" format="color|reference|integer"/> 5 <attr name="lrcTextColor" format="color|reference|integer"/> 6 <attr name="lineSpacing" format="dimension"/> 7 <attr name="textSize" format="dimension"/> 8 </declare-styleable> 9</resources>
這里只自定義了歌詞顏色,歌詞高亮顏色,歌詞大小,歌詞行間距的屬性,可根據自己需要自行添加。
然后在Java代碼中,設置默認值。
1 private int lrcTextColor;//歌詞顏色 2 private int highLineTextColor;//當前歌詞顏色 3 private int width, height;//屏幕寬高 4 private int lineSpacing;//行間距 5 private int textSize;//字體大小 6 7 public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 8 super(context, attrs, defStyleAttr); 9 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LrcView); 10 lrcTextColor = ta.getColor(R.styleable.LrcView_lrcTextColor, Color.GRAY); 11 highLineTextColor = ta.getColor(R.styleable.LrcView_highLineTextColor, Color.BLUE); 12 float fontScale = context.getResources().getDisplayMetrics().scaledDensity; 13 float scale = context.getResources().getDisplayMetrics().density; 14 //默認字體大小為16sp 15 textSize = ta.getDimensionPixelSize(R.styleable.LrcView_textSize, (int) (16 * fontScale)); 16 //默認行間距為30dp 17 lineSpacing = ta.getDimensionPixelSize(R.styleable.LrcView_lineSpacing, (int) (30 * scale)); 18 //回收 19 ta.recycle(); 20 }
1 private void init() { 2 //初始化歌詞畫筆 3 dPaint = new Paint(); 4 dPaint.setStyle(Paint.Style.FILL);//填滿 5 dPaint.setAntiAlias(true);//抗鋸齒 6 dPaint.setColor(lrcTextColor);//畫筆顏色 7 dPaint.setTextSize(textSize);//歌詞大小 8 dPaint.setTextAlign(Paint.Align.CENTER);//文字居中 9 10 //初始化當前歌詞畫筆 11 hPaint = new Paint(); 12 hPaint.setStyle(Paint.Style.FILL); 13 hPaint.setAntiAlias(true); 14 hPaint.setColor(highLineTextColor); 15 hPaint.setTextSize(textSize); 16 hPaint.setTextAlign(Paint.Align.CENTER); 17 }
我們把初始化的方法放到了構造方法中,這樣就可以避免在重繪時再次初始化。另外由于我們把init方法只放到了第三個構造方法中,所以在上面兩個構造方法需要將super改成this,這樣就能保證哪個構造方法都能執行init方法
1 public LrcView(Context context) { 2 this(context, null); 3 } 4 5 public LrcView(Context context, @Nullable AttributeSet attrs) { 6 this(context, attrs, 0); 7 } 8 9 public LrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 10 super(context, attrs, defStyleAttr); 11 TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LrcView); 12 ...... 13 //回收 14 ta.recycle(); 15 init(); 16 }
因為后面的步驟都是在onDraw方法中執行的,所以我們先貼出onDraw方法中的代碼
1 @Override 2 protected void onDraw(Canvas canvas) { 3 super.onDraw(canvas); 4 5 getMeasuredWidthAndHeight();//得到測量后的寬高 6 getCurrentPosition();//得到當前歌詞的位置 7 drawLrc(canvas);//畫歌詞 8 scrollLrc();//歌詞滑動 9 postInvalidateDelayed(100);//延遲0.1s刷新 10 }
1 private int width, height;//屏幕寬高 2 private void getMeasuredWidthAndHeight(){ 3 if (width == 0 || height == 0) { 4 width = getMeasuredWidth(); 5 height = getMeasuredHeight(); 6 } 7 }
為什么要獲得控件的寬高呢?因為在下面我們需要畫歌詞,畫歌詞時需要畫的位置,這時候就需要用到控件的寬高了。
1 private List<LrcBean> lrcBeanList;//歌詞集合 2 private int currentPosition;//當前歌詞的位置 3 private MediaPlayer player;//當前的播放器 4 5 6 private void getCurrentPosition() { 7 int curTime = player.getCurrentPosition(); 8 //如果當前的時間大于10分鐘,證明歌曲未播放,則當前位置應該為0 9 if (curTime < lrcBeanList.get(0).getStart()||curTime>10*60*1000) { 10 currentPosition = 0; 11 return; 12 } else if (curTime > lrcBeanList.get(lrcBeanList.size() - 1).getStart()) { 13 currentPosition = lrcBeanList.size() - 1; 14 return; 15 } 16 for (int i = 0; i < lrcBeanList.size(); i++) { 17 if (curTime >= lrcBeanList.get(i).getStart() && curTime <= lrcBeanList.get(i).getEnd()) { 18 currentPosition = i; 19 } 20 } 21 }
我們根據當前播放的歌曲時間來遍歷歌詞集合,從而判斷當前播放的歌詞的位置。細心的你可能會發現在currentPosition = 0中有個curTime>10601000的判斷,這是因為在實際使用中發現當player還未播放時,這時候得到的curTime會很大,所以才有了這個判斷(因為正常的歌曲不會超過10分鐘)。
在這個方法我們會發現出現了歌詞集合和播放器,你可能會感到困惑,這些不是還沒賦值嗎?困惑就對了,所以我們需要提供外部方法來給外部傳給歌詞控件歌詞集合和播放器。
1 //將歌詞集合傳給到這個自定義View中 2 public LrcView setLrc(String lrc) { 3 lrcBeanList = LrcUtil.parseStr2List(lrc); 4 return this; 5 } 6 7 //傳遞mediaPlayer給自定義View中 8 public LrcView setPlayer(MediaPlayer player) { 9 this.player = player; 10 return this; 11 }
外部方法中setLrc的參數必須是前面提到的標準歌詞格式的字符串形式,這樣我們就能利用上文的解析工具類LrcUtil中的解析方法將字符串解析成歌詞集合。
3. 畫歌詞
1 private void drawLrc(Canvas canvas) { 2 for (int i = 0; i < lrcBeanList.size(); i++) { 3 if (currentPosition == i) {//如果是當前的歌詞就用高亮的畫筆畫 4 canvas.drawText(lrcBeanList.get(i).getLrc(), width / 2, height / 2 + i * lineSpacing, hPaint); 5 } else { 6 canvas.drawText(lrcBeanList.get(i).getLrc(), width / 2, height / 2 + i * lineSpacing, dPaint); 7 } 8 } 9 }
知道了當前歌詞的位置就很容易畫歌詞了。遍歷歌詞集合,如果是當前歌詞,則用高亮的畫筆畫,其它歌詞就用普通畫筆畫。這里需注意的是兩支畫筆畫的位置公式都是一樣的,坐標位置為x=寬的一半,y=高的一半+當前位置*行間距。隨著當前位置的變化,就能畫出上下句歌詞來。所以其實繪制出來后你會發現歌詞是從控件的正中央開始繪制的,這是為了方便與下面歌詞同步滑動功能配合。
1 //歌詞滑動 2 private void scrollLrc() { 3 //下一句歌詞的開始時間 4 long startTime = lrcBeanList.get(currentPosition).getStart(); 5 long currentTime = player.getCurrentPosition(); 6 7 //判斷是否換行,在0.5內完成滑動,即實現彈性滑動 8 float y = (currentTime - startTime) > 500 ? currentPosition * lineSpacing : lastPosition * lineSpacing + (currentPosition - lastPosition) * lineSpacing * ((currentTime - startTime) / 500f); 9 scrollTo(0,(int)y); 10 if (getScrollY() == currentPosition * lineSpacing) { 11 lastPosition = currentPosition; 12 } 13 }
如果不實現彈性滑動的話,只要判斷當前播放歌曲的時間是否大于當前位置歌詞的結束時間,然后進行scrollTo(0,(int)currentPosition * lineSpacing)滑動即可。但是為了實現彈性滑動,我們需要將一次滑動分成若干次小的滑動并在一個時間段內完成,所以我們動態設置y的值,由于不斷重繪,就能實現在0.5秒內完成View的滑動,這樣就能實現歌詞同步彈性滑動。
500其實就是0.5s,因為在這里currentTime和startTime的單位都是ms
1 float y = (currentTime - startTime) > 500 ? currentPosition * lineSpacing : lastPosition * lineSpacing + (currentPosition - lastPosition) * lineSpacing * ((currentTime - startTime) / 500f);
通過不斷重繪才能實現歌詞同步滑動,這里每隔0.1s進行重繪
1postInvalidateDelayed(100);//延遲0.1s刷新
你以為這樣就結束了嗎?其實還沒有,答案下文揭曉!
然后我們興高采烈的在xml中,引用這個自定義View
LrcView前面的名稱為你建這個類的完整包名
1 <com.example.library.view.LrcView 2 android:id="@+id/lrcView" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 app:lineSpacing="40dp" 6 app:textSize="18sp" 7 app:lrcTextColor="@color/colorPrimary" 8 app:highLineTextColor="@color/highTextColor" 9 />
在Java代碼中給這個自定義View傳入標準歌詞字符串和播放器。
1lrcView.setLrc(lrc).setPlayer(player);
點擊運行,滿心期待自己的成果,接著你就會一臉懵逼,what?怎么是一片空白,什么也沒有!其實這時候你重新理一下上面歌詞繪制的流程,就會發現問題所在。 首先我們的自定義View控件引用到布局中時是先執行onDraw方法的,所以當你調用setLrc和setPlayer方法后,是不會再重新調用onDraw方法的,等于你并沒有傳入歌詞字符串和播放器,所以當然會顯示一片空白
解決方法 :我們在剛才自定義View歌詞控件中添加一個外部方法來調用onDraw,剛好這個invalidate()就能夠重新調用onDraw方法
1 public LrcView draw() { 2 currentPosition = 0; 3 lastPosition = 0; 4 invalidate(); 5 return this; 6 }
然后我們在主代碼中,在調用setLrc和setPlayer后還得調用draw方法
1lrcView.setLrc(lrc).setPlayer(player).draw();
這樣我們節約風的歌詞控件就大功告成了。
“Android如何自定義View歌詞控件”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。