Android Studio 学习(三)

352 阅读4分钟

使用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源代码