一、引言
我在一年前发过一篇用Android实现音乐播放器的教程:Android Studio如何实现音乐播放器(简单易上手)。当时实现的功能也很简单,就是播放音乐、暂停音乐、继续播放、退出播放、显示音乐列表和专辑封面的功能。如下图所示:
期间有很多同学问到我是否可以加上一首下一首功能,确实可以加,只要获取到歌曲文件的下标position即可实现此功能,不难。但是因为写完这个最初版本后就没有当时那种心境再改了,我想大家都会有这种体会。
虽然前段时间很忙,还是沉下心来回顾了自己的音乐播放器项目,也发现了很多不足的地方,然后进行了优化升级,主要有三点:
增加了上一首下一首功能更换了按钮样式,使用更个性化的按钮增加了大多数代码的注释,做到临缺勿滥
所以,这篇博客就是对最初版本(1.0版本)的一个完善,即2.0版本,话不多说,下面开始。
二、项目概述
1、需求分析
综合运用UI界面设计、数据存储、Activity(活动)、Service(服务)、MusicPlayer(音乐播放类)、ListView(列表)等知识,设计开发一款具有音乐列表的音乐播放器。
2、设计分析
整个项目包含五个java文件和五个layout文件,因为是比较简单的项目,所以没有用工程结构去实现它,这里介绍下它们之间的关系。
3、资源文件分析
本项目的所有音乐文件都是存放在本地的,没有用服务器,当然也可以用,参考我的多媒体播放器:Android Studio实现多媒体播放器。
我在res文件夹下创建了raw文件夹,用来放音乐文件,音乐文件的命名注意是从music0开始,很多同学从music1开始命名导致获取不到第一首音乐然后闪退。
在drawable文件夹存放了音乐专辑图片bg.jpg、播放器背景图片music_bg.jpg,歌手圆形图片music0.png、music1.png等,还有按钮图片play.png、pause.png等。
三、开发环境
四、优化设计
1、上一首下一首
想实现跳转到上一首和下一首歌曲的播放界面,肯定修改的是MusicActivity。
1.1、可以这样考虑:如果每次都获取对应歌曲的intent(意图)进行跳转,这样实现起来就比较复杂了。可以看到onCreate方法里面intent1就是获取到的歌曲列表界面跳转到音乐播放界面的意图。我们换个巧妙的方法,利用这个intent1来实现。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //绑定布局文件 setContentView(R.layout.activity_music); //获得意图 intent1=getIntent(); //初始化 init(); }
在frag1的列表点击事件里面,intent用键值对存储了歌曲名name[position]和下标position,分别存储到“name”和"position"这两个键中,然后传给MusicActivity。所以,这个position就是我们需要利用的。
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> parent, View view, int position, long id) { //创建Intent对象,启动音乐播放界面 Intent intent=new Intent(frag1.this.getContext(), MusicActivity.class); //将数据存入Intent对象,利用键值对 intent.putExtra("name",name[position]); intent.putExtra("position",String.valueOf(position)); //开启意图 startActivity(intent); } });
1.2、我们首先声明上一首、下一首这些按钮变量,然后绑定控件,设置监听器,修改下布局文件,这些大家肯定都很熟悉,所以这里也不再赘述了。
声明变量是在onCreate()的上面
private Button play;//播放按钮 private Button pause;//暂停按钮 private Button con;//继续播放按钮 private Button pre;//上一首按钮 private Button next;//下一首按钮 private Button exit;//退出按钮 private ImageView iv_music;//歌手图片框
绑定以及设置监听器是在init( )中
//依次绑定控件 play=findViewById(R.id.btn_play); pause=findViewById(R.id.btn_pause); con=findViewById(R.id.btn_continue_play); pre=findViewById(R.id.btn_pre); next=findViewById(R.id.btn_next); exit=findViewById(R.id.btn_exit); //依次设置监听器 play.setOnClickListener(this); pause.setOnClickListener(this); con.setOnClickListener(this); pre.setOnClickListener(this); next.setOnClickListener(this); exit.setOnClickListener(this);
完整的activity_music布局文件代码会在后面给出。
1.3、下面就是重写onClick()方法了,首先intent1是从歌曲列表界面跳转过来的意图。它传递过来歌曲下标position,因为用getStringExtra()方法获取的是字符串,所以用parseInt()转成整数i,这样就获取到这首歌的下标了。
//定义歌曲列表传过来的下标position String position= intent1.getStringExtra("position"); //将字符串转化为整型i int i=parseInt(position);
1.4、iv_music就是图片框,显示歌手的图片,name_song是歌曲名的文本框,显示歌曲名称。这里我在MusicActivity里面定义了一个musicName的数组,数组存放的就是歌曲名,和我们在frag1里面定义的name数组内容是一样的。其实这样我们就不需要刚刚的frag1传值给我们歌曲名了。
1.5、这时候我们来获取上一首歌,只需要i-1就可以了,但是问题来了,这样只能进行一次,继续下一首就会没有反应,因为onClick方法是每次被点击都要调用的。里面的position每次都初始化为那个i,所以你改变下标只能改变一次而已。
1.6、既然这样行不通,那么如何做到每次更新这个下标呢,这里可以自己定义个全局变量change,用来记录这个下标,每次加一或者减一,这个变量是全局变量,它的值是可以变化的,所以每次调用onClick()方法并不会复原值。
public int change=0;//记录下标的变化值
1.7、这样,不管是上一首i-1还是下一首i+1都可以直接再加上change就行了,代码如下:
case R.id.btn_pre://播放上一首 if((i+change)<1) { Toast.makeText(MusicActivity.this, "已经是第一首了", Toast.LENGTH_SHORT).show(); return; } else { change--; music_pic.setImageResource(frag1.icons[i+change]); name_song.setText(musicName[i+change]); musicControl.play(i+change); animator.start(); break; } case R.id.btn_next://播放下一首 if((i+change)==musicName.length-1) {//这里musicName.length-1表示的最后一首歌的下标 Toast.makeText(MusicActivity.this, "已经是最后一首了", Toast.LENGTH_SHORT).show(); return; } else { change++; music_pic.setImageResource(frag1.icons[i+change]); name_song.setText(musicName[i+change]); musicControl.play(i+change); animator.start(); break; }
1.8、这样在第一版基础上可以成功实现上一首下一首的效果:
完整的MusicActivity代码如下:
package zj.dzh.music_list; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import android.animation.ObjectAnimator; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.SystemClock; import android.view.View; import android.view.animation.LinearInterpolator; import android.widget.Button; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import static java.lang.Integer.parseInt; @RequiresApi(api = Build.VERSION_CODES.KITKAT) public class MusicActivity extends AppCompatActivity implements View.OnClickListener{ //定义歌曲名称的数组 public String[] musicName={"邓紫棋——光年之外","蔡健雅——红色高跟鞋","Taylor Swift——Love Story", "朴树——平凡之路","田馥甄——小幸运","周杰伦——七里香","林俊杰——江南"}; private static SeekBar sb;//定义进度条 private static TextView tv_progress,tv_total,name_song;//定义开始和总时长,歌曲名控件 private ObjectAnimator animator;//定义旋转的动画 private MusicService.MusicControl musicControl;//音乐控制类 private Button play;//播放按钮 private Button pause;//暂停按钮 private Button con;//继续播放按钮 private Button pre;//上一首按钮 private Button next;//下一首按钮 private Button exit;//退出按钮 private ImageView iv_music;//歌手图片框 Intent intent1,intent2;//定义两个意图 MyServiceConn conn; private boolean isUnbind =false;//记录服务是否被解绑 public int change=0;//记录下标的变化值 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //绑定布局文件 setContentView(R.layout.activity_music); //获得意图 intent1=getIntent(); //初始化 init(); } //初始化 private void init(){ //依次绑定控件 tv_progress=findViewById(R.id.tv_progress); tv_total=findViewById(R.id.tv_total); sb=findViewById(R.id.sb); name_song=findViewById(R.id.song_name); iv_music=findViewById(R.id.iv_music); play=findViewById(R.id.btn_play); pause=findViewById(R.id.btn_pause); con=findViewById(R.id.btn_continue_play); pre=findViewById(R.id.btn_pre); next=findViewById(R.id.btn_next); exit=findViewById(R.id.btn_exit); //依次设置监听器 play.setOnClickListener(this); pause.setOnClickListener(this); con.setOnClickListener(this); pre.setOnClickListener(this); next.setOnClickListener(this); exit.setOnClickListener(this); //创建意图对象 intent2=new Intent(this,MusicService.class); conn=new MyServiceConn();//创建服务连接对象 bindService(intent2,conn,BIND_AUTO_CREATE);//绑定服务 //从歌曲列表传过来的歌曲名 String name=intent1.getStringExtra("name"); //设置歌曲名显示 name_song.setText(name); //定义歌曲列表传过来的下标position String position= intent1.getStringExtra("position"); //将字符串转化为整型i int i=parseInt(position); //图像框设置为frag1里面的图标数组,下标为i iv_music.setImageResource(frag1.icons[i]); //为滑动条添加事件监听 sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { //当滑动条到末端时,自动播放下一首 if (progress==seekBar.getMax()){ change++;//自动下一首,下标加一 String position= intent1.getStringExtra("position"); int nowPosition=(parseInt(position)+change)%musicName.length;//当前歌曲下标 iv_music.setImageResource(frag1.icons[nowPosition]); name_song.setText(musicName[nowPosition]); // musicControl.play(nowPosition);//播放歌曲文件 // musicControl.seekTo(0); // pause.setVisibility(View.VISIBLE); //musicControl.seekTo(0);//重置播放进度 } } @Override public void onStartTrackingTouch(SeekBar seekBar) {//滑动条开始滑动时调用 } @Override public void onStopTrackingTouch(SeekBar seekBar) {//滑动条停止滑动时调用 //根据拖动的进度改变音乐播放进度 int progress=seekBar.getProgress();//获取seekBar的进度 musicControl.seekTo(progress);//改变播放进度 } }); 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(){//创建消息处理器对象 //在主线程中处理从子线程发送过来的消息 @Override public void handleMessage(Message msg){ Bundle bundle=msg.getData();//获取从子线程发送过来的音乐播放进度 int duration=bundle.getInt("duration"); int currentPosition=bundle.getInt("currentPosition"); sb.setMax(duration); sb.setProgress(currentPosition); //歌曲总时长,单位为毫秒 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=currentPosition/1000/60; second=currentPosition/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);//解绑服务 } } @RequiresApi(api = Build.VERSION_CODES.KITKAT) @Override public void onClick(View v) { //获取歌曲名的下标字符串 String position=intent1.getStringExtra("position"); //将字符串转为整数 int i=parseInt(position); switch (v.getId()){ case R.id.btn_play://播放按钮点击事件 play.setVisibility(View.INVISIBLE); musicControl.play(i); animator.start(); break; case R.id.btn_pre://播放上一首 if((i+change)<1) { change=musicName.length-1-i; musicControl.play(i+change); iv_music.setImageResource(frag1.icons[i+change]); name_song.setText(musicName[i+change]); pause.setVisibility(View.VISIBLE); animator.start(); break; } else { change--; iv_music.setImageResource(frag1.icons[i+change]); name_song.setText(musicName[i+change]); musicControl.play(i+change); pause.setVisibility(View.VISIBLE); animator.start(); break; } case R.id.btn_next://播放下一首 if((i+change)==musicName.length-1) {//这里musicName.length-1表示的最后一首歌的下标,即歌曲总数-1 change=-i; musicControl.play(i+change); iv_music.setImageResource(frag1.icons[i+change]); name_song.setText(musicName[i+change]); pause.setVisibility(View.VISIBLE); animator.start(); break; } else { change++; iv_music.setImageResource(frag1.icons[i+change]); name_song.setText(musicName[i+change]); musicControl.play(i+change); pause.setVisibility(View.VISIBLE); animator.start(); break; } case R.id.btn_pause://暂停按钮点击事件 pause.setVisibility(View.INVISIBLE); con.setVisibility(View.VISIBLE); musicControl.pausePlay(); animator.pause(); break; case R.id.btn_continue_play://继续播放按钮点击事件 con.setVisibility(View.INVISIBLE); pause.setVisibility(View.VISIBLE); musicControl.continuePlay(); animator.start(); break; case R.id.btn_exit://退出按钮点击事件 unbind(isUnbind); isUnbind=true; finish(); break; } } @Override protected void onDestroy(){ super.onDestroy(); unbind(isUnbind);//解绑服务 } }
2、个性化按钮
2.1、音乐播放器按钮用这种框框看起来属实有些别扭,所以我又花了几个小时从各大图标网站找到了一款比较中意的UI图。
2.2、通过WPS图片剪切,将它们裁剪成矢量图(其实就是剪成圆形),效果如下图所示:
2.3、然后修改layout文件,这里要将播放按钮、暂停按钮、继续播放按钮三个按钮进行重叠。我通过android:layout_centerHorizontal="true"来将它们三个全部水平居中,这样就重叠在一起了,但是重叠顺序也必须有讲究,最上层的是播放按钮(btn_play),第二层是暂停按钮(btn_pause),第三层是
继续播放按钮(btn_continue_play)。
同学甲:为什么最上层的是播放按钮?
博主:因为播放按钮点击之后要让它消失,所以它必须放第一个。
同学乙:为什么第二层的是暂停按钮?
博主:因为播放歌曲后肯定要能暂停歌曲,所以要能显示暂停按钮,而且暂停按钮点击之后会消失,显示最底下的继续播放按钮。
同学丙:那我按下继续播放按钮是不是歌曲继续播放,继续播放按钮消失,然后暂停按钮出现?
博主:你说的没错,就是这样的逻辑。
这里给出完整的layout代码:
五、运行效果
1、打开项目,编译成功后运行,会看到Android Studio执行这样的过程:Starting AVD启动模拟器,然后gradle build构建项目,接着就会install安装app,最后launch运行出app。首先显示的是歌曲列表界面:
2、我们选择【平凡之路】进行播放,跳转到音乐播放界面:
3、点击【播放】按钮,音乐开始播放,图片开始转动:
4、拖动进度条,音乐直接快进到指定位置:
5、点击【下一首】按钮,播放了【小幸运】:
6、再点击【下一首】按钮,播放了【七里香】:
7、点击【上一首】按钮,又播放了【小幸运】:
8、点击【暂停】按钮,歌曲停止播放,图片也停止旋转:
9、点击继续播放按钮,歌曲继续播放,图片继续旋转:
10、点击【退出】按钮,返回到歌曲列表界面:
11、点击【专辑】选项,进入到专辑界面,依旧选的是老霉的《Red》专辑封面
六、项目总结
本次项目主要是对上一个版本的音乐播放器进行功能和UI上的优化,实现起来并不算复杂但是有些细节需要注意,不过有耐心就可以了。其实不管遇到什么问题,都应该耐心去分析问题,然后想出解决思路,再然后解决问题。这个过程可能会很短暂也可能会很漫长,但是我们要坚信只要你用心去做,肯定会解决的。
到此这篇关于Android Studio实现音乐播放器2.0的文章就介绍到这了,更多相关Android Studio实现音乐播放器2.0内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!