安卓(android)音乐播放器【Android移动开发基础案例教程(第2版)黑马程序员】

一、实验目的

1.掌握服务的创建方式,能够独立创建一个服务。

2.掌握服务的两种启动方式,能够实现服务的启动与关闭功能。

3.掌握服务的通信,能够完成仿网易音乐播放器项目。

二、实验条件

掌握Android中多媒体应用开发的基本方法,能够使用MediaPlayer类实现音频文件的播放控制。熟悉Android界面布局和事件处理机制。

三、实验内容

1.创建项目,导入项目资源文件(主要包括:音乐文件、图片资源等)。

2.修改项目程序的主题样式,添加相应的界面控件。

3.创建需要的按钮状态选择器,修改默认标题栏名称。

4.创建音乐播放服务。

5.编写音乐播放界面交互的业务逻辑代码,运行程序并测试效果

四、实验指导

1 .创建项目,导入项目资源文件 (主要包括:音乐文件、图片资源等 )

(1)创建名为MusicPlayer项目,指定项目包名cn.itedus.musicplayer。

(2)在项目的res目录下创建raw文件夹,导入music.mp3文件;在res目录下的drawable-dhpi目录下导入img_music.png、music_bg.png、btn_bg.png、title_bg.png图片资源。

2.修改项目程序的主题样式,添加相应的界面控件

(1)修改程序的主题样式,具体代码实现如下所示:

<style name="Theme.MusicPlayer" parent="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge">

(2)添加界面控件,具体为4个TextView控件用于显示音乐名称、音乐类型、音乐播放进度时间及音乐总时间长度;1个SeekBar显示进度条;1个ImageView控件用于显示界面上的旋转图片;4个Button按钮分别用于显示"播放"按钮、"暂停"按钮、"继续"按钮、"退出"按钮。

3 .创建需要的按钮状态选择器,修改默认标题栏名称

(1)在drawable目录上右键"New→Drawable resource file",创建背景选择器btn_bg_selector.xml,根据按下和谈起的状态需求定义选择器,具体代码实现如下:

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <corners android:radius="3dp"/>
            <solid android:color="#d4d4d4"/>
        </shape>
    </item>
    <item android:state_pressed="false">
        <shape android:shape="rectangle">
            <corners android:radius="3dp"/>
            <solid android:color="#ffffff" />
        </shape>
    </item>
</selector>

(2)修改默认标题栏名称,修改res→values→strings.xml中name值为app_name的标签,将对应的值修改为"Musicplayer",具体实现代码如下:

XML 复制代码
<resources>
    <string name="app_name">Musicplayer</string>
</resources>

(3)在drawable也创建ic_launcher_background.xml

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <path
        android:fillColor="#3DDC84"
        android:pathData="M0,0h108v108h-108z" />
    <path
        android:fillColor="#00000000"
        android:pathData="M9,0L9,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,0L19,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,0L29,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,0L39,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,0L49,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,0L59,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,0L69,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,0L79,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M89,0L89,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M99,0L99,108"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,9L108,9"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,19L108,19"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,29L108,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,39L108,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,49L108,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,59L108,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,69L108,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,79L108,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,89L108,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M0,99L108,99"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,29L89,29"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,39L89,39"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,49L89,49"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,59L89,59"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,69L89,69"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M19,79L89,79"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M29,19L29,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M39,19L39,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M49,19L49,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M59,19L59,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M69,19L69,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
    <path
        android:fillColor="#00000000"
        android:pathData="M79,19L79,89"
        android:strokeWidth="0.8"
        android:strokeColor="#33FFFFFF" />
</vector>

(4)在layout下创建activity_main.xml

XML 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/music_bg"
    android:gravity="center"
    android:orientation="vertical">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="160dp">
        <RelativeLayout
            android:id="@+id/rl_title"
            android:layout_width="300dp"
            android:layout_height="70dp"
            android:layout_centerHorizontal="true"
            android:background="@drawable/title_bg"
            android:gravity="center_horizontal"
            android:paddingLeft="80dp">
            <TextView
                android:id="@+id/tv_music_title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="体面"
                android:textSize="12sp"
                android:textStyle="bold"
                android:textColor="@android:color/black"/>
            <TextView
                android:layout_marginTop="4dp"
                android:id="@+id/tv_type"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_music_title"
                android:layout_alignLeft="@id/tv_music_title"
                android:text="流行音乐"
                android:textSize="10sp"/>
            <SeekBar
                android:id="@+id/sb"
                android:layout_width="150dp"
                android:layout_height="wrap_content"
                android:layout_below="@id/rl_time"
                android:layout_alignParentBottom="true"
                android:thumb="@null"/>
            <RelativeLayout
                android:layout_marginTop="4dp"
                android:id="@+id/rl_time"
                android:layout_width="150dp"
                android:layout_height="wrap_content"
                android:layout_below="@id/tv_type">
                <TextView
                    android:id="@+id/tv_progress"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="00:00"
                    android:textSize="10sp"/>
                <TextView
                    android:id="@+id/tv_total"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentRight="true"
                    android:text="00:00"
                    android:textSize="10sp"/>
            </RelativeLayout>
        </RelativeLayout>
        <LinearLayout
            android:layout_width="340dp"
            android:layout_height="90dp"
            android:layout_below="@id/rl_title"
            android:layout_centerHorizontal="true"
            android:background="@drawable/btn_bg"
            android:gravity="center_vertical"
            android:paddingLeft="120dp"
            android:paddingRight="10dp">
            <Button
                android:id="@+id/btn_play"
                android:layout_width="0dp"
                android:layout_height="25dp"
                android:layout_margin="4dp"
                android:layout_weight="1"
                android:background="@drawable/btn_bg_selector"
                android:text="播放"
                android:padding="0dp"
                android:textSize="10sp"/>
            <Button
                android:id="@+id/btn_pause"
                android:layout_width="0dp"
                android:layout_height="25dp"
                android:layout_margin="4dp"
                android:padding="0dp"
                android:layout_weight="1"

                android:background="@drawable/btn_bg_selector"
                android:text="暂停"
                android:textSize="10sp"/>
            <Button
                android:id="@+id/btn_continue_play"
                android:layout_width="0dp"
                android:layout_height="25dp"
                android:layout_margin="4dp"
                android:padding="0dp"
                android:layout_weight="1"
                android:background="@drawable/btn_bg_selector"
                android:text="继续"
                android:textSize="10sp"/>
            <Button
                android:id="@+id/btn_exit"
                android:layout_width="0dp"
                android:layout_height="25dp"
                android:layout_margin="4dp"
                android:layout_weight="1"
                android:padding="0dp"
                android:background="@drawable/btn_bg_selector"
                android:text="退出"
                android:textSize="10sp"/>
        </LinearLayout>
        <ImageView
            android:id="@+id/iv_music"
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:layout_centerVertical="true"
            android:layout_marginLeft="35dp"
            android:layout_marginBottom="50dp"
            android:src="@drawable/img_music"/>

    </RelativeLayout>
</LinearLayout>

4 .创建音乐播放服务

在项目包名上右键"New→Service→Service",创建MusicService服务,分别实现该服务中的addTimer()方法实现进度条播放的更新,同时创建MusicControl类,其中包含play()、pausePlay()、continuePlay()、seekTo()等方法实现播放、暂停播放、继续播放及拖放进度条等功能,具体实现代码如下:

java 复制代码
package cn.itcast.musicplayer;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;

import java.util.Timer;
import java.util.TimerTask;

public class MusicService extends Service {
    private MediaPlayer player;
    private Timer timer;

    public MusicService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new MusicControl();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        player = new MediaPlayer();  //创建音乐播放器对象
    }

    public void addTimer() {  //添加计时器用于设置音乐播放器中的播放进度条
        if (timer == null) {
            timer = new Timer();     //创建计时器对象
            TimerTask task = new TimerTask() {
                @Override
                public void run() {
                    if (player == null) return;
                    int duration = player.getDuration();//获取歌曲总时长
                    int currentPosition = player.getCurrentPosition();//获取播放进度
                    Message msg = MainActivity.handler.obtainMessage();//创建消息对象
                    Bundle bundle = new Bundle();
                    bundle.putInt("duration", duration);
                    bundle.putInt("currentPosition", currentPosition);
                    msg.setData(bundle);
                    //将消息发送到主线程的消息队列
                    MainActivity.handler.sendMessage(msg);
                }
            };
//开始计时任务后的5毫秒,第一次执行task任务,以后每500毫秒执行一次
            timer.schedule(task, 5, 500);
        }
    }

    class MusicControl extends Binder {
        public void play() {
            try {
                player.reset();//重置音乐播放器
                player = MediaPlayer.create(getApplicationContext(), R.raw.music);
                player.start();//播放音乐
                addTimer();     //添加计时器
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        public void pausePlay() {
            player.pause();//暂停播放音乐
        }

        public void continuePlay() {
            player.start();//继续播放音乐
        }

        public void seekTo(int progress) {
            player.seekTo(progress);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (player == null) return;
        if (player.isPlaying()) player.stop();//停止播放音乐
        player.release();                         //释放占用的资源
        player = null;                            //将player置为空
    }
}

5 .编写音乐播放界面交互的业务逻辑代码,运行程序并测试效果

要实现音乐播放器的播放、暂停、继续播放和关闭当前界面功能,需要在MainActivity中实现四个按钮的点击事件,具体的实现步骤包括:①初始化界面控件;②显示音乐总时长;③创建连接MusicService服务的类MyServiceConn; ④解绑MusicService服务;⑤实现界面控件的点击事件;⑥当音乐播放器关闭界面时解绑服务。具体代码实现如下所示:

java 复制代码
package cn.itcast.musicplayer;

import android.animation.ObjectAnimator;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static SeekBar sb;
    private static TextView tv_progress, tv_total;
    private ObjectAnimator animator;
    private MusicService.MusicControl musicControl;
    MyServiceConn conn;
    Intent intent;
    private boolean isUnbind = false;//记录服务是否被解绑
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init(); }
    private void init() {
        tv_progress = findViewById(R.id.tv_progress);
        tv_total = findViewById(R.id.tv_total);
        sb = findViewById(R.id.sb);
        intent = new Intent(this, MusicService.class);//创建意图对象
        conn = new MyServiceConn();//创建服务连接对象(后续创建MyServiceConn类)
        bindService(intent, conn, BIND_AUTO_CREATE);  //绑定服务
        sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override //为滑动条添加事件监听
            public void onProgressChanged(SeekBar seekBar, int progress, boolean
                    fromUser) { //滑动条进度改变时,会调用此方法
                if (progress == seekBar.getMax()){//当滑动条滑到末端时,结束动画
                    animator.pause();}//停止播放动画
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {  }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                int progress = seekBar.getProgress();
                musicControl.seekTo(progress);
            }
        });
        ImageView iv_music = findViewById(R.id.iv_music);
        animator = ObjectAnimator.ofFloat(iv_music, "rotation", 0f, 360.0f);
        animator.setDuration(10000);  //动画旋转一周的时间为10秒
        animator.setInterpolator(new LinearInterpolator());
        animator.setRepeatCount(-1);  //-1表示设置动画无限循环
        findViewById(R.id.btn_play).setOnClickListener(this);
        findViewById(R.id.btn_pause).setOnClickListener(this);
        findViewById(R.id.btn_continue_play).setOnClickListener(this);
        findViewById(R.id.btn_exit).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_play:                //"播放"按钮点击事件
                musicControl.play();           //播放音乐
                animator.start();               //播放动画
                break;
            case R.id.btn_pause:               //"暂停"按钮点击事件
                musicControl.pausePlay();     //暂停播放音乐
                animator.pause();              //暂停播放动画
                break;
            case R.id.btn_continue_play:     //"继续"按钮点击事件
                musicControl.continuePlay(); //继续播放音乐
                animator.start();              //播放动画
                break;
            case R.id.btn_exit:                //"退出"按钮点击事件
                unbind(isUnbind);               //解绑服务绑定
                isUnbind = true;                //完成解绑服务
                finish();                         //关闭音乐播放界面
                break;
        }
    }
    public static Handler handler = new Handler() {//创建消息处理器对象
        @Override
        public void handleMessage(Message msg) {
            Bundle bundle = msg.getData(); //获取从子线程发送过来的音乐播放进度
            int duration = bundle.getInt("duration"); //音乐的总时长
            int currentPostition = bundle.getInt("currentPosition");//当前进度
            sb.setMax(duration); //设置SeekBar的最大值为音乐总时长
            sb.setProgress(currentPostition);//设置SeekBar当前的进度位置
            //音乐的总时长
            int minute = duration / 1000 / 60;
            int second = duration / 1000 % 60;
            String strMinute = null;
            String strSecond = null;
            if (minute < 10) {              //如果音乐的时间中的分钟小于10
                strMinute = "0" + minute; //在分钟的前面加一个0
            } else {strMinute = minute + "";}
            if (second < 10) { //如果音乐的时间中的秒钟小于10
                strSecond = "0" + second;//在秒钟前面加一个0
            } else {strSecond = second + "";}
            tv_total.setText(strMinute + ":" + strSecond);
            //音乐当前播放时长
            minute = currentPostition / 1000 / 60;
            second = currentPostition / 1000 % 60;
            if (minute < 10) {             //如果音乐的时间中的分钟小于10
                strMinute = "0" + minute;//在分钟的前面加一个0
            } else {strMinute = minute + "";}
            if (second < 10) {//如果音乐的时间中的秒钟小于10
                strSecond = "0" + second;  //在秒钟前面加一个0
            } else {strSecond = second + "";}
            tv_progress.setText(strMinute + ":" + strSecond);
        }
    };
    class MyServiceConn implements ServiceConnection { //用于实现连接服务
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            musicControl = (MusicService.MusicControl) service; }
        @Override
        public void onServiceDisconnected(ComponentName name) {}
    }
    private void unbind(boolean isUnbind){
        if(!isUnbind){                  //判断服务是否被解绑
            musicControl.pausePlay();//暂停播放音乐
            unbindService(conn);      //解绑服务
            stopService(intent);      //停止服务
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbind(isUnbind); //解绑服务
    }
}

五、代码下载地址(如果代码有错漏,可查看源码):

android: 实现注册界面、实现注册界面、饭堂小广播、音乐播放器、记事本、读取手机通讯录、学生管理系统 - Gitee.com

相关推荐
花开月满西楼6 分钟前
保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
android·前端·android studio
这儿有一堆花9 分钟前
打造你的 Android 图像编辑器:深入解析 PhotoEditor 开源库
android·开源
皮皮高1 小时前
itvbox绿豆影视tvbox手机版影视APP源码分享搭建教程
android·前端·后端·开源·tv
EnzoRay2 小时前
MotionEvent
android
玲小珑2 小时前
Auto.js 入门指南(七)定时任务调度
android·前端
墨狂之逸才3 小时前
adb常用命令调试
android
YoungForYou3 小时前
Android端部署NCNN
android
移动开发者1号3 小时前
Jetpack Compose瀑布流实现方案
android·kotlin
移动开发者1号3 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·kotlin
移动开发者1号3 小时前
ListView与RecyclerView区别总结
android·kotlin