IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> android音乐播放器(mvp架构) -> 正文阅读

[移动开发]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架构确实是很适合学习的架构。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-07-03 10:57:45  更:2022-07-03 11:00:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 2:41:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码