多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國(guó)內(nèi)最全I(xiàn)T社區(qū)平臺(tái) 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁(yè) > php開(kāi)源 > 綜合技術(shù) > MP3歌詞的同步與拖拽設(shè)計(jì)

MP3歌詞的同步與拖拽設(shè)計(jì)

來(lái)源:程序員人生   發(fā)布時(shí)間:2016-06-24 17:34:50 閱讀次數(shù):2701次

原文地址:http://blog.csdn.net/mc_hust/article/details/51534901

自從準(zhǔn)備畢業(yè)論文開(kāi)始,就沒(méi)寫過(guò)博客了,關(guān)注量也明顯呈下滑趨勢(shì)(雖然本來(lái)就少)。到現(xiàn)在已入職1個(gè)多月了,抽空把之前做的1個(gè)項(xiàng)目整理1下,算是畢業(yè)后的第1篇博客吧。


關(guān)于Mp3播放器,網(wǎng)上有各種實(shí)現(xiàn)方法,但是對(duì)歌詞的同步和滑動(dòng)更改播放進(jìn)度的講授卻少之又少,所以我這里重點(diǎn)放在歌詞的設(shè)計(jì)上(需要完全代碼的朋友,可以在評(píng)論中留下郵箱,我會(huì)盡快回復(fù)),關(guān)于Mp3的“播放\切歌\暫停”和“隨機(jī)\順序\單曲”播放等經(jīng)常使用功能應(yīng)當(dāng)還是比較好做的。下面看看效果: 
- 主界面以下圖:
圖1 - 主界面.jpg
- 右滑以后進(jìn)入歌詞界面:
圖2 - 右滑進(jìn)入歌詞界面.jpg
- 點(diǎn)擊右上角那個(gè)大設(shè)置按鈕:
圖3 - 設(shè)置界面.jpg


全部項(xiàng)目主要觸及到以下知識(shí)點(diǎn):
- ViewPager
- Service與Activity通訊
- Broadcast
- ContentResolver
- PreferenceActivity
- MediaPlayer
以上幾個(gè)知識(shí)點(diǎn)大家應(yīng)當(dāng)比較熟習(xí),,4大組件全用上了,個(gè)人覺(jué)得這是個(gè)比較好的練手項(xiàng)目。下面從播放開(kāi)始看吧。


1、MP3播放器Service

作為播放器,固然是需要能夠支持后臺(tái)播放的,所以在啟動(dòng)播放之前,需要開(kāi)啟service。為了方便Activity與Service通訊,這里通過(guò)bindService方法開(kāi)啟Service,代碼以下:

bindService(new Intent(MainActivity.this, PlayService.class), connection, Context.BIND_AUTO_CREATE);

其中connection是Servive的1個(gè)回調(diào)方法,在里面獲得Mp3Binder:

private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { PlayService.Mp3Binder binder = (PlayService.Mp3Binder) service; player = new Mp3Player(binder.getService(), musicInfos); } @Override public void onServiceDisconnected(ComponentName name) { } };

上面有個(gè)player,這個(gè)就是對(duì)播放器播放、暫停、切歌等操作的1個(gè)封裝類,下面來(lái)看看:


2、Mp3的播放、暫停、切歌

為了方便使用,將Mp3的播放操作封裝到Mp3Player類中,在里面我實(shí)現(xiàn)了Mp3的各種經(jīng)常使用操作,和循環(huán)、單曲、順序播放等經(jīng)常使用播放模式,通過(guò)此類與Service通訊,便可完成對(duì)MediaPlayer的操作。


3、MediaPlayer的使用

MediaPlayer的使用應(yīng)當(dāng)還是很簡(jiǎn)單的,如果沒(méi)有做過(guò)MediaPlayer開(kāi)發(fā)的朋友,需要注意幾個(gè)問(wèn)題:
1. 在播放之前1定要先重置、準(zhǔn)備。調(diào)用的順序?yàn)椋簉eset、setDataSource、prepare、start。
2. 由于播放的歌曲通常是在SD卡上,記得要申明權(quán)限:



3. 由于觸及到搜索歌詞、和隨機(jī)播放的時(shí)候需要計(jì)算下1首歌,那末我們分別需要捕捉播放開(kāi)始和播放結(jié)束的信號(hào),可使用兩個(gè)監(jiān)聽(tīng)器完成,以下:

mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { sendBroadcast(new Intent(MainActivity.Mp3Receiver.ACTION_NEW)); } }); mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { sendBroadcast(new Intent(MainActivity.Mp3Receiver.ACTION_END)); } });

這里我通過(guò)廣播的方式將“開(kāi)始播放”和“結(jié)束播放”兩個(gè)信號(hào)傳遞出去。


4、獲得歌曲列表

說(shuō)了這么多,下面開(kāi)始搜歌吧。這里用到Android的ContentProvider,Android系統(tǒng)會(huì)搜索手機(jī)里所有的音頻文件,并放在MediaStore下面,我們要做的就是從這里面拿出想要的數(shù)據(jù)。通過(guò)

context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);

可以拿到列表的cursor,然后在當(dāng)中去逐條獲得信息便可。把每個(gè)音頻文件視為1個(gè)對(duì)象,可以以下定義音頻對(duì)象:

class MusicInfo { long id; String title; String artist; String duration; int durationInSeconds; long size; String data; long albumId; @Override public boolean equals(Object o) { data = data.replace("file://", ""); return data.equals(((MusicInfo) o).data); } }

這樣從Cursor中獲得數(shù)據(jù)以后填寫到上面MusicInfo中就能夠了,代碼示意以下:

private static ListgetMusicInfoList(Context context) { Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); Listlist = new ArrayList<>(); int count = cursor.getCount(); while (count-- > 0) { cursor.moveToNext(); if (0 == cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC))) { continue; } MusicInfo info = new MusicInfo(); info.id = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media._ID)); info.artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)); long durationSeconds = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)) / 1000; info.durationInSeconds = (int) durationSeconds; info.duration = durationSeconds % 60 < 10 ? durationSeconds / 60 + ":0" + durationSeconds % 60 : durationSeconds / 60 + ":" + durationSeconds % 60; info.size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE)); info.title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE)); info.data = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA)); info.albumId = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM_ID)); list.add(info); } return list; }

這樣拿到1個(gè)list然后設(shè)置到ListView中就能夠完成歌曲列表的顯示了。


5、搜索歌詞

搜索歌詞的原理其實(shí)就是在當(dāng)前歌曲目錄下去搜索同名的.lrc文件,然后從中讀入數(shù)據(jù)流進(jìn)行解析,歌詞的解析可以參考lrc歌詞的協(xié)議自行完成(需要完全代碼可以在下面留下您的郵箱)。


6、歌詞部份

接下來(lái)就是歌詞的同步與歌詞的滑動(dòng)了,網(wǎng)上對(duì)同步的實(shí)現(xiàn)大多是采取自定義1個(gè)TextView,然后再onDraw當(dāng)中去用Paint畫(huà)筆來(lái)畫(huà)出歌詞。這樣做對(duì)同步顯示來(lái)說(shuō)非常容易,但是如果想讓他在切換歌詞的時(shí)候平滑移動(dòng)和拖拽歌詞改變播放進(jìn)度這都是比較麻煩的。因此這里我采取ListView來(lái)做歌詞,這樣平滑移動(dòng)和滑動(dòng)監(jiān)聽(tīng)都比較方便。

由于需要將歌詞放在屏幕中央,所以需要提早計(jì)算出屏幕中央是ListView的第幾個(gè)Item,然后在前后順次留相應(yīng)數(shù)據(jù)的空白。例如第5個(gè)item在中間,則在設(shè)置歌詞數(shù)據(jù)的時(shí)候需要在前后分別留5個(gè)空白(示意代碼,不建議這么寫):

public void setLrcList(ListlrcList) { //設(shè)置歌詞內(nèi)容 this.lrcList = lrcList; //在歌詞后留白 lrcList.add(new Lrc()); lrcList.add(new Lrc()); lrcList.add(new Lrc()); lrcList.add(new Lrc()); lrcList.add(new Lrc()); lrcList.add(new Lrc()); //在歌詞前留白 lrcList.add(0, new Lrc()); lrcList.add(0, new Lrc()); lrcList.add(0, new Lrc()); lrcList.add(0, new Lrc()); lrcList.add(0, new Lrc()); lrcList.add(0, new Lrc());}
6.1 同步平滑更新歌詞

通過(guò)update方法封裝更新功能:

/** * 更新歌詞內(nèi)容 * * @param position 當(dāng)前歌曲播放的時(shí)間 */ public void update(int position) { if (!isTouching) { adapter.notifyDataSetChanged(); isAutoScroll = true; lvLrc.smoothScrollToPositionFromTop(adapter.update(position) - 4, 0, 1000); //減4是保證當(dāng)前這句歌詞能顯示在正中間 } }
  • 這里對(duì)ListView的滑動(dòng)沒(méi)有用到smoothScrollToPosition(int position);緣由是這個(gè)函數(shù)僅僅是保證position的那個(gè)item會(huì)顯示出來(lái),而我們想要的效果是讓他顯示到正中間,所以只能用smoothScrollToPositionFromTop,讓第前4句歌詞顯示在最頂端來(lái)實(shí)現(xiàn)效果。
  • adapter.update(position):這個(gè)方法的作用是獲得歌曲播放到position時(shí)間的時(shí)候是第幾句歌詞,從而讓他顯示在中間,代碼以下:
public int update(int position) { for (int i = 0; i < lrcList.size() - 1; i++) { //判斷當(dāng)前播放時(shí)間是不是在歌詞的第1句和最后1句歌詞時(shí)間內(nèi) if (position >= lrcList.get(i).getLrcTime() && position < lrcList.get(i + 1).getLrcTime() || position < lrcList.get(0).getLrcTime()) { index = i; break; } //如果時(shí)間超過(guò)了最后1句歌詞,則停留在最后1句歌詞 else if (position > lrcList.get(lrcList.size() - 1).getLrcTime()) { index = lrcList.size() - 1; } } return index; }

這類似1個(gè)順序查找算法,固然朋友們可以采取2分查找等其他算法提高效力。

這里實(shí)現(xiàn)的界面是1個(gè)ViewPager,第1頁(yè)是歌曲列表,右滑到第2頁(yè)是歌詞。效果見(jiàn)上圖

6.2 拖拽歌詞改變播放進(jìn)度

這部份主要是對(duì)歌詞布局,即ListView的觸摸監(jiān)聽(tīng)操作,采取listView.setOnTouchListener來(lái)實(shí)現(xiàn),先來(lái)看看這部份代碼:

lvLrc.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isTouching = true; break; case MotionEvent.ACTION_UP: int time = lrcList.get(lvLrc.getFirstVisiblePosition() + 5).getLrcTime(); ((MainActivity) activity).resume(time / 1000); isTouching = false; break; case MotionEvent.ACTION_CANCEL: isTouching = false; break; } return false; } });

主要是在ACTION_UP的時(shí)候進(jìn)行操作,計(jì)算出當(dāng)前播放的歌詞的時(shí)間字段,然后通過(guò)service控制播放進(jìn)度(resume中封裝了對(duì)service的操作)。可以看到,在ACTION_DOWN和ACTION_CANCEL中也做了操作,主要是設(shè)置isTouching的值。這是為了避免在我們正在拖拽歌詞的進(jìn)程中,由于歌詞同步作用致使當(dāng)前歌詞改變從而使歌詞的ListView自動(dòng)滑動(dòng)。為了避免這個(gè)矛盾的出現(xiàn),在歌詞同步函數(shù)(update)中需要先檢查isTouch的值,然后決定是不是要進(jìn)行自動(dòng)同步(代碼見(jiàn)6.1)。


7、設(shè)置界面PreferenceActivity

設(shè)置界面幾近是所有的App都要用到的,PreferenceActivity就是專門為設(shè)置界面打造的,而Android原生代碼中幾近所有的設(shè)置界面也都是通過(guò)這個(gè)完成的。PreferenceActivity的使用方法網(wǎng)上有很多,他的使用與1般的布局類似,主要有以下幾種類型:
* ListPreference 列表項(xiàng)菜單
* EditTextPreference 編輯框菜單
* SwitchPreference 開(kāi)關(guān)菜單
本項(xiàng)目中就使用了以上幾種菜單項(xiàng),其余的也大同小異。我們可以對(duì)菜單項(xiàng)按功能進(jìn)行分組,每組是1個(gè)PreferenceCategory,而所有的PreferenceCategory都屬于1個(gè)PreferenceScreen,這樣的層級(jí)關(guān)系非常明確,具體的菜單布局代碼以下:

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:title="設(shè)置"> <PreferenceCategory android:title="播放模式"> <ListPreference android:defaultValue="單曲循環(huán)" android:entries="@array/play_mode" android:entryValues="@array/play_mode_value" android:key="@string/key_play_mode" android:title="選擇播放模式" /> PreferenceCategory> <PreferenceCategory android:title="歌詞設(shè)置"> <ListPreference android:entries="@array/lrc_color" android:entryValues="@array/lrc_color_value" android:key="@string/key_lrc_color" android:title="歌詞色彩" /> <ListPreference android:entries="@array/lrc_size" android:entryValues="@array/lrc_size_value" android:key="@string/key_lrc_size" android:title="歌詞大小" /> PreferenceCategory> <PreferenceCategory android:title="定時(shí)關(guān)機(jī)"> <EditTextPreference android:summary="將在設(shè)置的分鐘數(shù)后關(guān)機(jī)" android:title="請(qǐng)輸入關(guān)機(jī)時(shí)間" /> PreferenceCategory> <PreferenceCategory android:title="搖1搖切歌"> <SwitchPreference android:title="開(kāi)啟搖晃切歌" /> PreferenceCategory>

Activity的代碼也非常簡(jiǎn)單:

package com.example.machao10.mp3; import android.preference.EditTextPreference; import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.SwitchPreference; import android.support.v7.app.AppCompatActivity; import android.os.Bundle;public class SettingsActivity extends PreferenceActivity { ListPreference listPlayMode, listLrcSize, listLrcColor, listRing, listNotification, listSms; EditTextPreference etAutoShutdown; SwitchPreference switchShake; private void initPreference() { listPlayMode = (ListPreference) findPreference(getString(R.string.key_play_mode)); SettingsChangeListener listener = new SettingsChangeListener(); listPlayMode.setOnPreferenceChangeListener(listener); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.settings); initPreference(); } class SettingsChangeListener implements Preference.OnPreferenceChangeListener { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { String key = preference.getKey(); return true; } } }

固然,以上只是對(duì)設(shè)值界面進(jìn)行了顯示,還需要完成相應(yīng)的邏輯和用戶設(shè)置的持久化,這個(gè)大家可以參考PreferenceActivity的具體用法,這里我就不展開(kāi)講了,需要完全開(kāi)起源碼的,可以在下面留下郵箱,我會(huì)及時(shí)給您回復(fù)的。


好了,mp3播放器就講到這里,主要是從邏輯結(jié)構(gòu)上做的梳理,然后針對(duì)部份細(xì)節(jié)進(jìn)行展開(kāi),并未將完全的代碼做1個(gè)串接,主要還是斟酌到關(guān)于Mp3的功能網(wǎng)上有很多資料,只是在歌詞那1塊應(yīng)當(dāng)還是很空白的。也希望我的這個(gè)歌詞方案能夠給大家?guī)?lái)1些方便,同時(shí)大家有甚么好的建議歡迎討論~

——超低空

生活不易,碼農(nóng)辛苦
如果您覺(jué)得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 日本在线一区二区三区 | 久草在线资源福利站 | 亚洲精品一区二区 | 国产成人精选免费视频 | v影院最新在线v视频 | 国产欧美亚洲精品第3页在线 | 欧美一二三区视频 | 99精品日韩 | 亚州免费视频 | 欧美日韩免费看 | 国产欧美日韩高清专区手机版 | 欧美日韩精品免费一区二区三区 | 国产视频一区二区在线观看 | 亚洲欧美日韩高清一区二区三区 | 亚洲国产成人资源在线桃色 | free asian xxxxx黑人| 久久99一区| 欧美最猛性xxxx | 国产在线视频一区二区三区 | 国产精品一区二区久久 | 日本在线不卡免费视频一区 | 国产视频一二三 | 免费爱爱网站 | 国产在线精品一区二区中文 | 免费福利在线 | 真实国产乱人伦在线视频播放 | 国产精品99久久久久久夜夜嗨 | 社区天堂| 中日韩欧美中文字幕毛片 | 99久久精品国产一区二区成人 | 最近的中文字幕大全免费8 最近的中文字幕大全免费版 | 3www黄| 亚洲一区二区精品 | 在线看的黄色网址 | 老司机午夜免费福利 | 毛片资源站| 国产精品亚洲专一区二区三区 | 一区二区三区四区在线 | 亚洲精品大片 | 在线xxx | 国产一区二区精品 |