西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁(yè)編程開(kāi)發(fā)Android → android手機(jī)音樂(lè)播放器實(shí)現(xiàn)歌詞同步

android手機(jī)音樂(lè)播放器實(shí)現(xiàn)歌詞同步

相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來(lái)源:西西整理時(shí)間:2013/5/6 20:57:51字體大。A-A+

作者:西西點(diǎn)擊:1926次評(píng)論:0次標(biāo)簽: 音樂(lè)播放器

  • 類型:塞班平臺(tái)應(yīng)用大。1.2M語(yǔ)言:中文 評(píng)分:4.0
  • 標(biāo)簽:
立即下載

最近在做一款android手機(jī)上的音樂(lè)播放器,學(xué)習(xí)到了很多東西,像是Fragment,ActionBar的使用等等,這里就先介紹一下歌詞同步的實(shí)現(xiàn)問(wèn)題。

歌詞同步的實(shí)現(xiàn)思路很簡(jiǎn)單:獲取歌詞文件LRC中的時(shí)間和歌詞內(nèi)容,然后在指定的時(shí)間內(nèi)播放相應(yīng)的內(nèi)容。獲取不難,難就在于如何在手機(jī)屏幕上實(shí)現(xiàn)歌詞的滾動(dòng)。

先上效果圖:

先從最基本的讀取歌詞文件開(kāi)始:

Public class LrcHandle {
    private List mWords = new ArrayList();
    private List mTimeList = new ArrayList();

    //處理歌詞文件
    public void readLRC(String path) {
        File file = new File(path);

        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            InputStreamReader inputStreamReader = new InputStreamReader(
                    fileInputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(
                    inputStreamReader);
            String s = "";
            while ((s = bufferedReader.readLine()) != null) {
                addTimeToList(s);
                if ((s.indexOf("[ar:") != -1) || (s.indexOf("[ti:") != -1)
                        || (s.indexOf("[by:") != -1)) {
                    s = s.substring(s.indexOf(":") + 1, s.indexOf("]"));
                } else {
                    String ss = s.substring(s.indexOf("["), s.indexOf("]") + 1);
                    s = s.replace(ss, "");
                }
                mWords.add(s);
            }

            bufferedReader.close();
            inputStreamReader.close();
            fileInputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            mWords.add("沒(méi)有歌詞文件,趕緊去下載");
        } catch (IOException e) {
            e.printStackTrace();
            mWords.add("沒(méi)有讀取到歌詞");
        }
    }
   public List getWords() {
        return mWords;
   }

    public List getTime() {
        return mTimeList;
    }

    // 分離出時(shí)間
    private int timeHandler(String string) {
       string = string.replace(".", ":");
       String timeData[] = string.split(":");
// 分離出分、秒并轉(zhuǎn)換為整型
        int minute = Integer.parseInt(timeData[0]);
        int second = Integer.parseInt(timeData[1]);
        int millisecond = Integer.parseInt(timeData[2]);

        // 計(jì)算上一行與下一行的時(shí)間轉(zhuǎn)換為毫秒數(shù)
        int currentTime = (minute * 60 + second) * 1000 + millisecond * 10;

        return currentTime;
    }

   private void addTimeToList(String string) {
        Matcher matcher = Pattern.compile(
                "\\[\\d{1,2}:\\d{1,2}([\\.:]\\d{1,2})?\\]").matcher(string);
        if (matcher.find()) {
            String str = matcher.group();
            mTimeList.add(new LrcHandle().timeHandler(str.substring(1,
                    str.length() - 1)));
        }

    }
}

 一般歌詞文件的格式大概如下:

[ar:藝人名]

[ti:曲名]

[al:專輯名]

[by:編者(指編輯LRC歌詞的人)]

[offset:時(shí)間補(bǔ)償值] 其單位是毫秒,正值表示整體提前,負(fù)值相反。這是用于總體調(diào)整顯示快慢的。

但也不一定,有時(shí)候并沒(méi)有前面那些ar:等標(biāo)識(shí)符,所以我們這里也提供了另一種解析方式。

歌詞文件中的時(shí)間格式則比較統(tǒng)一:[00:00.50]等等,00:表示分鐘,00.表示秒數(shù),.50表示毫秒數(shù),當(dāng)然,我們最后是要將它們轉(zhuǎn)化為毫秒數(shù)處理才比較方便。

處理完歌詞文件并得到我們想要的數(shù)據(jù)后,我們就要考慮如何在手機(jī)上滾動(dòng)顯示我們的歌詞并且與我們得到的時(shí)間同步了。

先是布局文件:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >

<Button
android:id="@+id/button"
android:layout_width="60dip"
android:layout_height="60dip"
android:text="@string/停止" />

<com.example.slidechange.WordView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/button" />


WordView是自定義的TextView,它繼承自TextView:

public class WordView extends TextView {
private List mWordsList = new ArrayList();
private Paint mLoseFocusPaint;
private Paint mOnFocusePaint;
private float mX = 0;
private float mMiddleY = 0;
private float mY = 0;
private static final int DY = 50;
private int mIndex = 0;

public WordView(Context context) throws IOException {
super(context);
init();
}

public WordView(Context context, AttributeSet attrs) throws IOException {
super(context, attrs);
init();
}

public WordView(Context context, AttributeSet attrs, int defStyle)
throws IOException {
super(context, attrs, defStyle);
init();
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

canvas.drawColor(Color.BLACK);
Paint p = mLoseFocusPaint;
p.setTextAlign(Paint.Align.CENTER);
Paint p2 = mOnFocusePaint;
p2.setTextAlign(Paint.Align.CENTER);

canvas.drawText(mWordsList.get(mIndex), mX, mMiddleY, p2);

int alphaValue = 25;
float tempY = mMiddleY;
for (int i = mIndex - 1; i >= 0; i--) {
tempY -= DY;
if (tempY < 0) {
break;
}
p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
canvas.drawText(mWordsList.get(i), mX, tempY, p);
alphaValue += 25;
}
alphaValue = 25;
tempY = mMiddleY;
for (int i = mIndex + 1, len = mWordsList.size(); i < len; i++) {
tempY += DY;
if (tempY > mY) {
break;
}
p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
canvas.drawText(mWordsList.get(i), mX, tempY, p);
alphaValue += 25;
}
mIndex++;
}

@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);

mX = w * 0.5f;
mY = h;
mMiddleY = h * 0.3f;
}

@SuppressLint("SdCardPath")
private void init() throws IOException {
setFocusable(true);

LrcHandle lrcHandler = new LrcHandle();
lrcHandler.readLRC("/sdcard/陪我去流浪.lrc");
mWordsList = lrcHandler.getWords();

mLoseFocusPaint = new Paint();
mLoseFocusPaint.setAntiAlias(true);
mLoseFocusPaint.setTextSize(22);
mLoseFocusPaint.setColor(Color.WHITE);
mLoseFocusPaint.setTypeface(Typeface.SERIF);

mOnFocusePaint = new Paint();
mOnFocusePaint.setAntiAlias(true);
mOnFocusePaint.setColor(Color.YELLOW);
mOnFocusePaint.setTextSize(30);
mOnFocusePaint.setTypeface(Typeface.SANS_SERIF);
}
}

最主要的是覆蓋TextView的onDraw()和onSizeChanged()。

在onDraw()中我們重新繪制TextView,這就是實(shí)現(xiàn)歌詞滾動(dòng)實(shí)現(xiàn)的關(guān)鍵。歌詞滾動(dòng)的實(shí)現(xiàn)思路并不復(fù)雜:將上一句歌詞向上移動(dòng),當(dāng)前歌詞字體變大,顏色變黃突出顯示。

我們需要設(shè)置位移量DY = 50。顏色和字體大小我們可以通過(guò)設(shè)置Paint來(lái)實(shí)現(xiàn)。

我們注意到,在我實(shí)現(xiàn)的效果中,距離當(dāng)前歌詞越遠(yuǎn)的歌詞,就會(huì)變透明,這個(gè)可以通過(guò)p.setColor(Color.argb(255 - alphaValue, 245, 245, 245))來(lái)實(shí)現(xiàn)。

接著就是主代碼:

public class MainActivity extends Activity {
private WordView mWordView;
private List mTimeList;
private MediaPlayer mPlayer;

@SuppressLint("SdCardPath")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {

@Override
public void onClick(View v) {
mPlayer.stop();
finish();
}
});

mWordView = (WordView) findViewById(R.id.text);

mPlayer = new MediaPlayer();
mPlayer.reset();
LrcHandle lrcHandler = new LrcHandle();
try {
lrcHandler.readLRC("/sdcard/陪我去流浪.lrc");
mTimeList = lrcHandler.getTime();
mPlayer.setDataSource("/sdcard/陪我去流浪.mp3");
mPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
}
final Handler handler = new Handler();
mPlayer.start();
new Thread(new Runnable() {
int i = 0;

@Override
public void run() {
while (mPlayer.isPlaying()) {
handler.post(new Runnable() {

@Override
public void run() {
mWordView.invalidate();
}
});
try {
Thread.sleep(mTimeList.get(i + 1) - mTimeList.get(i));
} catch (InterruptedException e) {
}
i++;
if (i == mTimeList.size() - 1) {
mPlayer.stop();
break;
}
}
}
}).start();
}
}

歌詞的顯示需要重新開(kāi)啟一個(gè)線程,因?yàn)橹骶程是播放歌曲的。

代碼很簡(jiǎn)單,功能也很簡(jiǎn)單,最主要的是多多嘗試,多多修改,就能明白代碼的原理了。

    相關(guān)評(píng)論

    閱讀本文后您有什么感想? 已有人給出評(píng)價(jià)!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過(guò)難過(guò)
    • 5 囧
    • 3 圍觀圍觀
    • 2 無(wú)聊無(wú)聊

    熱門評(píng)論

    最新評(píng)論

    發(fā)表評(píng)論 查看所有評(píng)論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字?jǐn)?shù): 0/500 (您的評(píng)論需要經(jīng)過(guò)審核才能顯示)