1. LRC文本解讀
Android中要實(shí)現(xiàn)滾動(dòng)歌詞的第一步,是需要對(duì)LRC歌詞文本進(jìn)行解析,首先來看一份標(biāo)準(zhǔn)的歌詞文本模式:
[ti:失戀戰(zhàn)線聯(lián)盟]
[ar:草蜢]
[al:]
[00:00.00]草蜢-失戀戰(zhàn)線聯(lián)盟
[00:08.78]編輯:小婧
[01:43.33][00:16.27]她總是只留下電話號(hào)碼
[01:46.97][00:19.81]從不肯讓我送她回家
[01:50.61][00:23.43]聽說你也曾經(jīng)愛上過她
[01:54.15][00:27.07]曾經(jīng)也同樣無法自拔
[01:57.78][00:30.72]你說你學(xué)不會(huì)假裝瀟灑
[02:01.41][00:34.36]卻叫我別太早放棄她
[02:05.05][00:37.99]把過去傳說成一段神話
[02:08.70][00:41.59]然后笑你是一樣的傻
[02:12.01][00:45.11]我們那么在乎她
[02:14.15][00:47.01]卻被她全部抹殺
[02:15.96][00:48.87]越談她越相信永遠(yuǎn)得不到回答
[02:19.57][00:52.49]到底她怎么想
[02:21.35][00:54.28]應(yīng)該繼續(xù)在這么
[02:23.37][00:56.36]還是說穿跑了吧
[02:26.89][00:59.80]找一個(gè)承認(rèn)失戀的方法
[02:30.48][01:03.41]讓心情好好地放個(gè)假
[02:34.14][01:07.00]當(dāng)你我不小心又想起她
[02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
[02:48.69]
LRC有兩種類型的標(biāo)簽,一種是 標(biāo)識(shí)標(biāo)簽
,一種是 時(shí)間標(biāo)簽
- 標(biāo)識(shí)標(biāo)簽,格式為[標(biāo)識(shí)名:值],例如
[ti:失戀戰(zhàn)線聯(lián)盟]
,這種預(yù)定義的標(biāo)簽 - 時(shí)間標(biāo)簽,格式為
[mm:ss]
或[mm:ss.ff]
(分鐘:秒數(shù).毫秒數(shù)),例如[02:48.69]
,其中數(shù)字必須為非負(fù)整數(shù)。
時(shí)間標(biāo)簽必須位于行首位置,一行歌詞可以包含多個(gè)時(shí)間標(biāo)簽,例如[02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
,表示 “就在記憶里畫一個(gè)叉” 這句在這首歌的[02:45.69][02:42.20][02:37.69][01:10.60]
這四個(gè)時(shí)間節(jié)點(diǎn)出現(xiàn)過。這句歌詞可以簡(jiǎn)化為
[02:45.69]就在記憶里畫一個(gè)叉
[02:42.20]就在記憶里畫一個(gè)叉
[02:37.69]就在記憶里畫一個(gè)叉
[01:10.60]就在記憶里畫一個(gè)叉
2. 從assets目錄下讀取LRC文件
在真實(shí)項(xiàng)目中一般文件需要及時(shí)去獲取,此處為了方便,我們將LRC文本放在assets目錄下進(jìn)行讀取,從assets目錄下讀取文件的代碼如下:
/**
* 根據(jù)文件名獲取assets目錄下的文件內(nèi)容
* @param fileName
* @return
*/
private String getLrcText(String fileName) {
String lrcText = null;
try {
InputStreamReader inputReader = new InputStreamReader(getResources().getAssets().open(fileName));
BufferedReader bufReader = new BufferedReader(inputReader);
String line = "";
while ((line = bufReader.readLine()) != null)
lrcText += line;
} catch (IOException e) {
e.printStackTrace();
}
return lrcText;
}
將text.lrc文件解析為字符串lrcText
。
3. 解析由LRC文件得到的lrcText 字符串
創(chuàng)建LrcEntity類,用于解析每行歌詞包含的內(nèi)容,該類包括3個(gè)變量:time
,timeLong
,text
。
- time: 表示當(dāng)前行歌詞的播放時(shí)間標(biāo)簽,例如[01:10.60]
- timeLong: 表示當(dāng)前時(shí)間標(biāo)簽time轉(zhuǎn)換為long型后的數(shù)值,例如
[01:10.60]
轉(zhuǎn)換為long為70060
毫秒(70060=01 * 60
1000 + 10 * 1000+60) - text: 表示當(dāng)前歌詞的內(nèi)容,例如
就在記憶里畫一個(gè)叉
創(chuàng)建的LrcEntity類如下所示:
/**
* 歌詞解析基類
*/
public class LrcEntity {
public String time;
public long timeLong;
public String text;
public LrcEntity(String time, String text, long timeLong) {
this.time = time;
this.text = text;
this.timeLong = timeLong;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public long getTimeLong() {
return timeLong;
}
public void setTimeLong(long timeLong) {
this.timeLong = timeLong;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public int compareTo(LrcEntity entity) {
if (entity == null) {
return -1;
}
return (int) (timeLong - entity.getTimeLong());
}
/**
* 解析歌詞文本
* @param lrcText
* @return
*/
private static List<LrcEntity> parseLrc(String lrcText) {
if (TextUtils.isEmpty(lrcText)) {
return null;
}
List<LrcEntity> entityList = new ArrayList<>();
// 將字符串以換行符切割
String[] array = lrcText.split("\\n");
for (String line : array) {
// 循環(huán)遍歷按行解析
List<LrcEntity> list = parseLine(line);
if (list != null && !list.isEmpty()) {
entityList.addAll(list);
}
}
// 使序列按大小升序排序(由小到大)
Collections.sort(entityList);
return entityList;
}
/**
* 針對(duì)每一句歌詞解析,并存到LrcEntity中
* @param line
* @return
*/
private static List<LrcEntity> parseLine(String line) {
if (TextUtils.isEmpty(line)) {
return null;
}
// 去除空格
line = line.trim();
// 正則表達(dá)式,判斷l(xiāng)ine中是否有[01:10.60]格式的片段
Matcher lineMatcher = Pattern.compile("((\\[\\d\\d:\\d\\d\\.\\d\\d\\])+)(.+)").matcher(line);
// 如果沒有,返回null
if (!lineMatcher.matches()) {
return null;
}
// 得到時(shí)間標(biāo)簽
String times = lineMatcher.group(1);
// 得到文本內(nèi)容
String text = lineMatcher.group(3);
List<LrcEntity> entryList = new ArrayList<>();
Matcher timeMatcher = Pattern.compile("\\[(\\d\\d):(\\d\\d)\\.(\\d\\d)\\]").matcher(times);
while (timeMatcher.find()) {
long min = Long.parseLong(timeMatcher.group(1));// 分
long sec = Long.parseLong(timeMatcher.group(2));// 秒
long mil = Long.parseLong(timeMatcher.group(3));// 毫秒
// 得到long型時(shí)間
long time = min * DateUtils.MINUTE_IN_MILLIS + sec * DateUtils.SECOND_IN_MILLIS + mil * 10;
// 最終解析得到一個(gè)list
entryList.add(new LrcEntity(times, text, time));
}
return entryList;
}
}
代碼中有很詳細(xì)的注釋, 上述parseLine方法中用到了正則表達(dá)式,不了解的同學(xué)請(qǐng)自行百度,很簡(jiǎn)單。這種解析方式的弊端在于不能解析多個(gè)時(shí)間標(biāo)簽同時(shí)存在,當(dāng)然肯定是有方法的,但是我還沒學(xué)會(huì)。。。
我們來看另外一種接地氣的方法:
/**
* 針對(duì)每一句歌詞解析,并存到LrcEntity中
*
* @param line [02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
* @return
*/
public List<LrcEntity> parseLine2(String line) {
List<LrcEntity> entryList = new ArrayList<>();
int pos1 = line.indexOf("[");//0
int pos2 = line.indexOf("]");//9 indexof如果找不到返回-1
if (pos1 == 0 && pos2 != -1) {
//long數(shù)組用于存放時(shí)間戳,判斷含有多少個(gè)時(shí)間標(biāo)簽
String[] times = new String[getCount(line)];
String strTime = line.substring(pos1, pos2+1);//[02:45.69]
// 時(shí)間標(biāo)簽數(shù)組
times[0] = strTime;
//判斷是否還有下一個(gè)
String text = line;//[02:45.69][02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
int i = 1;
while (pos1 == 0 && pos2 != -1) {//判斷是否有時(shí)間的顯示,既歌詞
text = text.substring(pos2 + 1);//[02:42.20][02:37.69][01:10.60]就在記憶里畫一個(gè)叉
pos1 = text.indexOf("[");//0
pos2 = text.indexOf("]");//9
if (pos2 != -1) {
strTime = text.substring(pos1, pos2 + 1);//[02:42.20]
times[i] = strTime;//將第二個(gè)時(shí)間戳添加到數(shù)組中
if (times[i] == "") {
return entryList;
}
}
i++;
}
LrcEntity lrcEntity = new LrcEntity();
for (int j = 0; j < times.length; j++) {
if (times[j] != null) {
lrcEntity.setText(text);
lrcEntity.setTimeLong(Str2Long(times[j]));
lrcEntity.setTime(times[j]);
entryList.add(lrcEntity);//將歌詞信息添加到集合中
lrcEntity = new LrcEntity();
}
}
}
return entryList;
}
//將字符串轉(zhuǎn)換為long類型
private long Str2Long(String strTime) {
long showTime = -1;
try {
strTime = strTime.substring(1,strTime.length()-1);
String[] s1 = strTime.split(":");
String[] s2 = s1[1].split("\\.");
long min = Long.parseLong(s1[0]);
long second = Long.parseLong(s2[0]);
long mil = Long.parseLong(s2[1]);
showTime = min * 60 * 1000 + second * 1000 + mil * 10;
} catch (NumberFormatException e) {
e.printStackTrace();
}
return showTime;
}
/**
* 判斷當(dāng)前行的歌詞播放幾次
*
* @param line
* @return
*/
private int getCount(String line) {
String[] split = line.split("\\]");
return split.length;
}
由于上述代碼都是重復(fù)的,這里只給出針對(duì)每一句歌詞解析,并存到LrcEntity中的方法,主要是針對(duì)字符串進(jìn)行切割,是字符串的基本操作,相信大家對(duì)這塊都很熟悉。
最后得到了對(duì)歌詞文本解析的集合,其中包括時(shí)間標(biāo)簽,內(nèi)容以及時(shí)間標(biāo)簽轉(zhuǎn)換為long后的數(shù)據(jù),解析這里基本就說完了,關(guān)于歌詞的顯示和繪制,下次再說,謝謝大家。