实现音乐播放器功能的具体步骤如下:
创建一个名为MusicPlayer的应用程序,指定包名为cn.itcast.musicplayer。
音频文件一般放在res/raw文件夹【raw文件夹中的文件会被映射到R.java文件中,访问该文件时可直接使用资源ID,即R.id.music(文件名)】中,因此需要在res文件夹中创建一个raw文件夹。首先将Android Studio中的选项卡切换到Project,接着选中程序中的res文件夹,右击选择【New】—>【Directory】选项,创建一个名为raw的文件夹,接着将音乐文件music.mp3(不能为中文)导入到raw文件夹中。
将播放器界面所需的背景图片、音乐图片导入到程序中的drawable文件夹中。
界面实现效果如图
在activity_main.xml布局文件中,放置1个ImageView控件用于显示界面上的旋转图片,1个SeekBar用于显示音乐播放器的进度条,2个TextView分别用于显示音乐播放的进度时间与音乐的总时间,4个Button控件分别用于显示“播放音乐”按钮、“暂停播放”按钮、“继续播放”按钮、“退出”按钮,完整布局代码如下:
<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"
tools:context=".MainActivity"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/music_bg">
<ImageView
android:id="@+id/iv_music"
android:layout_width="240dp"
android:layout_height="240dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="15dp"
android:src="@drawable/music"/>
<SeekBar
android:id="@+id/sb"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp">
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00" />
<TextView
android:id="@+id/tv_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="00:00" />
RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_play"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="@drawable/btn_bg_selector"
android:text="播放音乐"/>
<Button
android:id="@+id/btn_pause"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="@drawable/btn_bg_selector"
android:text="暂停播放"/>
<Button
android:id="@+id/btn_continue_play"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="@drawable/btn_bg_selector"
android:text="继续播放"/>
<Button
android:id="@+id/btn_exit"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_margin="8dp"
android:layout_weight="1"
android:background="@drawable/btn_bg_selector"
android:text="退出"/>
LinearLayout>
LinearLayout>
通过背景选择器实现界面4个按钮背景的四个角是圆角,并且背景在按下与弹起时,背景颜色会有明显区别。选中drawable文件夹,右击选择【New】——>【Drawable resource file】选项,创建一个背景选择器btn_bg_selector.xml,具体代码+注释如下:
<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>
由于音乐的加载、播放、暂停以及播放进度条的更新是一件比较耗时的操作,因此需要创建一个MusicService服务来处理这些操作。首先选择cn.itcast.musicplayer包,右击选择【new】—>【Service】—>【Service】选项,创建名为MusicService的服务。具体代码如下:
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();
}
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);
}
};
//调用Timer对象的schedule()方法执行TimerTask任务
// 该方法有三个参数:1、要执行的任务;2、开始执行计时任务的5毫秒后第一次执行task任务;3、每隔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); //设置音乐的播放位置
}
}
public void onDestroy(){
super.onDestroy();
if(player == null) return;
if(player.isPlaying()) player.stop(); //停止播放音乐
player.release(); //释放占用的资源
player = null; //将player置为空
}
}
上述代码中,addTimer()方法用于每隔500毫秒更新音乐播放器的进度条,在该方法中首先创建一个计时器Timer的对象,接着创建一个TimerTask任务task,在该任务中重写了run()方法创建一个线程,在run()方法中通过getDuration()方法与getCurrentPosition()方法分别获取歌曲的总时长与歌曲当前的播放进度。getDuration()方法与getCurrentPosition()方法是MediaPlayer常用方法,更多Mediaplayer常用方法见:MediaPlayer常用方法介绍
MainActivity实现了音乐文件的播放、暂停播放、继续播放、播放进度的设置、退出音乐播放界面的功能以及实现音乐播放界面4个按钮的点击事件。具体代码如下(带详细注释):
package cn.itcast.musicplayer;
import androidx.appcompat.app.AppCompatActivity;
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;
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 = (TextView) findViewById(R.id.tv_progress);
tv_total = (TextView) findViewById(R.id.tv_total);
sb = (SeekBar) findViewById(R.id.sb);
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);
intent = new Intent(this, MusicService.class);//创建意图对象
conn = new 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(); //获取seekBar的进度
musicControl.seekTo(progress); //改变播放进度
}
});
ImageView iv_music = (ImageView) 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表示设置动画无线循环
}
//创建消息处理器对象
public static Handler handler = new Handler(){
//在主线程中处理从子线程发送过来的消息
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);
}
};
@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;
}
}
private class MyServiceConn implements ServiceConnection { //用于实现连接服务
@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
musicControl = (MusicService.MusicControl) service;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}
private void unbind(Boolean isUnbind){
if(!isUnbind){ //判断服务是否解绑
musicControl.pausePlay(); //暂停播放音乐
unbindService(conn); //解绑服务
stopService(intent); //停止服务
}
}
protected void onDestroy() {
super.onDestroy();
unbind(isUnbind); //解绑服务
}
}
运行上诉程序,分别点击界面上的“播放音乐”按钮、“暂停播放”按钮、“继续播放”按钮,可实现音乐的播放、暂停、继续播放的功能。点击界面上的“退出”按钮,可退出音乐播放器界面。运行结果如图所示:
文件创建地址不明白的可参考下图:
Handler消息处理首先需要在UI线程中创建一个Handler对象,然后在子线程中调用Handler的SendMessage()方法,接着这个消息会存放在UI线程的MessageQueue中,通过Looper对象取出MessageQueue中的消息,最后分发回Handler的HandleMessage()方法中。
谢谢观看!祝学习进步编程开心~希望可以得到你的赞与关注哟