android音乐播放器(mvp架构)

1.最终效果

在这里插入图片描述
功能:播放、暂停、停止、可拖动进度条等。
个人建议有一定基础看本篇博客。
为了条理清晰易于学习,代码只针对最主要的功能,可自行进行扩展。
没有对代码进行过多的阐述,可以先将功能实现之后,再根据代码梳理思路。

2.思路及流程

  1. 分析确定需求
  2. 定义接口,主要包括处理业务逻辑的接口和更新ui的接口
  3. 编写对应的布局文件
  4. 创建服务,ui层方法调用,ui的控制
  5. 逻辑层接口实现,播放器播放逻辑
  6. 进度条编写

3.总的目录结构

在这里插入图片描述

4.定义接口

4.1播放器控制接口

我们的view层持有IPlayerControlPresenter接口,可以调用其方法来进行业务逻辑的处理,面向接口编程可以使系统获得良好的扩展性。

public interface IPlayerControlPresenter {

    /**
     * 注册回调接口
     * @param iPlayerViewControlCallback
     */
    void registerCallback(IPlayerViewControlCallback iPlayerViewControlCallback);

    /**
     * 取消注册回调接口
     */
    void unRegisterCallback();

    /**
     * 播放
     * @param assets
     */
    void playOrPause(AssetManager assets);

    /**
     * 停止播放
     */
    void stop();

    /**
     * 设置进度条
     * @param seek
     */
    void setSeek(int seek);
}

4.2ui更新回调接口

可以让view实现这个回调接口,或者是new一个匿名内部类。

public interface IPlayerViewControlCallback {
    /**
     * 播放进度改变
     * @param state
     */
    void playStateChange(int state);

    /**
     * 进度条改变
     * @param seek
     */
    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架构确实是很适合学习的架构。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值