前一篇已經將視頻模塊弄好了,今天主要理一下音樂這一模塊,包括:
- 通過ContentProvider獲取音樂列表數據
- 音樂播放界面的動畫和佈局
- 音樂播放的界面AudioPlayerActivity和AudioPlayService交互。(實現播放、暫停、上下一首、播放模式的切換)
效果圖
結構圖
獲取音樂列表數據
- 分析:這裏還是通過內容提供者ContentProvider去獲取。前面獲取視頻時沒有將爲什麼這樣獲取,這裏補充一下,面試概率還挺高的:使用ContentProvider可以實現不同應用程序下數據的共享,主要是通過把自己的數據通過uri的形式共享出去來實現共享的。這樣的好處就是:屏蔽數據存儲的細節,對開發者透明,我們只需要關心操作數據的uri就可以了。
- 觀察音樂列表,我們需要這些數據:音樂名稱(DISPLAY_NAME),音樂時長(DURATION),藝術家(ARTIST),音樂路徑路徑(DATA)。
下面是具體獲取的方法(跟獲取視頻一模一樣)。
private AudioListAdapter adapter; private SimpleQueryHandler queryHandler; @Override protected void initData() { adapter = new AudioListAdapter(getActivity(), null, false); lv.setAdapter(adapter); queryHandler = new SimpleQueryHandler(getActivity().getContentResolver()); String[] projection = {Media._ID, Media.DISPLAY_NAME, Media.DURATION, Media.DATA, Media.ARTIST}; queryHandler.startQuery(0, adapter, Media.EXTERNAL_CONTENT_URI, projection, null, null, null); } class SimpleQueryHandler extends AsyncQueryHandler { public SimpleQueryHandler(ContentResolver cr) { super(cr); } /** * token: 查詢的標識 */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { super.onQueryComplete(token, cookie, cursor); if (cookie != null && cookie instanceof CursorAdapter) { CursorAdapter adapter = (CursorAdapter) cookie; adapter.changeCursor(cursor);//相當於notifyDatasetChange CursorUtil.printCursor(cursor); } } }
至此,可以實現上面音樂的列表。
音樂播放界面的佈局和動畫
音樂之所以能播放並且能切換上/下一首,肯定在點擊listview的條目的時候把所有數據都傳遞過來了。
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Cursor cursor = adapter.getCursor(); Bundle bundle = new Bundle(); bundle.putInt("currentPosition", position); bundle.putSerializable("audioList", cursorToList(cursor)); enterActivity(AudioPlayerActivity.class, bundle); } }); private ArrayList<AudioItem> cursorToList(Cursor cursor){ cursor.moveToPosition(-1); ArrayList<AudioItem> list = new ArrayList<AudioItem>(); while (cursor.moveToNext()){ AudioItem audioItem = AudioItem.fromCursor(cursor); list.add(audioItem); } return list; }
播放頁面佈局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@mipmap/base_bg" android:orientation="vertical"> <!--titleBar--> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:background="@mipmap/base_titlebar_bg" android:orientation="horizontal"> <ImageView android:id="@+id/iv_back" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/selector_btn_back" /> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="@color/white" android:textSize="20sp" /> </RelativeLayout> <!--音頻跳動的動畫和歌曲藝術家--> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_anim" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:background="@drawable/audio_player_anim" /> <TextView android:id="@+id/tv_artist" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBottom="@id/iv_anim" android:layout_centerHorizontal="true" android:textColor="@color/white" android:textSize="16sp" /> </RelativeLayout> <!--自定義歌詞--> <com.justforme.mobileplayer.ui.view.LyricView android:id="@+id/lyric_view" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="10dp"> <!--歌曲進度條和歌曲時間--> <TextView android:id="@+id/tv_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="right" android:textColor="@color/white" android:textSize="16sp" /> <SeekBar android:id="@+id/sb_audio" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:progressDrawable="@drawable/audio_progress_drawable" android:thumb="@mipmap/audio_seek_thumb" android:thumbOffset="0dp" /> <!--各種播放邏輯的控件佈局--> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_marginTop="5dp" android:gravity="center" android:orientation="horizontal"> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> <ImageView android:id="@+id/iv_play_mode" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerInParent="true" android:background="@drawable/selector_btn_mode_order" /> </RelativeLayout> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> <ImageView android:id="@+id/iv_pre" android:layout_width="45dp" android:layout_height="45dp" android:layout_centerInParent="true" android:background="@drawable/selector_btn_audio_pre" /> </RelativeLayout> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> <ImageView android:id="@+id/iv_play" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerInParent="true" android:background="@drawable/selector_btn_audio_pause" /> </RelativeLayout> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> <ImageView android:id="@+id/iv_next" android:layout_width="45dp" android:layout_height="45dp" android:layout_centerInParent="true" android:background="@drawable/selector_btn_audio_next" /> </RelativeLayout> <RelativeLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1"> <ImageView android:layout_width="40dp" android:layout_height="40dp" android:layout_centerInParent="true" android:background="@drawable/selector_btn_lyric" /> </RelativeLayout> </LinearLayout> </LinearLayout> </LinearLayout>
音頻跳動的動畫效果 (準備9張圖片)
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@mipmap/now_playing_matrix_01" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_02" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_03" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_04" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_05" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_06" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_07" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_08" android:duration="200" /> <item android:drawable="@mipmap/now_playing_matrix_09" android:duration="200" /> </animation-list>
//播放頁面控件加載完成後,加載音頻跳動動畫 AnimationDrawable animationDrawable = (AnimationDrawable) ivAnim.getBackground(); animationDrawable.start();
Activity與Service交互
可以再看下上面的結構圖,播放音樂等相關功能是在服務中實現的,通過服務的中間人對象,實現交互。
下面這些方法是寫在服務中間人對象(AudioServiceBinder)中的。
播放音樂
/** * 播放音樂 */ public void playAudio() { if (mediaPlayer != null) { mediaPlayer.release(); mediaPlayer = null; } mediaPlayer = new MediaPlayer(); AudioItem audioItem = audioList.get(currentPosition); try { mediaPlayer.setDataSource(AudioPlayerService.this, Uri.parse(audioItem.getPath())); mediaPlayer.setOnPreparedListener(onPreparedListener); mediaPlayer.setOnCompletionListener(onCompletionListener); mediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } }
判斷音樂是否正在播放
public boolean isPlaying() { return mediaPlayer != null && mediaPlayer.isPlaying(); }
暫停
public void pause() { if (mediaPlayer != null) { mediaPlayer.pause(); } }
播放
public void start() { if (mediaPlayer != null) { mediaPlayer.start(); } }
獲取歌曲總時間
public long getDuration() { return mediaPlayer == null ? 0 : mediaPlayer.getDuration(); }
獲取歌曲當前的時間
public long getCurrentPosition() { return mediaPlayer == null ? 0 : mediaPlayer.getCurrentPosition(); }
拖動歌曲到指定的時間
public void seekTo(int progress) { if (mediaPlayer != null) { mediaPlayer.seekTo(progress); } }
播放上一首
public void playPre() { if (currentPosition > 0) { currentPosition--; playAudio(); } }
播放下一首
public void playNext() { if (currentPosition < audioList.size() - 1) { currentPosition++; playAudio(); } }
獲取當前正在播放的音樂的position
public int getCurrentPlayingIndex() { return currentPosition; }
獲取音樂集合的大小
public int getAudioListSize() { return audioList.size(); }
切換播放模式
public void switchPlayMode() { switch (playMode) { case MODE_ORDER://順序播放 --> 單曲循環 playMode = MODE_SINGLE_REPEAT; break; case MODE_SINGLE_REPEAT://單曲循環 --> 循環播放 playMode = MODE_ALL_REPEAT; break; case MODE_ALL_REPEAT://循環播放 --> 順序播放 playMode = MODE_ORDER; break; } savePlayMode(); }
保存播放模式
private void savePlayMode() { SharedPreferenceUtils.putInt(AudioPlayerService.this, "playMode", playMode); }
獲取播放模式(讓外界獲取)
public int getPlayMode() { return playMode; }
從SharedPreferences中獲取播放模式
private int getPlayModeFromSp() { return SharedPreferenceUtils.getInt(AudioPlayerService.this, "playMode", MODE_ORDER); }
歌曲準備完成的監聽器
private OnPreparedListener onPreparedListener = new OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { mediaPlayer.start(); notifyPrepared(); } }; /** * 音樂準備完成時,發送廣播將數據傳給外界 */ private void notifyPrepared() { Intent intent = new Intent(ACTION_PREPARED); intent.putExtra("audioItem", audioList.get(currentPosition)); sendBroadcast(intent); }
歌曲播放完成的監聽器
private OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { @Override public void onCompletion(MediaPlayer mp) { notifyCompletion(); autoPlayByPlayMode(); } }; private void notifyCompletion() { Intent intent = new Intent(ACTION_COMPLETION); intent.putExtra("audioItem", audioList.get(currentPosition)); sendBroadcast(intent); }
根據播放模式自動播放
private void autoPlayByPlayMode() { switch (playMode) { case MODE_ORDER: serviceBinder.playNext(); break; case MODE_SINGLE_REPEAT: serviceBinder.playAudio(); break; case MODE_ALL_REPEAT: if (currentPosition == audioList.size() - 1) { currentPosition = 0; serviceBinder.playAudio(); } else { serviceBinder.playNext(); } break; } }
至此,service中的方法基本上寫好了。下面音樂播放的activity直接調用這些方法就可以了。
養成好習慣,服務銷燬的時候,釋放資源。
@Override
public void onDestroy() {
super.onDestroy();
mediaPlayer.release();
mediaPlayer = null;
}
AudioPlayerActivity的調用代碼
初始化數據
@Override protected void initData() { registerAudioStateReceiver(); Bundle bundle = new Bundle(); //將音樂列表傳遞過來的數據(所有音樂的集合和當期音樂的position)傳遞給service bundle = getIntent().getExtras(); Intent serviceIntent = new Intent(this, AudioPlayerService.class); serviceIntent.putExtras(bundle); bindService(serviceIntent, new AudioPlayerServiceConn(), Service.BIND_AUTO_CREATE);//爲了拿到serviceBinder對象 startService(serviceIntent);//爲了傳遞數據 }
/** * 註冊一個音樂狀態的廣播接受者 */ private void registerAudioStateReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(AudioPlayerService.ACTION_PREPARED); filter.addAction(AudioPlayerService.ACTION_COMPLETION); audioServiceReceiver = new AudioServiceReceiver(); registerReceiver(audioServiceReceiver, filter); } private class AudioServiceReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(AudioPlayerService.ACTION_PREPARED)) { //音樂準備完成 AudioItem audioItem = (AudioItem) intent.getSerializableExtra("audioItem"); tvTitle.setText(StringUtil.formatAudioName(audioItem.getTitle())); tvArtist.setText(audioItem.getArtist()); tvTime.setText("00:00/" + StringUtil.formatVideoDuration(audioItem.getDuration())); sbAudio.setMax((int) audioItem.getDuration()); updatePlayProgress(); updatePlayModeBtnBg(false); } else if (action.equals(AudioPlayerService.ACTION_COMPLETION)) { //音樂播放完成 handler.removeMessages(UPDATE_PROGRESS); AudioItem audioItem = (AudioItem) intent.getSerializableExtra("audioItem"); tvTime.setText(StringUtil.formatVideoDuration(audioItem.getDuration()) + "/" + StringUtil.formatVideoDuration(audioItem.getDuration())); sbAudio.setProgress((int) audioItem.getDuration()); ivPlay.setBackgroundResource(R.drawable.selector_btn_audio_play); } } }
/** * 更新播放進度 */ private void updatePlayProgress() { long currentProgress = serviceBinder.getCurrentPosition(); tvTime.setText(StringUtil.formatVideoDuration(currentProgress) + "/" + StringUtil.formatVideoDuration(serviceBinder.getDuration())); sbAudio.setProgress((int) currentProgress); handler.sendEmptyMessageDelayed(UPDATE_PROGRESS, 1000); }
控件的點擊事件
@Override protected void processClick(View view) { switch (view.getId()) { case R.id.iv_back: finish(); break; case R.id.iv_play_mode: serviceBinder.switchPlayMode(); updatePlayModeBtnBg(true); break; case R.id.iv_pre: if (serviceBinder.getCurrentPlayingIndex() != 0) { serviceBinder.playPre(); } else { ToastUtil.showShort(this, "已經是第一首了"); } break; case R.id.iv_play: if (serviceBinder.isPlaying()) { serviceBinder.pause(); handler.removeMessages(UPDATE_PROGRESS); handler.removeMessages(UPDATE_LYRIC); } else { serviceBinder.start(); handler.sendEmptyMessage(UPDATE_PROGRESS); handler.sendEmptyMessage(UPDATE_LYRIC); } updatePlayBtnBg(); break; case R.id.iv_next: if (serviceBinder.getCurrentPlayingIndex() != serviceBinder.getAudioListSize() - 1) { serviceBinder.playNext(); } else { ToastUtil.showShort(this, "已經是最後一首了"); } break; } }
/**更新播放模式按鈕的背景 * @param isShowTip 是否顯示建議(切換時的toast提醒) */ private void updatePlayModeBtnBg(boolean isShowTip) { int playMode = serviceBinder.getPlayMode(); switch (playMode) { case AudioPlayerService.MODE_ORDER: if (isShowTip) ToastUtil.showShort(this, "順序播放"); ivPlayMode.setBackgroundResource(R.drawable.selector_btn_mode_order); break; case AudioPlayerService.MODE_SINGLE_REPEAT: if (isShowTip) ToastUtil.showShort(this, "單曲循環"); ivPlayMode.setBackgroundResource(R.drawable.selector_btn_mode_single_repeat); break; case AudioPlayerService.MODE_ALL_REPEAT: if (isShowTip) ToastUtil.showShort(this, "循環播放"); ivPlayMode.setBackgroundResource(R.drawable.selector_btn_mode_all_repeat); break; } }
/** * 更新播放按鈕的背景 */ private void updatePlayBtnBg() { ivPlay.setBackgroundResource(serviceBinder.isPlaying() ? R.drawable.selector_btn_audio_pause : R.drawable.selector_btn_audio_play); }
基本功能完成了,記住在activity銷燬的時候,移除一些消息和廣播。
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(audioServiceReceiver);
handler.removeCallbacksAndMessages(null);
}
好了,先整理到這裏。下一篇將整理音樂發送系統通知欄的相關知識,以及滾動歌詞的繪製