前一篇已经将视频模块弄好了,今天主要理一下音乐这一模块,包括:
- 通过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);
}
好了,先整理到这里。下一篇将整理音乐发送系统通知栏的相关知识,以及滚动歌词的绘制