当前位置: 代码网 > it编程>App开发>Android > 基于Android实现一个简易音乐播放器

基于Android实现一个简易音乐播放器

2024年08月26日 Android 我要评论
1、简介一个简易的音乐app,主要练习对四大组件的应用。感兴趣的可以看看。播放界面如下:歌曲列表界面如下:项目结构如下:接下来将对代码做详细介绍:2、music: 音频对象public class m

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 之后的 字符串 而不是直接比较 currentpositionseekbar.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_nextbtn_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音乐播放器的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com