1、简介
一个简易的音乐app,主要练习对四大组件的应用。感兴趣的可以看看。
播放界面如下:
歌曲列表界面如下:
项目结构如下:
接下来将对代码做详细介绍:
2、music: 音频对象
public class music { private string name;//歌曲的名称 private string author;//歌曲的作者(歌手) private long time;//歌曲的时长 private string id;//歌曲的唯一id private string url;//歌曲的地址 }
特殊说明: 由于本app没有使用数据库而是使用 list 去存储对象信息,所以没找到合适的属性值去唯一代表一个音频。此id
用的是 name+author
进行字符串拼接而成。
这种做法很有可能会发生 id 碰撞。如有严格需求,请自行解决。
3、baseactivity:
自定义activity去继承appcompatactivity。此class主要用来存放一些全局都要访问的东西。
public class baseactivity extends appcompatactivity { //用来存放音频对象。 public static list<music> musiclist = null; //用来标志 当前播放的是第几首歌, 值代表在 musiclist 中的下标。 public static int currentorder = -1; //不多解释,就看成一个解析音频文件的工具即可 protected mediametadataretriever retriever; @override protected void oncreate(@nullable bundle savedinstancestate) { super.oncreate(savedinstancestate); retriever = new mediametadataretriever(); } @suppresslint("range") protected void initmusiclist() { //此处是有代码的,后面再具体讲解 } }
4、activity_main.xml:
主界面,这里主要是用了一个相对布局,没什么好讲的。
后面会把整个项目代码放到资源里,免费使用。
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <relativelayout android:layout_width="match_parent" android:layout_height="match_parent" tools:ignore="uselessparent"> <linearlayout android:id="@+id/title" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="70sp" android:layout_alignparenttop="true" > <textview android:layout_width="0dp" android:layout_weight="5" android:layout_height="match_parent" android:layout_marginstart="5sp" android:text="@string/app_name" android:textsize="30sp" android:textcolor="#1295da" android:gravity="center|start"/> <imagebutton android:id="@+id/btn_list" android:layout_width="0dp" android:layout_weight="1" android:layout_height="match_parent" android:background="@drawable/list" android:scaletype="fitcenter"/> </linearlayout> <imagebutton android:id="@+id/music" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/music" android:layout_margintop="70sp" android:layout_centerinparent="true" android:layout_below="@+id/title" android:scaletype="fitcenter"/> <linearlayout android:id="@+id/music_message" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="70sp" android:layout_below="@+id/music" android:orientation="vertical"> <textview android:id="@+id/tv_music_name" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginstart="10sp" android:textsize="29sp" android:textcolor="#000000" android:text="@string/default_music"/> <textview android:id="@+id/tv_music_author" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginstart="10sp" android:textsize="25sp" android:text="@string/default_author"/> </linearlayout> <seekbar android:id="@+id/seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="60sp" android:layout_below="@+id/music_message" /> <relativelayout android:layout_below="@+id/seekbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <textview android:id="@+id/tv_now_time" android:layout_marginstart="10sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/default_music_time"/> <textview android:id="@+id/tv_all_time" android:layout_marginend="15sp" android:layout_alignparentend="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/default_music_time"/> </relativelayout> <linearlayout android:layout_width="match_parent" android:layout_height="80sp" android:layout_alignparentbottom="true" android:layout_marginbottom="20sp" android:orientation="horizontal"> <imagebutton android:id="@+id/btn_last" android:layout_width="0dp" android:layout_height="match_parent" android:layout_marginend="1sp" android:layout_weight="1" android:background="@color/white" android:scaletype="fitcenter" android:src="@drawable/last" /> <imagebutton android:id="@+id/btn_start" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@color/white" android:scaletype="fitcenter" android:src="@drawable/start" /> <imagebutton android:id="@+id/btn_next" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:background="@color/white" android:scaletype="fitcenter" android:src="@drawable/next" /> </linearlayout> </relativelayout> </linearlayout>
5、mainactivity:
主activity 。代码很长,分模块讲解。
属性:
protected static string current_id = "-1"; //当前正在播放的歌曲id protected static music currentmusic; protected static boolean isbind = false; protected imagebutton btn_list, btn_last, btn_start, btn_next; protected seekbar seekbar; protected textview tv_music_name, tv_music_author, tv_all_time, tv_now_time; protected static int flag = 0; //当前的状态 1:正在播放 0:暂停 protected musicservice.musicbinder musicbinder; protected musicserviceconnection musicserviceconnection; public static localbroadcastmanager localbroadcastmanager; private static final int req_read_external_storage = 1; private static boolean is_permission = false; //是否授予权限
5.1、oncreate():
protected void oncreate(bundle savedinstancestate) { ... //省略一些属性赋值。 //获取权限 requestpermissionbyhand(); //注册广播 registerbroadcast(); //绑定服务 startandbindservice();//启动服务 //进度条 seekbar.setonseekbarchangelistener(new seekbar.onseekbarchangelistener() { @override public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) { } @override public void onstarttrackingtouch(seekbar seekbar) { if (currentmusic == null) { toastutil.toast(mainactivity.this, "未播放歌曲"); } } @override public void onstoptrackingtouch(seekbar seekbar) { int progress = seekbar.getprogress(); tv_now_time.settext(format(progress)); musicbinder.seekto(progress); } }); oprseekbar(false);//刚开始不允许操作 }
5.1.1、requestpermissionbyhand()
: 因为要读取音频文件,第一步肯定要先进行授权。代码就是很标准的权限获取流程。
public void requestpermissionbyhand() { //检查有没有这个权限 int checkwritestoragepermission = contextcompat.checkselfpermission( mainactivity.this, manifest.permission.read_external_storage); //如果没有被授予 if (checkwritestoragepermission != packagemanager.permission_granted) { //请求权限,此处可以同时申请多个权限 activitycompat.requestpermissions(mainactivity.this, new string[]{manifest.permission.read_external_storage}, req_read_external_storage); //这里会根据授权的结果,去调用onrequestpermissionsresult 相应的操作。 } else { //如果已经有权限了,把这个标识设为 true,后面讲为什么。 is_permission = true; initmusiclist(); } } @override public void onrequestpermissionsresult(int requestcode, final string[] permissions, int[] grantresults) { super.onrequestpermissionsresult(requestcode, permissions, grantresults); switch (requestcode) { case req_read_external_storage: // 如果请求被取消了,那么结果数组就是空的 if (grantresults.length > 0 && grantresults[0] == packagemanager.permission_granted) { // 权限被授予了 initmusiclist();//初始化数据 is_permission = true; } else { //拒绝了权限请求,弹出提示,然后退出程序。 toastutil.toast(mainactivity.this, "请前往设置授予权限"); } break; default: break; } }
==注意:==当我们安装完应用后第一次启动时如果拒绝了权限请求。那么再次启动应用时,它会默认为禁止此权限,且 activitycompat.requestpermissions()
将不会再弹出权限授予框进行选择。如果想获取权限,只能手动去手机应用设置处授权。
is_permission
: 这玩意是干啥用的?
主要是考虑到下列情景:
如果第一次授权被拒绝了,程序虽然自动结束了,但我发现其实它仍在后台进行(才疏学浅,没找到彻底杀死进程的方法)。这个时候我们去手动授权结束后,再次打开app(),其实是执行了 onstop()->onrestart()->onresume()这样一个流程(activity的生命周期)。那我们这时应该再去判断一次,是否授权。如果缺少这次判断,那么应用将会一直退出。(虽然我们手动授权了,但是app自己不知道,必须告诉它一声)。
@override protected void onrestart() { super.onrestart(); if (!is_permission) {//当从后台进入时,判断应用是否已经有权限了 ,没有就去申请 requestpermissionbyhand(); } }
为什么不放在 onresume()
里面呢? 这个主要是会出现重复授权请求的情况(可以自己思考一下哈)。
仔细留意可以看到,我们在授权完成后,其实是去执行了 baeactivity.initmusiclist()
方法。
5.1.2 initmusiclist()
: 初始化音频数据
@suppresslint("range") protected void initmusiclist() { musiclist = new arraylist<>(); contentresolver contentresolver = getcontentresolver(); //系统提供的内容提供者,可以通过去去访问一些数据。 cursor cursor = null; //读取sd卡 //这一部分直接用就行 try { cursor = contentresolver.query(mediastore.audio.media.external_content_uri, null, null, null, null); if (cursor != null) { while (cursor.movetonext()) { //是否是音频 int ismusic = cursor.getint(cursor.getcolumnindex(mediastore.audio.media.is_music)); //时长 long duration = cursor.getlong(cursor.getcolumnindex(mediastore.audio.media.duration)); //是音乐并且时长大于1分钟 if (ismusic != 0 && duration >= 60 * 1000) { //歌名 string musicname = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.title)); //歌手 string musicauthor = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.artist)); //文件路径 string musicpath = cursor.getstring(cursor.getcolumnindexorthrow(mediastore.audio.media.data)); //歌名,歌手,时长,专辑,图标,文件路径,sequence number of list in listview music music = new music(musicname, musicauthor, duration, musicname + musicauthor, musicpath); musiclist.add(music); } } } } catch (exception e) { e.printstacktrace(); } finally { if (cursor != null) cursor.close();//用完要关闭 } //主要是这一部分 //这一部分是可有可无,上面一部分是读取的本地的音频文件 //这一部分主要是 将两个音频文件塞进了app内部,进行测试系统功能,可删除 //在上面系统结构图中可以看到 ,我在 /res/raw 下放了两首 mp3 // 由于没找到具体去直接遍历的操作,所以这里使用了暴力去解决,即把文件名设置成有规律的,如:m1,m2这样。 // 如果有好方法可以提出来。 try { for (int i = 1; i <= 2; i++) { uri uri = uri.parse("android.resource://" + getpackagename() + "/raw/m" + i); retriever.setdatasource(this,uri); string musicname = retriever.extractmetadata(mediametadataretriever.metadata_key_title); if(musicname == null) musicname = "music"+i; //歌手 string musicauthor = retriever.extractmetadata(mediametadataretriever.metadata_key_artist); if(musicauthor == null) musicauthor = "网络歌手"; //文件路径 string musicpath = "android.resource://" + getpackagename() + "/raw/m" + i; //时长 string duration = retriever.extractmetadata(mediametadataretriever.metadata_key_duration); //歌名,歌手,时长,专辑,图标,文件路径,sequence number of list in listview music music = new music(musicname, musicauthor, long.parselong(duration), musicname + musicauthor, musicpath); musiclist.add(music); } }catch (exception e){ e.printstacktrace(); }finally { if(retriever != null) retriever.release(); } }
到这里 requestpermissionbyhand()
就结束了,就是 授权+读文件
5.1.3、registerbroadcast();
注册广播: 这里采用的是 本地广播 + 动态注册
private void registerbroadcast() { localbroadcastmanager = localbroadcastmanager.getinstance(this); musicreceiver musicreceiver = new musicreceiver(); intentfilter intentfilter = new intentfilter(); intentfilter.addaction("com.xhy.musicrunning"); localbroadcastmanager.registerreceiver(musicreceiver, intentfilter); }
class musicreceiver extends broadcastreceiver { @override public void onreceive(context context, intent intent) { bundle bundle = intent.getbundleextra("bundle"); int currentposition = bundle.getint("currentposition"); seekbar.setprogress(currentposition); tv_now_time.settext(format(currentposition)); if (format(currentposition).equals(format(seekbar.getmax())) && flag == 1) { handleend(); } } }
ok,先到这里,后面再讲 musicreceiver
的操作。
5.1.4、startandbindservice()
private void startandbindservice() { intent intent = new intent(mainactivity.this, musicservice.class); musicserviceconnection = new musicserviceconnection(); startservice(intent); bindservice(intent, musicserviceconnection, bind_auto_create); }
class musicserviceconnection implements serviceconnection { @override public void onserviceconnected(componentname name, ibinder service) { musicbinder = (musicservice.musicbinder) service; isbind = true; } @override public void onservicedisconnected(componentname name) {} }
这就是很标准的服务绑定流程。
5.1.5、seekbar.setonseekbarchangelistener()
这种都比较好理解,不多讲。
seekbar.setonseekbarchangelistener(new seekbar.onseekbarchangelistener() { @override public void onprogresschanged(seekbar seekbar, int progress, boolean fromuser) { } @override public void onstarttrackingtouch(seekbar seekbar) { if (currentmusic == null) { toastutil.toast(mainactivity.this, "未播放歌曲"); } } //主要看这个 //当我们滑动或者点击进度条时,会跟随改变歌曲的进度。 @override public void onstoptrackingtouch(seekbar seekbar) { int progress = seekbar.getprogress(); // progress就是代表当前进度条的数据 tv_now_time.settext(format(progress)); //修改展示的当前时间(歌曲的进度) musicbinder.seekto(progress); } });
format()
: 将 ms 转化成 mm:ss
的格式
private string format(long time) { int minute = 0; int second = 0; minute = (int) (time / (1000 * 60)) % 60; second = (int) (time / 1000) % 60; return string.format("%02d", minute) + ":" + string.format("%02d", second); }
5.1.6、oprseekbar()
:
刚开始,seekbar
处于不可点击状态。本应用启动时是不会主动播放歌曲的,也就是处于 暂无歌曲
状态。seekbar
此时应处于不可用状态(因为有监听点击事件,会导致一些错误)。
private void oprseekbar(boolean clickable) { //禁止拖动 seekbar.setclickable(clickable); seekbar.setenabled(clickable); seekbar.setfocusable(clickable); }
oncreate()
到这里就暂时先结束,我们要先去看服务。
6、musicservice
public class musicservice extends service { //用来控制音乐的播放与暂停。系统自带的 protected mediaplayer mediaplayer; //定时器 protected timer timer; //广播管理器 //用的是 mainactivity中的 public static localbroadcastmanager localbroadcastmanager; public musicservice() { } @override public void oncreate() { super.oncreate(); mediaplayer = new mediaplayer(); localbroadcastmanager = mainactivity.localbroadcastmanager; } private void createtimer() { if (timer == null) { timer = new timer(); timertask timertask = new timertask() { //定时任务 @override public void run() { //还没有播放器的时候,就直接退出。 if(mediaplayer == null) return; //当前进度, mediaplayer 自带api,获取当前音频播放到哪里了 int currentposition = mediaplayer.getcurrentposition(); //携带数据 bundle bundle=new bundle(); bundle.putint("currentposition",currentposition); intent intent = new intent(); intent.setaction("com.xhy.musicrunning"); intent.setclassname("com.xhy.musicplayer","mainactivity&musicreceiver"); intent.putextra("bundle",bundle); //发送广播 localbroadcastmanager.sendbroadcast(intent); } }; timer.schedule(timertask,1,1000); // 1ms后,每1000ms执行 一次 timertask; //总结下来就是,只要有 mediaplay的存在,就把当前歌曲播放的具体时长 以广播的形式发送,由mainactivity进行捕获与响应 } } @override public ibinder onbind(intent intent) { return new musicbinder(); } //用来绑定服务,这样可以通过activity 与服务进行交互了 public class musicbinder extends binder { public void play(string url){//string path uri uri= uri.parse(url); try{ //重置音乐播放器 mediaplayer.reset(); //加载多媒体文件 mediaplayer=mediaplayer.create(getapplicationcontext(),uri); mediaplayer.start();//播放音乐 createtimer();//添加计时器 }catch(exception e){ e.printstacktrace(); } } //下面的暂停继续和退出方法全部调用的是mediaplayer自带的方法 public void pauseplay(){ mediaplayer.pause();//暂停播放音乐 } public void continueplay(){ mediaplayer.start();//继续播放音乐 } public void seekto(int progress){ mediaplayer.seekto(progress);//设置音乐的播放位置 } //播放下一首 public void nextplay(){ //当前的下标加1, baseactivity.currentorder +=1; //确定下一首歌的坐标 if(baseactivity.currentorder == baseactivity.musiclist.size()) baseactivity.currentorder = 0; //获取下一首歌的对象 music nextmusic = baseactivity.musiclist.get(baseactivity.currentorder); //播放 play(nextmusic.geturl()); } //播放上一首 public void lastplay(){ baseactivity.currentorder -=1; if(baseactivity.currentorder == -1) baseactivity.currentorder = 0; music lastmusic = baseactivity.musiclist.get(baseactivity.currentorder); play(lastmusic.geturl()); } } @override public void ondestroy() { //当服务被销毁就 销毁 mediaplayer,释放资源 super.ondestroy(); if(mediaplayer==null) return; if(mediaplayer.isplaying()) mediaplayer.stop();//停止播放音乐 mediaplayer.release();//释放占用的资源 mediaplayer=null;//将player置为空 if(timer != null) timer = null; } }
ok,此时我们回去看一下,广播接收器干了什么。
class musicreceiver extends broadcastreceiver { @override public void onreceive(context context, intent intent) { bundle bundle = intent.getbundleextra("bundle"); int currentposition = bundle.getint("currentposition"); seekbar.setprogress(currentposition);//调整进度条 tv_now_time.settext(format(currentposition)); //设置当前的播放时间 if (format(currentposition).equals(format(seekbar.getmax())) && flag == 1) {//如果进度条已经到头了 handleend(); } } }
private void handleend() { //歌曲放完了,相当于触发一次下一首 flag = 0;//先暂停这一首,然后执行下一首 btn_start.setimageresource(r.drawable.start); toastutil.toast(mainactivity.this, "即将播放下一首"); //延迟2.5s,播放下一首 new handler().postdelayed(new runnable() { @override public void run() { btn_next.performclick(); log.d("testrecycler", "发送消息"); //如果此时是在歌曲列表界面,发个消息 if (musiclistactivity.musichandler != null) { message message = new message(); message.what = musiclistactivity.update_text; musiclistactivity.musichandler.sendmessage(message); } } }, 2500); }
总结来说:musicreceiver
就复杂监听音乐的播放,动态的去更新 界面上时间及进度条的显示。
if (format(currentposition).equals(format(seekbar.getmax())) && flag == 1) {//如果进度条已经到头了 handleend(); }
==提示:==这里简单的提一下,为什么要判断 format 之后的 字符串 而不是直接比较 currentposition
和seekbar.getmax()
。
因为我们接受的是广播,且广播一秒才发一次,再加上传播产生的时间,在 ms 时间级内, currentposition和seekbar.getmax()。大概不不会出现相等。所以这里比较的是格式化后的 s 级内。
musicservice
就到这里
7、musiclistactivity
歌曲列表界面。这里采用的是 recyclerview
布局去展示。
public class musiclistactivity extends baseactivity { protected imagebutton btn_back; public static handler musichandler; public static final int update_text = 1; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_music_list); recyclerview recyclerview = findviewbyid(r.id.recycle_view); linearlayoutmanager layoutmanager = new linearlayoutmanager(this); recyclerview.setlayoutmanager(layoutmanager); musicadapter musicadapter = new musicadapter(musiclist, currentorder == -1 ? "-1":musiclist.get(currentorder).getid()); musicadapter.setonitemclicklistener(new onitemclicklistener() { //给我们的 item 设置点击事件,代表选中这首歌 @override public void onitemclick(view view, int position) { music music = musiclist.get(position); if (music != null) { intent intent = new intent(musiclistactivity.this, mainactivity.class); currentorder = position; //更新选中的小标, startactivity(intent); // 回到 mainactivity , } } }); recyclerview.setadapter(musicadapter); musichandler = new handler(new handler.callback() { @override public boolean handlemessage(@nonnull message msg) { if (msg.what == update_text){ //刷新 recycler musicadapter.setcurrentid(musiclist.get(currentorder).getid()); recyclerview.setadapter(null); recyclerview.setadapter(musicadapter); } return true; } }); btn_back = findviewbyid(r.id.btn_back); btn_back.setonclicklistener(new view.onclicklistener() { @override public void onclick(view v) { finish(); } }); } }
这里主要有两个部分需要注意。
1、
musicadapter musicadapter = new musicadapter(musiclist, currentorder == -1 ? "-1":musiclist.get(currentorder).getid());
我们在这里传了当前正在播放歌曲的 id 。因为我们要对这个做特殊处理。musicadapter
做的大部分都是标准的流程化处理
public class musicadapter extends recyclerview.adapter<musicadapter.viewholder> { protected list<music> mymusiclist; protected onitemclicklistener myitemlistener; public string currentid; private static final string choose_color = "#7fe67f"; public void setcurrentid(string id){ currentid = id; } public musicadapter(list<music> musiclist, string currentid) { mymusiclist = musiclist; this.currentid = currentid; } public void setonitemclicklistener(onitemclicklistener listener){ this.myitemlistener = listener; } @nonnull @override public viewholder oncreateviewholder(@nonnull viewgroup parent, int viewtype) { view view = layoutinflater.from(parent.getcontext()).inflate(r.layout.music_list, parent, false); return new viewholder(view,myitemlistener); } //在这里 @override public void onbindviewholder(@nonnull viewholder holder, int position) { log.d("testrecycler","会执行几次呢"); music music = mymusiclist.get(position); holder.musicname.settext(music.getname()); holder.musicauthor.settext(music.getauthor()); //检测是否是正在播放的歌曲 //对于正在播放的歌曲要加绿处理。 if(currentid.equalsignorecase(music.getid())){ log.d("testrecycler","匹配成功--"+music.getname()); holder.chooseflag.settext("正在播放"); holder.musicname.settextcolor(color.parsecolor(choose_color)); holder.musicauthor.settextcolor(color.parsecolor(choose_color)); holder.point.settextcolor(color.parsecolor(choose_color)); holder.chooseflag.settextcolor(color.parsecolor(choose_color)); } } @override public int getitemcount() { return mymusiclist.size(); } class viewholder extends recyclerview.viewholder implements view.onclicklistener { textview musicname; textview musicauthor; textview point; textview chooseflag; public viewholder(view view, onitemclicklistener onitemclicklistener) { super(view); myitemlistener = onitemclicklistener; view.setonclicklistener(this); musicname = view.findviewbyid(r.id.tv_list_name); musicauthor = view.findviewbyid(r.id.tv_list_author); point = view.findviewbyid(r.id.point); chooseflag = view.findviewbyid(r.id.tv_choose); } @override public void onclick(view v) { myitemlistener.onitemclick(v,getposition()); } } }
2、
musichandler = new handler(new handler.callback() { @override public boolean handlemessage(@nonnull message msg) { if (msg.what == update_text){ //刷新 recycler musicadapter.setcurrentid(musiclist.get(currentorder).getid()); recyclerview.setadapter(null); recyclerview.setadapter(musicadapter); } return true; } });
不知道还记不记得,前面有个地方发了一个消息。当歌曲播放完成后,如果我们正处于 musiclistactivity
界面。会发送一条消息。然后 musiclistactivity
就会接受这条消息,然后刷新当前页面(主要就是为了更新 绿色的正在播放)。这里我先是用了notifyitemrangechanged()
去测试,但是发现如果一直待在这个界面,有绿色状态的会变的不唯一,也是debug很久,没解决,就用了这种 重置适配器 的暴力方法(大数据时不可取)。如果有别的方法,还请多多指教。
private void handleend() { //歌曲放完了,相当于触发一次下一首 flag = 0;//先暂停这一首,然后执行下一首 btn_start.setimageresource(r.drawable.start); toastutil.toast(mainactivity.this, "即将播放下一首"); //延迟2.5s,播放下一首 new handler().postdelayed(new runnable() { @override public void run() { btn_next.performclick(); log.d("testrecycler", "发送消息"); //如果此时是在歌曲列表界面,发个消息 if (musiclistactivity.musichandler != null) { message message = new message(); message.what = musiclistactivity.update_text; musiclistactivity.musichandler.sendmessage(message); } } }, 2500); }
这个activity功能较少。让我们继续回到mainactivity
8、onresume()
@override protected void onresume() { super.onresume(); intent intent = getintent(); //这个判断是为了区别时初始化还是从 musiclistactivity 返回来的。 if (intent != null && currentorder != -1) { //从歌曲列表返回来时,更新正在播放的音频对象 currentmusic = musiclist.get(currentorder);//这个更新不会影响到播放,因为播放是 mediaplayer 控制的 //如果我们点击的是正在播放的歌曲,那么我们就不会进行任何操作 //如果歌曲不一样,就会进行更新 if (currentmusic != null && !current_id.equalsignorecase(currentmusic.getid())) { initmusicmessage();//更新展示界面 btn_start.performclick(); //这个意思是 触发一次 btn_start的点击事件。后面再讲,这里主要是理清是否需要切歌的逻辑。 } } }
private void initmusicmessage() { //更新展示界面 currentmusic = musiclist.get(currentorder); seekbar.setmax((int) currentmusic.gettime()); seekbar.setprogress(0); tv_music_name.settext(currentmusic.getname()); tv_music_author.settext(currentmusic.getauthor()); tv_all_time.settext(format(currentmusic.gettime())); tv_now_time.settext(r.string.default_music_time); }
9、点击事件处理
坚持住,就要结束了!
btn_list
: 点击后跳转到 歌曲列表。
case r.id.btn_list: //展示歌曲列表 if (is_permission) { intent intent = new intent(this, musiclistactivity.class); startactivity(intent); } else { toastutil.toast(mainactivity.this, "请先前往授权"); } break;
btn_start
: 情况最多的点击
case r.id.btn_start: /* *三种情况会触发。 * 1、刚进入界面,还没有选择任何歌曲 * 2、歌曲播放中,点击按钮 * 3、选歌界面返回后,触发 */ //1、刚进入界面,没有选择任何歌曲 if (currentorder == -1) { startfirstmusic();//选中第一首歌进行播放 break; } //如果二者不相等,说明发生了切歌 //什么时候不相等?还记的 onresume() 触发了一次点击事件不,就在这里 if (!current_id.equalsignorecase(currentmusic.getid())) { //在歌曲列表选择了不同的歌曲 if (flag == 0) { //如果是暂停装填,则修改一下图标 btn_start.setimageresource(r.drawable.pause); } current_id = currentmusic.getid(); initmusicmessage();//初始化歌曲信息 musicbinder.play(currentmusic.geturl());//播放 } else { //相等就是单纯的暂停与播放 if (flag == 1) { //处于播放状态,点击后暂停 btn_start.setimageresource(r.drawable.start); musicbinder.pauseplay(); } else { btn_start.setimageresource(r.drawable.pause); //这个地方要判断下 是还没有播放,还是继续播放 // play()是会从头开始重新播放的,所以不能乱用 if (seekbar.getprogress() == 0) { musicbinder.play(currentmusic.geturl()); } else { musicbinder.continueplay(); } } flag = flag == 1 ? 0 : 1; } break;
btn_next
、btn_last
二者差不多
case r.id.btn_last: nextandlast(false); break; case r.id.btn_next: nextandlast(true); break;
private void nextandlast(boolean nextflag) { if (currentorder == -1) { //与开始按钮一样,最开始的时候,点击三个中的任意一个,都会选中第一首歌进行播放 startfirstmusic(); return; } if (flag == 0) { //如果此时处于暂停状态 flag = 1; //更新状态 btn_start.setimageresource(r.drawable.pause); // 更新下图标 } if (nextflag) { musicbinder.nextplay(); //执行下一首 } else { musicbinder.lastplay(); //执行上一首 } initmusicmessage(); //更新界面 current_id = currentmusic.getid(); //跟新 currnet_id 的值,供后续使用 }
还有最后一个函数
private void startfirstmusic() { if (!is_permission) { //如果没有授权,点击任何一个按钮,都会弹出提示,然后什么也不干 toastutil.toast(mainactivity.this, "请先前往授权"); return; } if (baseactivity.musiclist.isempty()) { //授权了,但是没有歌曲,也是弹出提示,然后啥也不干 toastutil.toast(mainactivity.this, "暂无曲目"); return; } //有歌曲就播放第一首 currentorder = 0; currentmusic = musiclist.get(currentorder); current_id = currentmusic.getid(); initmusicmessage(); btn_start.setimageresource(r.drawable.pause); flag = 1; musicbinder.play(musiclist.get(currentorder).geturl()); oprseekbar(true)//设置我们的进度条可以进行点击、滑动。 }
以上就是基于android实现一个简易音乐播放器的详细内容,更多关于android音乐播放器的资料请关注代码网其它相关文章!
发表评论