1.最终效果
功能:播放、暂停、停止、可拖动进度条等。 个人建议有一定基础看本篇博客。 为了条理清晰易于学习,代码只针对最主要的功能,可自行进行扩展。 没有对代码进行过多的阐述,可以先将功能实现之后,再根据代码梳理思路。
2.思路及流程
- 分析确定需求
- 定义接口,主要包括处理业务逻辑的接口和更新ui的接口
- 编写对应的布局文件
- 创建服务,ui层方法调用,ui的控制
- 逻辑层接口实现,播放器播放逻辑
- 进度条编写
3.总的目录结构
4.定义接口
4.1播放器控制接口
我们的view层持有IPlayerControlPresenter接口,可以调用其方法来进行业务逻辑的处理,面向接口编程可以使系统获得良好的扩展性。
public interface IPlayerControlPresenter {
void registerCallback(IPlayerViewControlCallback iPlayerViewControlCallback);
void unRegisterCallback();
void playOrPause(AssetManager assets);
void stop();
void setSeek(int seek);
}
4.2ui更新回调接口
可以让view实现这个回调接口,或者是new一个匿名内部类。
public interface IPlayerViewControlCallback {
void playStateChange(int state);
void seekChange(int seek);
}
5.编写页面布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:id="@+id/seek_bar"/>
<LinearLayout
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="播放"
android:layout_margin="10dp"
android:id="@+id/play"/>
<Button
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:text="关闭"
android:id="@+id/close"/>
</LinearLayout>
</LinearLayout>
6.创建一个service文件
onBind方法会在服务绑定的时候返回一个binder,我们可以让PlayerControlPresenterImpl类继承自Binder类并且实现IPlayerControlPresenter接口。
public class PlayerService extends Service {
public PlayerService() {
}
private PlayerControlPresenterImpl playerControlPresenter;
@Override
public void onCreate() {
super.onCreate();
if (playerControlPresenter == null) {
playerControlPresenter = new PlayerControlPresenterImpl();
}
}
@Override
public IBinder onBind(Intent intent) {
return playerControlPresenter;
}
@Override
public void onDestroy() {
super.onDestroy();
playerControlPresenter = null;
}
}
7.view层activity
服务绑定成功并且返回的binder不为空的时候,onServiceConnected方法会被调用。iPlayerViewControlCallback 即是接口IPlayerViewControlCallback的实现。初始化对应的监听事件。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private SeekBar seekBar;
private Button playOrPause,close;
private IPlayerControlPresenter iPlayerControlPresenter;
private boolean isUserHandTouch = false;
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG,"service connect success");
iPlayerControlPresenter = (IPlayerControlPresenter) service;
iPlayerControlPresenter.registerCallback(iPlayerViewControlCallback);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iPlayerControlPresenter = null;
}
};
private IPlayerViewControlCallback iPlayerViewControlCallback = new IPlayerViewControlCallback() {
@Override
public void playStateChange(int state) {
switch (state) {
case State.PLAYER_STATE_PLAY:
playOrPause.setText("暂停");
break;
case State.PLAYER_STATE_PAUSE:
case State.PLAYER_STATE_STOP:
playOrPause.setText("播放");
break;
}
}
@Override
public void seekChange(int seek) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if(!isUserHandTouch) {
seekBar.setProgress(seek);
}
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initService();
initEvent();
}
private void initView() {
seekBar = findViewById(R.id.seek_bar);
playOrPause = findViewById(R.id.play);
close = findViewById(R.id.close);
}
private void initService() {
Log.d(TAG,"initService方法被调用");
Intent intent = new Intent(this, PlayerService.class);
startService(intent);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
private void initEvent() {
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
isUserHandTouch = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
isUserHandTouch = false;
int progress = seekBar.getProgress();
Log.d(TAG,"当前的进度条数值为:" + progress);
if(iPlayerControlPresenter != null && !isUserHandTouch) {
iPlayerControlPresenter.setSeek(progress);
}
}
});
playOrPause.setOnClickListener(v -> {
if(iPlayerControlPresenter != null) {
AssetManager assets = getAssets();
iPlayerControlPresenter.playOrPause(assets);
}
});
close.setOnClickListener(v -> {
if(iPlayerControlPresenter != null) {
iPlayerControlPresenter.stop();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (iPlayerControlPresenter != null) {
iPlayerControlPresenter.unRegisterCallback();
unbindService(serviceConnection);
}
}
}
8.导入歌曲
首先创建assets目录,必须创建在app/src/main这个目录下面,也就是和java、res 这两个目录是平级的。右击app/src/main——>New——>Directory,在弹出的对话框中输入"assets",目录创建完成。歌曲下载推荐在 http://tool.liumingye.cn/music/?page=homePage ,特别好用。
9.定义播放状态
public interface State {
int PLAYER_STATE_PLAY = 1;
int PLAYER_STATE_PAUSE = 2;
int PLAYER_STATE_STOP = 3;
}
10.编写逻辑具体实现
推荐阅读代码,或者先将功能实现再阅读。
public class PlayerControlPresenterImpl extends Binder implements IPlayerControlPresenter {
private static final String TAG = "PlayerControlPresenter";
private IPlayerViewControlCallback iPlayerViewControlCallback;
private int currentState = State.PLAYER_STATE_STOP;
private MediaPlayer mediaPlayer;
private Timer timer;
private SeekTimeTask timeTask;
@Override
public void registerCallback(IPlayerViewControlCallback iPlayerViewControlCallback) {
this.iPlayerViewControlCallback = iPlayerViewControlCallback;
}
@Override
public void unRegisterCallback() {
this.iPlayerViewControlCallback = null;
}
@Override
public void playOrPause(AssetManager assets) {
if(currentState == State.PLAYER_STATE_STOP) {
initPlayer(assets);
mediaPlayer.start();
currentState = State.PLAYER_STATE_PLAY;
startTimer();
}else if(currentState == State.PLAYER_STATE_PLAY) {
mediaPlayer.pause();
currentState = State.PLAYER_STATE_PAUSE;
stopTimer();
}else if(currentState == State.PLAYER_STATE_PAUSE) {
mediaPlayer.start();
currentState = State.PLAYER_STATE_PLAY;
startTimer();
}
iPlayerViewControlCallback.playStateChange(currentState);
}
private void initPlayer(AssetManager assets) {
mediaPlayer = new MediaPlayer();
try {
AssetFileDescriptor fd = assets.openFd(SongNames.SUNNY_DAY);
mediaPlayer.setDataSource(fd.getFileDescriptor(),fd.getStartOffset(),fd.getLength());
mediaPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void stop() {
if (mediaPlayer != null) {
currentState = State.PLAYER_STATE_STOP;
mediaPlayer.stop();
mediaPlayer.release();
iPlayerViewControlCallback.playStateChange(currentState);
iPlayerViewControlCallback.seekChange(0);
stopTimer();
}
}
@Override
public void setSeek(int seek) {
Log.d(TAG,"进度条数值为:" + seek);
if (mediaPlayer != null) {
int targetSeek = (int) (seek * 1f / 100 * mediaPlayer.getDuration());
mediaPlayer.seekTo(targetSeek);
}else if(currentState == State.PLAYER_STATE_STOP){
iPlayerViewControlCallback.seekChange(0);
}
}
private void startTimer() {
if(timer == null) {
timer = new Timer();
}
if(timeTask == null) {
timeTask = new SeekTimeTask();
}
timer.schedule(timeTask,0,500);
}
private void stopTimer() {
if(timeTask != null) {
timeTask.cancel();
timeTask = null;
}
if(timer != null) {
timer.cancel();
timer = null;
}
}
private class SeekTimeTask extends TimerTask {
@Override
public void run() {
if (mediaPlayer != null && iPlayerViewControlCallback != null) {
int currentPosition = mediaPlayer.getCurrentPosition();
int duration = mediaPlayer.getDuration();
int nowPosition = (int) (currentPosition * 1f / duration * 100);
iPlayerViewControlCallback.seekChange(nowPosition);
}
}
}
}
总结 使用mvp架构可以很好地解耦,并且代码十分清晰,虽说现在普遍使用mvvm架构,但是mvp架构确实是很适合学习的架构。
|