使用Broadcast实现音乐盒(包含进度条)
一、设计目标以及实现内容展示
设计目标
- “音乐盒”界面能显示歌曲封面、歌曲名、作者、播放进度、当前时间、歌曲总时间
- “音乐盒”能实现上一首、下一首、暂停、播放、停止,并且封面随之改变
- “音乐盒”中拖动进度条可以对应的改变歌曲的播放进度
- “音乐盒”页面布局清晰,运行流畅
实现内容展示
二、详细思路以及实现
布局文件的设计
- “音乐盒”页面由基本信息界面、控制界面两个界面组成,分别对应两个LinearLayout
- 基本信息界面包含一个ImageView、两个LinearLayout
- 基本信息界面中的ImageView是歌曲封面
- 基本信息界面中的第一个LinearLayout中有两个TextView分别对应歌曲名、作者
- 基本信息界面中的第二个LinearLayout中包含两个TextView和一个SeeBar分别对应当前时间、歌曲总时间、进度条
- 控制界面包含四个ImageButton分别对应上一首、播放(暂停)、停止、下一首
基本信息界面:歌曲名、作者
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffffff"
android:text="Title"
android:textSize="20dp" />
<TextView
android:id="@+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#ffffff"
android:text="Author"
android:textSize="20dp" />
</LinearLayout>
基本信息界面:当前时间、进度条、歌曲总时间
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/curTime"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#ffffff"
android:text="00:00" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="310dp"
android:layout_height="wrap_content"
android:background="#ffffff"
android:scrollbarSize="20sp" />
<TextView
android:id="@+id/endTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ffffff"
android:text="00:00" />
</LinearLayout>
控制界面
<LinearLayout
android:layout_width="match_parent"
android:layout_height="45dp"
android:orientation="horizontal">
<ImageButton
android:id="@+id/ptrack"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffffff"
app:srcCompat="@drawable/ptract" />
<ImageButton
android:id="@+id/play"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffffff"
app:srcCompat="@drawable/play" />
<ImageButton
android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffffff"
app:srcCompat="@drawable/stop" />
<ImageButton
android:id="@+id/ntrack"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ffffff"
app:srcCompat="@drawable/ntract" />
</LinearLayout>
事件控制的设计
控制流程:
①点击按钮 --> ②设置intent,广播intent并将被MusicService中的MyReceiver 接收到 --> ③根据广播的intent进行音乐的播放、暂停、切换以及对应进度条的控制 --> ④设置intent,广播intent并将被MusicboxFragement中的MyReceiver接收到 --> ⑤MusicboxFragement更改播放(暂停)图标、歌曲封面、歌曲名、作者名
若在歌曲播放的过程中,点击按钮则执行上述循环,否则歌曲播放完成后自动播放下一首并执行上述循环(无①点击按钮)
重要代码:
点击事件
public void onClick(View view) {
// 创建 Intent
Intent intent = new Intent("org.mywechat.action.CTL_ACTION");
switch (view.getId()) {
//按下播放/暂停按钮
case R.id.play:
intent.putExtra("control", 1);
break;
// 按下 停止 按钮
case R.id.stop:
intent.putExtra("control", 2);
break;
// 按下 上一首
case R.id.ptrack:
intent.putExtra("control", 3);
break;
// 按下 下一首
case R.id.ntrack:
intent.putExtra("control", 4);
break;
}
// 发送广播,将被Service 组件 中的BroadcastReceiver 接收到
getActivity().sendBroadcast(intent);
}
MusicFragement中的MyReceiver
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 获取 Intent 中的update消息,update 代表播放状态
int update = intent.getIntExtra("update", -1);
// 获取 Intent 中的current消息,current代表当前正在播放的歌曲
int current = intent.getIntExtra("current", -1);
if (current >= 0) {
title.setText(titleStrs[current]);
author.setText(authorStrs[current]);
imag.setImageResource(imagStrs[current]);
}
switch (update) {
//没有 播放状态
case 0x11:
play.setImageResource(R.drawable.play);
status = 0x11;
title.setText("");
author.setText("");
imag.setImageResource(R.drawable.music);
break;
//控制系统进入 播放状态
case 0x12:
//播放状态下设置使用暂停图标
play.setImageResource(R.drawable.pause);
//设置当前状态
status = 0x12;
break;
//控制系统进入暂停状态
case 0x13:
//暂停状态下设置使用播放图标
play.setImageResource(R.drawable.play);
//设置当前状态
status = 0x13;
break;
}
}
}
MusicService中的MyReceiver
public void onReceive(Context context, Intent intent) {
int control = intent.getIntExtra("control", -1);
switch (control) {
//播放或暂停
case 1:
//原来处于没有播放状态
if (status == 0x11) {
// 准备 并播放 音乐
prepareAndPlay(musics[current]);
status = 0x12;
}
//原来处于播放状态
else if (status == 0x12) {
//暂停
mediaPlayer.pause();
//改为暂停状态
status = 0x13;
}
// 原来 处于 暂停状态
else if (status == 0x13) {
//播放
mediaPlayer.start();
// 改变状态
status = 0x12;
}
break;
//停止 播放
case 2:
//如果原来正在播放或暂停
if (status == 0x12 || status == 0x13) {
// 停止播放
mediaPlayer.pause();
mediaPlayer.seekTo(0);
status = 0x11;
}
break;
// 上一首
case 3:
if (status == 0x12 || status == 0x13) {
current = ((current - 1) + musics.length) % musics.length;
prepareAndPlay(musics[current]);
status = 0x12;
}
break;
// 下一首
case 4:
if (status == 0x12 || status == 0x13) {
current = (current + 1) % musics.length;
prepareAndPlay(musics[current]);
status = 0x12;
}
break;
}
// 广播通知 MusicboxFragment 更改图标、文本框
Intent sendIntent = new Intent(MusicboxFragment.UPDATE_ACTION);
sendIntent.putExtra("update", status);
sendIntent.putExtra("current", current);
//发送广播,将被MusicboxFragment组件中的BroadcastReceiver接收到
sendBroadcast(sendIntent);
}
}
音乐准备和播放
private void prepareAndPlay(String music) {
int max;
// 打开指定音乐文件
try {
AssetFileDescriptor afd = assetManager.openFd(music);
mediaPlayer.reset();
// 使用MediaPlayer加载指定的声音文件。
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
// 准备声音
mediaPlayer.prepare();
max = mediaPlayer.getDuration();
seekBar.setMax(max);
endTime.setText(MusicService.formatTime(max));
//创建一个进程来控制进度条
handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
seekBar.setProgress(mediaPlayer.getCurrentPosition());
}
};//消息传递
DelayThread delaythread = new DelayThread(100);
delaythread.start();
//播放
mediaPlayer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
MediaPlayer完成事件监听
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
//实现自动播放
current = (current + 1) % musics.length;
//发送广播 通知 MusicboxFragment 更改文本框
Intent sendIntent = new Intent(MusicboxFragment.UPDATE_ACTION);
sendIntent.putExtra("current", current);
// 发送广播将被MusicboxFragment组件中的BroadcastReceiver 接收到
sendBroadcast(sendIntent);
//准备并播放音乐
prepareAndPlay(musics[current]);
}
});
seekBar监听
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
//设置当前时间
curTime.setText(MusicService.formatTime(i));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
//拖动跳转
mediaPlayer.seekTo(seekBar.getProgress());
}
});
控制seekBar的进程
class DelayThread extends Thread {
int milliseconds;
public DelayThread(int i) {
milliseconds = i;
}
public void run() {
while (true) {
try {
sleep(milliseconds);
//设置音乐进度读取频率
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
}
三,总结
本项目的难点在于Service和Fragement之间相互广播以及进度条的设置。Service和Fragement之间相互广播的关键是要弄清楚整个项目的事件控制流程(参考事件“控制流程的设计”)。进度条的设置需要使用Handler,以及多进程,使用Handler让SeekBar获取歌曲的实时位置,然后在开一个进程handler.sendEmptyMessage(0). gitee源代码