Android音乐播放器开发(6)—歌曲播放列表

Stella981
• 阅读 1032

1. 说明

本文很早就开始写了,不过中间把电脑借给小伙伴了,后来就忘了这件事了…

内容已同步到Gitee仓库

以往的文章

适用于平时做个小课设的小伙伴们

本部分内容实现效果如下:

Android音乐播放器开发(6)—歌曲播放列表

2. 改动

由于我设计不周,播放功能实现时没有考虑到本次内容的实现,现针对播放功能的实现做出部分改动。

因为不同Activity之间互相调用内部的方法比较复杂,现在将可以复用的部分程序拿出来构建一个工具类(作为桥梁的功能)

新建一个工具类,命名为MusicPlayUtil

构建单例模式,保证其它类拿到的对象只有一个。

//这里私有化了无参构造,其它类不可以new该对象
private  MusicPlayUtil(){
   
   
}

public static MusicPlayUtil musicPlayUtil = new MusicPlayUtil();

public static MusicPlayUtil getInstance(){
   
   
    return musicPlayUtil;
}

绑定MainActivityPlayerControl,因为需要使用到它们其中的变量和方法。

private MainActivity mainActivity = null;
private PlayerControl mPlayerControl = null;

//绑定MainActivity
public void setMainActivity(MainActivity activity){
   
   
    this.mainActivity = activity;
    mPlayerControl = mainActivity.mPlayerControl;   //MainActivity已经调用了PlayerControl,这里直接使用
}

MainActivity中显示音乐界面的方法提取到了工具类里。

这一部分内容在前文里已经做了说明,这里再简单介绍一下。歌曲播放界面初始化时,除了初始化功能按钮等内容,还需要初始化有关歌曲的元素,所谓’有关歌曲’,是因为不同的歌曲所展示的内容是不同的,包括歌曲封面、歌曲名称和演唱者等信息。因此在播放器初始化和后面的切换歌曲时都需要重新初始化有关歌曲的部分界面。我们还可以看到,方法传递了一个参数playState,这个参数相当于在询问播放器“是否需要播放?”,因为我们不希望用户刚打开界面就已经在播放歌曲了,没有哪个播放器是这么做的,而在切换歌曲的时候需要自动播放,这里做了个区分。而这个参数是静态的(static),所以可以直接调用。

//设置有关歌曲的界面
public void setMusicView(MainActivity.IsPlay playState){
   
   
    try {
   
   
        JSONObject musicInfo = (JSONObject) mainActivity.sMusicList.get(mainActivity.musicId);
        String name = musicInfo.optString("name");
        String author = musicInfo.optString("author");
        String img = musicInfo.optString("img");
        mainActivity.playAddress=musicInfo.optString("address");
        mainActivity.mMusicPic.setImageUrl(IMG+img, R.mipmap.ic_launcher,R.mipmap.ic_launcher);
        mainActivity.mMusicName.setText(name);
        mainActivity.mMusicArtist.setText(author);
    } catch (Exception e) {
   
   
        e.printStackTrace();
    }
    if(playState == MainActivity.IsPlay.play){
   
   
        if ( mPlayerControl != null) {
   
   
            mPlayerControl.stopPlay();
        }
        mPlayerControl.playOrPause(playState);
    }
}

然后就是在播放界面设置和获取部分变量

//获取歌曲列表
public JSONArray getMusicList(){
   
   
    return mainActivity.sMusicList;
}

//获取歌曲id
public int getMusicId(){
   
   
    return mainActivity.musicId;
}

//获取歌曲总数
public int getMusicNum(){
   
   
    return mainActivity.songNum;
}

//设置歌曲id
public void setMusicId(int id){
   
   
    mainActivity.musicId = id;
}

工具类的内容就这些,那么怎么用呢?

  1. 获取工具类对象

    private MusicPlayUtil musicPlayUtil = MusicPlayUtil.getInstance(); //获取工具类实例化的对象

  2. MainActivity中,初始化时需要将自身这个对象作为参数传递给工具类,保证工具类也可以修改部分UI

    musicPlayUtil.setMainActivity(this);

  3. 另外在初始化或者切换歌曲时调用工具类的setMusicView方法(将自己原有的这个方法删掉)

    musicPlayUtil.setMusicView(IsPlay.notPlay);

3. 界面设计

界面主体是一个ListView,它以列表的形式展示内容,当数据量足够多时会出现滚动条,并且能够根据数据量自适应屏幕;而ListView的元素都是歌曲对应的基本信息。

  1. 标题栏

标题栏仿照登录时介绍的main_title_bar.xml新建了一个表头文件,命名为title_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="56dp"
    android:background="@color/colorPrimary"
    >
    <ImageButton
        android:id="@+id/ib_title_back"
        android:src="@drawable/go_back_selector"
        android:background="@null"
        android:layout_marginLeft="10dp"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_alignParentLeft="true"
        android:layout_centerVertical="true"
        />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="播放列表"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:textColor="#fff"
        android:textSize="20sp"
        android:id="@+id/tv_title"
        />
</RelativeLayout>

Android音乐播放器开发(6)—歌曲播放列表

  1. ListView

这一部分只加了一个ListView组件(activity_music_list.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"
    tools:context="cn.sjcup.musicplayer.activity.MusicListActivity">
    <include layout="@layout/title_bar"></include>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/lv_music" />

    </FrameLayout>

</LinearLayout>

Android音乐播放器开发(6)—歌曲播放列表

  1. Item布局

这部分是ListView显示的元素布局。(activity_item.xml)

Item显示了歌曲的基本信息,包括歌曲封面、歌名和演唱信息,另外加了一个标记,用于标识当前所播的歌曲。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="65dp">
    <com.loopj.android.image.SmartImageView
        android:id="@+id/siv_img"
        android:layout_width="80dp"
        android:layout_height="60dp"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="5dp"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="5dp"
        android:scaleType="centerCrop"
        android:src="@mipmap/ic_launcher">

    </com.loopj.android.image.SmartImageView>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tv_name"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="10dp"
        android:layout_toRightOf="@id/siv_img"
        android:ellipsize="end"
        android:maxLength="20"
        android:singleLine="true"
        android:text="歌曲"
        android:textColor="#000000"
        android:textSize="18sp"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tv_author"
        android:layout_below="@id/tv_name"
        android:layout_marginLeft="5dp"
        android:layout_marginTop="5dp"
        android:layout_toRightOf="@id/siv_img"
        android:ellipsize="end"
        android:maxLength="16"
        android:singleLine="true"
        android:text="作者"
        android:textColor="#99000000"
        android:textSize="14sp"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/tv_type"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="5dp"
        android:layout_marginRight="10dp"
        android:text="播放中"
        android:textColor="#99000000"
        android:textSize="12sp"/>

</RelativeLayout>

Android音乐播放器开发(6)—歌曲播放列表

4. 功能设计

4.1定义变量

//控件
private ImageButton mBack;
private ListView mMusicList;
private TextView mState;

//获取到的数据
private JSONArray musicList;
private JSONObject musicInfo;

//获取工具类实例化对象
private MusicPlayUtil musicPlayUtil = MusicPlayUtil.getInstance();   

4.2 初始化界面

在初始化时,分为了界面初始化和事件初始化

@Override
protected void onCreate(Bundle savedInstanceState) {
   
   
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_music_list);

    //初始化界面
    initView();
    //设置相关事件
    initEvent();
}

初始化界面,绑定界面控件,将歌曲信息填充到ListView中

//初始化界面
private void initView() {
   
   
    mBack = findViewById(R.id.ib_title_back);
    mMusicList = findViewById(R.id.lv_music);

    fillData();  //填充数据
}

数据填充使用setAdapter方法,这是ListView本身自带的方法,通过名称可以看出,Adapter意为适配器,我们将适配器填充到ListView中。参数是实例化后的MusicAdapter对象。MusicAdapter对象继承了BaseAdapter,并重写了四个方法。

四个方法分别为:

  • int getCount() 填充的item个数
  • Object getItem(int position) 指定索引对应的item数据项
  • long getItemId(int position) 指定索引对应item的id值
  • View getView(final int position, View convertView, ViewGroup parent) 填充每个item的可视内容并返回

我们可以简单的理解为,适配器先从getCount里确定填充的数量,然后循环执行getView方法将条目一个一个绘制出来,所以必须重写的是getCountgetView方法。而getItemgetItemId是调用某些函数才会触发的方法,如果不需要使用时可以暂时不修改。

具体的介绍可以参考—>博客

下面是填充数据的这一部分整体的程序,稍后会拆开介绍。

//填充数据
private void fillData() {
   
   
    mMusicList.setAdapter(new MusicAdapter());
}

private class MusicAdapter extends BaseAdapter {
   
   
    @Override
    public int getCount() {
   
   
        return musicPlayUtil.getMusicNum();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
   
   
        ViewHolder holder;   //ViewHolder是定义的局部定义类,作为信息传递的载体
        musicList = musicPlayUtil.getMusicList();
        try{
   
   
            musicInfo = musicList.getJSONObject(position);
        }catch (Exception e){
   
   
            e.printStackTrace();
        }
        if (convertView == null){
   
   
            convertView = LayoutInflater.from(
                getApplicationContext()).inflate(R.layout.activity_item,parent,false);
            holder = new ViewHolder();
            holder.siv = convertView.findViewById(R.id.siv_img);
            holder.tv_name = convertView.findViewById(R.id.tv_name);
            holder.tv_author = convertView.findViewById(R.id.tv_author);
            holder.tv_type = convertView.findViewById(R.id.tv_type);

            convertView.setTag(holder);
        }else {
   
   
            holder = (ViewHolder) convertView.getTag();
        }

        holder.siv.setImageUrl(IMG+musicInfo.optString("img"), R.mipmap.ic_launcher,
                               R.mipmap.ic_launcher);
        holder.tv_name.setText(musicInfo.optString("name"));
        holder.tv_author.setText(musicInfo.optString("author"));
        holder.tv_type.setText("");
        if (musicPlayUtil.getMusicId()==position){
   
   
            holder.tv_type.setText("播放中");
            holder.tv_type.setTextColor(Color.RED);
            mState=holder.tv_type;
        }
        return convertView;
    }

    @Override
    public Object getItem(int position) {
   
   
        return null;
    }

    @Override
    public long getItemId(int position) {
   
   
        return 0;
    }

    class ViewHolder{
   
   
        TextView tv_name;
        TextView tv_author;
        TextView tv_type;
        SmartImageView siv;
    }
}

分别讲解一下。

因为需要频繁的传递信息,这里定义了一个局部内部类,分别对应了item布局的4个控件(拓展一下:内部类分为静态内部类、成员内部类、局部内部类、匿名内部类。感兴趣的小伙伴可以去复习一下)

class ViewHolder{
   
   
    TextView tv_name;
    TextView tv_author;
    TextView tv_type;
    SmartImageView siv;
}

getCount方法,返回的是填充的item的数量,在本项目中,就是歌曲的数量。歌曲数量已经在MainActivity中获取,这里可以通过工具类直接获取到这个值。

@Override
public int getCount() {
   
   
    return musicPlayUtil.getMusicNum();
}

getView方法,返回的是填充的view信息,这一部分是本文最难理解的。参数position相当于一个标识,用来表示进行操作的是哪一个item,从0开始计数;convertView就是待返回的view信息,初始化时为null,我们要加工的也就是这个对象。

首先,定义了一个类holder,使用时需要实例化一个ViewHolder(上面定义的局部内部类)作为信息传递的载体,也就是填充得item数量决定了ViewHolder的实例化对象的数量。所有歌曲信息可以通过上面定义的工具类获取。然后通过position获取到对应的歌曲信息(JSONArray里的object也是从0开始计数的,我们可以认为是一一对应的)。

下面我们做个测试,检测一下什么时候调用getView方法。

------------------------------------------------------------Test---------------------------------------------------------

我们在getView方法中输出position,也就是每次调用getView方法时都会输出当前的position

Android音乐播放器开发(6)—歌曲播放列表

这时我们打开音乐列表,可以看到显示了11首歌曲

Android音乐播放器开发(6)—歌曲播放列表

输出显示调用了11次getView方法,position对应了0–>10

Android音乐播放器开发(6)—歌曲播放列表

然后向上滑,加载其它的歌曲,上滑的同时,其它内容通过getView方法被加载,position对应11->16

Android音乐播放器开发(6)—歌曲播放列表

现在我们想一个问题,此时下滑,也就是希望显示之前的歌曲信息,getView会被重新调用吗?(1. 第一种想法当然是认为之前的歌曲信息已经加载出来了,当然不会再调用了;2. 第二种想法就是会调用吧)

事实是加载之前的信息会重新调用getView

Android音乐播放器开发(6)—歌曲播放列表

经过简单的测试,我们可以得出一个小结论,界面中可以显示出多少个item,就会调用多少次getView方法加载信息,也就是哪个item进入可是范围,就会调用getView方法信息。前面介绍道,理论上有多少条信息就应该有对应相应数量的item,但是考虑到手机的内存是有限的,如果数量过多,占用内存就会过大,实际上的操作只会初始化特定数量的item(依屏幕长度而定),在滑动过程中,item重复使用,使用getView方法不断赋予新的信息。

------------------------------------------------------------ENDTest---------------------------------------------------------

关于setTaggetTag的使用。在setTag之前,convertView需要找到xml中定义的layout,这里是activity_item,相当于绑定了一个目标item,然后使用findViewById绑定item中的组件,再对每个组件赋予不同的值。前面介绍道,上滑又下滑后,前面加载的信息会再次使用getView方法“绘制”一遍,但已经绑定的组件没有必要再绑定一次,因为这个过程比较消耗资源,因此在一次使用findViewById绑定后,调用setTag保存起来,再次加载时,同于同样的信息,直接使用getTag获取到已绑定组件的ViewHolder,而无需重复绑定组件。

if (convertView == null){
   
   
    convertView = LayoutInflater.from(
        getApplicationContext()).inflate(R.layout.activity_item,parent,false);
    holder = new ViewHolder();
    holder.siv = convertView.findViewById(R.id.siv_img);
    holder.tv_name = convertView.findViewById(R.id.tv_name);
    holder.tv_author = convertView.findViewById(R.id.tv_author);
    holder.tv_type = convertView.findViewById(R.id.tv_type);

    convertView.setTag(holder);
}else {
   
   
    holder = (ViewHolder) convertView.getTag();
}

对于初学者来说,这个比较难理解,建议多看几篇文章。以上很多内容都是本人自己的理解,如果有不对的地方敬请指出。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
   
   
    ViewHolder holder;   //ViewHolder是定义的局部内部类,作为信息传递的载体
    musicList = musicPlayUtil.getMusicList();
    try{
   
   
        musicInfo = musicList.getJSONObject(position);
    }catch (Exception e){
   
   
        e.printStackTrace();
    }
    if (convertView == null){
   
   
        convertView = LayoutInflater.from(
            getApplicationContext()).inflate(R.layout.activity_item,parent,false);
        holder = new ViewHolder();
        holder.siv = convertView.findViewById(R.id.siv_img);
        holder.tv_name = convertView.findViewById(R.id.tv_name);
        holder.tv_author = convertView.findViewById(R.id.tv_author);
        holder.tv_type = convertView.findViewById(R.id.tv_type);

        convertView.setTag(holder);
    }else {
   
   
        holder = (ViewHolder) convertView.getTag();
    }

    holder.siv.setImageUrl(IMG+musicInfo.optString("img"), R.mipmap.ic_launcher,
                           R.mipmap.ic_launcher);
    holder.tv_name.setText(musicInfo.optString("name"));
    holder.tv_author.setText(musicInfo.optString("author"));
    holder.tv_type.setText("");
    if (musicPlayUtil.getMusicId()==position){
   
   
        holder.tv_type.setText("播放中");
        holder.tv_type.setTextColor(Color.RED);
        mState=holder.tv_type;
    }
    return convertView;
}

4.3定义事件

在这个界面,我们希望实现两个点击事件,1. 点击“返回”按钮,可以返回播放界面;2. 点击一个歌曲信息,可以播放对应的歌曲。

//初始化事件
private void initEvent() {
   
   
    mMusicList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
   
   
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
   
   

            if (mState == null) {
   
   
                
                mState = view.findViewById(R.id.tv_type);
                mState.setText("播放中");
                mState.setTextColor(Color.RED);
                musicPlayUtil.setMusicId(position);
                musicPlayUtil.setMusicView(MainActivity.IsPlay.play);
                finish();
            } else {
   
   
                mState.setText("");
                mState = view.findViewById(R.id.tv_type);
                mState.setText("播放中");
                mState.setTextColor(Color.RED);
                musicPlayUtil.setMusicId(position);
                musicPlayUtil.setMusicView(MainActivity.IsPlay.play);
                finish();
            }

        }
    });

    mBack.setOnClickListener(new View.OnClickListener() {
   
   
        @Override
        public void onClick(View v) {
   
   
            MusicListActivity.this.finish();
        }
    });
}

第一个事件,返回播放界面比较简单,这里不再赘述。

关于第二个事件,定义的变量mState表示的是歌曲的播放状态,如果在播放状态,就显示“播放中”(如下图),否则不显示内容。

Android音乐播放器开发(6)—歌曲播放列表

第一次点击时,mState是null,并没有被赋值,view就是被点击的item,第一步就是绑定被点击的item的组件,更改它的内容,响应点击事件。然后调用工具类中的方法修改歌曲id,这里的position和前面介绍的可以认为是一样的,由于position从0开始,和我定义的歌曲id是一样的,这里直接把它当作歌曲id了,如果不一致,这里需要再加一些处理,找到对应的歌曲id,再调用setMusicView播放对应的歌曲。至于finish方法,是做出点击事件后关闭播放列表界面,返回播放界面,如果你认为没有必要可以不加。

mState如果不是null,那么之前是已经绑定过别的item了,修改之前绑定的播放状态为空字符串,表示没有播放,再重新绑定新的item,后面内容就与上面相似了,不再赘述。

5. 测试

测试成功,放一个测试的动图

Android音乐播放器开发(6)—歌曲播放列表

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这