Android – 简易音乐播放器
 
 播放器功能:
 * 1. 播放模式:单曲、列表循环、列表随机;
 * 2. 后台播放(单例模式);
 * 3. 多位置同步状态回调;
 
 处理模块:
 * 1. 提取文件信息:音频文件(.mp3) -> 对象类(AudioBean);
 * 2. 后台播放管理:VMPlayer(实现对音频的播放相关处理);
 * 3. UI显示及控制:歌曲列表 + 播放控制器;
 
效果:
 

 
模块一:处理音频文件(后台服务内)
 
private void flashAudioRes(boolean autoPlay) {
        Log.d(TAG, "同步音频中...");
        new Thread(() -> {
            try {
                List<AudioBean> audioItems = synLocalMusic2(FileUtils.getAudioDir());
                if (audioItems != null && !audioItems.isEmpty()) {
                    
                    Collections.sort(audioItems, (o1, o2) -> o1.getDisplayName().compareTo(o2.getDisplayName()));
                    VMPlayer.getInstance().setPlayList(audioItems);
                    if(autoPlay){
                        Thread.sleep(1000);
                        VMPlayer.getInstance().resetIndex();
                        VMPlayer.getInstance().play();
                        VMPlayer.getInstance().notifyListChanged();
                    }
                } else {
                    
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
    
    private List<AudioBean> synLocalMusic2(String dir) {
        File root = new File(dir);
        if (root.exists()) {
            File[] files = root.listFiles();
            if (files == null || files.length < 1) {
                return null;
            }
            List<AudioBean> list = new ArrayList<>();
            MediaPlayer mediaPlayer = new MediaPlayer();
            int duration = 0;
            for (File f : files) {
                
                if (f.isFile() && f.getName().endsWith(".mp3")) {
                    try {
                        mediaPlayer.reset();
                        mediaPlayer.setDataSource(f.getPath());
                        mediaPlayer.prepare();
                        duration = mediaPlayer.getDuration();
                    } catch (IOException var5) {
                        var5.printStackTrace();
                    }
                    Log.v(TAG, "synLocalMusic: " + f.getName() + " - " + duration);
                    AudioBean bean = getAudioFileInfo(f.getPath(), f.length(), duration);
                    list.add(bean);
                }
            }
            if (mediaPlayer != null) {
                mediaPlayer.reset();
                mediaPlayer.release();
            }
            return list;
        }
        return null;
    }
    
    private AudioBean getAudioFileInfo(String path, long size, int duration) {
        AudioBean songsInfo = new AudioBean();
        
        
        String displayName = path.substring(path.lastIndexOf("/") + 1);
        String album = displayName.substring(0, displayName.lastIndexOf("."));
        String name;
        String artist;
        if (album.contains("-")) {
            artist = album.substring(0, album.lastIndexOf("-")).trim();
            name = album.substring(album.lastIndexOf("-") + 1).trim();
        } else {
            artist = name = album;
        }
        songsInfo.setName(name);
        songsInfo.setDisplayName(displayName);
        songsInfo.setArtist(artist);
        songsInfo.setDuration(duration);
        songsInfo.setSize(size);
        songsInfo.setPath(path);
        return songsInfo;
    }
 
public class AudioBean implements Serializable {
    private String name;
    private String displayName;
    private String artist;
    private String path;
    private int duration;
    private long size;
    public AudioBean() {
    }
    public AudioBean(String path) {
        
        this.path = path;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDisplayName() {
        return displayName;
    }
    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }
    public String getArtist() {
        return artist;
    }
    public void setArtist(String artist) {
        this.artist = artist;
    }
    public String getPath() {
        return path;
    }
    public void setPath(String path) {
        this.path = path;
    }
    public int getDuration() {
        return duration;
    }
    public void setDuration(int duration) {
        this.duration = duration;
    }
    public long getSize() {
        return size;
    }
    public void setSize(long size) {
        this.size = size;
    }
    @Override
    public String toString() {
        return "AudioBean{" +
                "name='" + name + '\'' +
                ", displayName='" + displayName + '\'' +
                ", artist='" + artist + '\'' +
                ", path='" + path + '\'' +
                ", duration=" + duration +
                ", size=" + size +
                '}';
    }
}
 
模块二:播放管理器
 
VMPlayer.java (主要类)
 
import android.annotation.SuppressLint;
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import com.nepalese.harinetest.config.ShareDao;
import com.nepalese.harinetest.utils.MathUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class VMPlayer implements MediaPlayer.OnCompletionListener, VirgoPlayerCallback {
    private static final String TAG = "VMPlayer";
    private static final long INTERVAL_GET_PROGRESS = 500;
    
    public static final int STATE_ERROR = -1; 
    public static final int STATE_INITIAL = 0;
    public static final int STATE_PREPARED = 1;
    public static final int STATE_PLAYING = 2;
    public static final int STATE_PAUSE = 3;
    
    public static final int MODE_SINGLE = 0;
    public static final int MODE_LOOP = 1;
    public static final int MODE_RANDOM = 2;
    @SuppressLint("StaticFieldLeak")
    private static volatile VMPlayer instance;
    private Context context;
    private MediaPlayer mediaPlayer;
    private List<AudioBean> beanList;
    private List<iPlayBack> iPlayBacks;
    private AudioBean curBean;
    private int curState;
    private int curIndex;
    private int curMode;
    private int errTime;
    private int aimSeek;
    public static VMPlayer getInstance() {
        if (instance == null) {
            synchronized (VMPlayer.class) {
                if (instance == null) {
                    instance = new VMPlayer();
                }
            }
        }
        return instance;
    }
    private VMPlayer() {
        beanList = new ArrayList<>();
        iPlayBacks = new ArrayList<>(5);
        mediaPlayer = new MediaPlayer();
        mediaPlayer.setLooping(false);
        mediaPlayer.setOnCompletionListener(this);
    }
    public void init(Context context) {
        this.context = context;
        curState = STATE_INITIAL;
        curMode = ShareDao.getAudioMode(context);
        curIndex = ShareDao.getAudioIndex(context);
        errTime = 0;
        aimSeek = 0;
        Log.d(TAG, "init: " + curIndex);
    }
    
    private boolean isValid() {
        return curState >= STATE_PREPARED && !beanList.isEmpty();
    }
    public List<AudioBean> getBeanList() {
        return beanList;
    }
    
    public void resetIndex() {
        this.curIndex = 0;
    }
    @Override
    public void onCompletion(MediaPlayer mp) {
        if (curMode == MODE_SINGLE) {
            
            mediaPlayer.seekTo(0);
            mediaPlayer.start();
        } else {
            notifyComplete();
        }
    }
    public void playOrPause() {
        if (curState == STATE_PLAYING) {
            pause();
        } else {
            play();
        }
    }
    
    @Override
    public void play() {
        if (isValid()) {
            if (curState == STATE_PAUSE) {
                
                curState = STATE_PLAYING;
                mediaPlayer.start();
                notifyStateChanged(true);
            } else if (curState == STATE_PREPARED) {
                prepareAndPlay();
            }
            
        } else {
            Log.d(TAG, "play: " + curState + " - size: " + beanList.size());
            notifyError("未设置播放列表!");
        }
    }
    private void prepareAndPlay() {
        curState = STATE_PREPARED;
        if (curIndex < 0 || curIndex >= beanList.size()) {
            curIndex = 0;
        }
        ShareDao.setAudioIndex(context, curIndex);
        Log.d(TAG, "播放: " + curIndex);
        playResource(beanList.get(curIndex));
    }
    
    private void playResource(AudioBean bean) {
        if (bean == null || TextUtils.isEmpty(bean.getPath())) {
            ++errTime;
            notifyStateChanged(false);
            if (errTime >= beanList.size()) {
                
                curState = STATE_ERROR;
                notifyError("播放列表异常!");
            } else {
                
                curState = STATE_PREPARED;
                playNext();
            }
            return;
        }
        try {
            mediaPlayer.reset();
            mediaPlayer.setDataSource(bean.getPath());
            mediaPlayer.setOnPreparedListener(mp -> {
                notifySongChanged(bean);
                notifyStateChanged(true);
                curState = STATE_PLAYING;
                mediaPlayer.seekTo(aimSeek);
                mediaPlayer.start();
                errTime = 0;
                aimSeek = 0;
                curBean = bean;
            });
            mediaPlayer.prepareAsync();
            startTask();
        } catch (IOException e) {
            ++errTime;
            if (errTime >= beanList.size()) {
                
                curState = STATE_ERROR;
            } else {
                
                if (beanList.size() > 0) {
                    curState = STATE_PREPARED;
                } else {
                    curState = STATE_INITIAL;
                }
            }
            notifyStateChanged(false);
            notifyError("播放器出错!" + e.getMessage());
        }
    }
    
    @Override
    public void play(int index) {
        if (isValid()) {
            curIndex = index;
            prepareAndPlay();
        } else {
            notifyError("未设置播放列表!");
        }
    }
    
    @Override
    public void play(AudioBean bean) {
        if (bean == null) {
            notifyError("指定歌曲为空!");
            return;
        }
        curState = STATE_PREPARED;
        playResource(bean);
    }
    
    @Override
    public void play(List<AudioBean> list, int index) {
        if (list == null || list.isEmpty()) {
            notifyError("新列表为空!");
            return;
        }
        curIndex = index;
        setPlayList(list);
        prepareAndPlay();
    }
    
    @Override
    public void playLast() {
        if (isValid()) {
            switch (curMode) {
                case MODE_SINGLE:
                    break;
                case MODE_LOOP:
                    if (curIndex > 0) {
                        --curIndex;
                    } else {
                        curIndex = beanList.size() - 1;
                    }
                    prepareAndPlay();
                    break;
                case MODE_RANDOM:
                    curIndex = MathUtil.getRandom(0, beanList.size(), curIndex);
                    prepareAndPlay();
                    break;
            }
        } else {
            notifyError("未设置播放列表!");
        }
    }
    
    @Override
    public void playNext() {
        if (isValid()) {
            switch (curMode) {
                case MODE_SINGLE:
                    break;
                case MODE_LOOP:
                    ++curIndex;
                    prepareAndPlay();
                    break;
                case MODE_RANDOM:
                    curIndex = MathUtil.getRandom(0, beanList.size(), curIndex);
                    prepareAndPlay();
                    break;
            }
        } else {
            notifyError("未设置播放列表!");
        }
    }
    
    @Override
    public void pause() {
        if (isPlaying()) {
            curState = STATE_PAUSE;
            mediaPlayer.pause();
            notifyStateChanged(false);
        }
    }
    
    @Override
    public void seekTo(int progress) {
        if (isValid()) {
            if (curState > STATE_PREPARED) {
                aimSeek = 0;
                mediaPlayer.seekTo(progress);
            } else {
                aimSeek = progress;
            }
        }
    }
    
    @Override
    public void setPlayList(List<AudioBean> beans) {
        if (beans == null || beans.isEmpty()) {
            notifyError("新列表为空!");
            return;
        }
        Log.d(TAG, "setPlayList: " + beans.size());
        curState = STATE_PREPARED;
        beanList.clear();
        beanList.addAll(beans);
        curBean = beanList.get(curIndex);
    }
    
    @Override
    public void setPlayMode(int mode) {
        if (mode == this.curMode) {
            return;
        }
        this.curMode = mode;
        ShareDao.setAudioMode(context, mode);
        Log.d(TAG, "setPlayMode: " + curMode);
    }
    
    @Override
    public boolean isPlaying() {
        return isValid() && mediaPlayer.isPlaying();
    }
    
    @Override
    public int getCurProgress() {
        return mediaPlayer.getCurrentPosition();
    }
    
    @Override
    public int getCurState() {
        return curState;
    }
    @Override
    public int getCurMode() {
        return curMode;
    }
    
    @Override
    public AudioBean getCurMusic() {
        if (isValid()) {
            return curBean;
        }
        return null;
    }
    
    @Override
    public void releasePlayer() {
        stopTask();
        if (iPlayBacks != null) {
            iPlayBacks.clear();
            iPlayBacks = null;
        }
        if (beanList != null) {
            beanList.clear();
            beanList = null;
        }
        try {
            if (mediaPlayer != null) {
                
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.stop();
                }
                mediaPlayer.reset();
                mediaPlayer.release();
                mediaPlayer = null;
            }
        } catch (Exception e) {
            
        } finally {
            if (mediaPlayer != null) {
                mediaPlayer.reset();
                mediaPlayer.release();
                mediaPlayer = null;
            }
        }
        instance = null;
        curState = STATE_INITIAL;
    }
    
    @Override
    public void registerCallback(iPlayBack callback) {
        iPlayBacks.add(callback);
    }
    
    @Override
    public void unregisterCallback(iPlayBack callback) {
        iPlayBacks.remove(callback);
    }
    @Override
    public void removeCallbacks() {
        iPlayBacks.clear();
    }
    public void notifyListChanged() {
        if (iPlayBacks != null) {
            for (iPlayBack callBack : iPlayBacks) {
                callBack.onListChange();
            }
        }
    }
    private void notifySongChanged(AudioBean bean) {
        if (iPlayBacks != null) {
            for (iPlayBack callBack : iPlayBacks) {
                callBack.onChangeSong(bean);
            }
        }
    }
    private void notifyStateChanged(boolean isPlaying) {
        if (iPlayBacks != null) {
            for (iPlayBack callback : iPlayBacks) {
                callback.onPlayStateChanged(isPlaying);
            }
        }
    }
    private void notifyComplete() {
        if (iPlayBacks != null) {
            for (iPlayBack callback : iPlayBacks) {
                callback.onPlayCompleted();
            }
        }
    }
    private void notifyProcessChanged(int process) {
        if (iPlayBacks != null) {
            for (iPlayBack callback : iPlayBacks) {
                callback.onProcessChanged(process);
            }
        }
    }
    private void notifyError(String msg) {
        if (iPlayBacks != null) {
            for (iPlayBack callback : iPlayBacks) {
                callback.onPlayError(curState, msg);
            }
        }
    }
    
    private final Handler handler = new Handler(msg -> false);
    private final Runnable getProcessTask = new Runnable() {
        @Override
        public void run() {
            handler.postDelayed(getProcessTask, INTERVAL_GET_PROGRESS);
            try {
                if (isPlaying()) {
                    notifyProcessChanged(getCurProgress());
                }
            } catch (Throwable ignored) {
            }
        }
    };
    private void startTask() {
        stopTask();
        handler.post(getProcessTask);
    }
    private void stopTask() {
        handler.removeCallbacks(getProcessTask);
    }
}
 
VirgoPlayerCallback.java (功能接口)
 
public interface VirgoPlayerCallback {
    
    void play();
    
    void play(int index);
    
    void play(AudioBean bean);
    
    void play(List<AudioBean> beanList, int index);
    
    void playLast();
    
    void playNext();
    
    void pause();
    
    void seekTo(int progress);
    
    void setPlayList(List<AudioBean> beans);
    
    void setPlayMode(int mode);
    
    boolean isPlaying();
    
    int getCurProgress();
    
    int getCurState();
    
    int getCurMode();
    
    AudioBean getCurMusic();
    
    void releasePlayer();
    void registerCallback(iPlayBack callback);
    void unregisterCallback(iPlayBack callback);
    void removeCallbacks();
}
 
iPlayBack.java(播放状态回调接口)
 
public interface iPlayBack {
    
    void onListChange();
    void onChangeSong(@NonNull AudioBean bean);
    
    void onPlayCompleted();
    
    void onPlayStateChanged(boolean isPlaying);
    
    void onProcessChanged(int process);
    
    void onPlayError(int state, String error);
}
 
模块三:播放控件+歌曲列表
 
VirgoSimplePlayer.java (简单音乐播放器控件)
 

 
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import com.nepalese.harinetest.R;
import com.nepalese.harinetest.utils.ConvertUtil;
public class VirgoSimplePlayer extends RelativeLayout {
    private static final String TAG = "VirgoSimplePlayer";
    private SeekBar musicSeekbar;
    private TextView musicName, musicCur, musicAll;
    private ImageButton musicLast, musicPlay, musicNext, musicMode;
    private VMPlayer vmPlayer;
    public VirgoSimplePlayer(Context context) {
        this(context, null);
    }
    public VirgoSimplePlayer(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public VirgoSimplePlayer(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.layout_simple_virgo_player, this, true);
        init();
    }
    private void init() {
        initUI();
        initData();
        setListener();
    }
    private void initUI() {
        musicSeekbar = findViewById(R.id.music_seekbar);
        musicName = findViewById(R.id.music_tv_name);
        musicCur = findViewById(R.id.music_cur);
        musicAll = findViewById(R.id.music_all);
        musicLast = findViewById(R.id.music_btn_last);
        musicPlay = findViewById(R.id.music_btn_paly);
        musicNext = findViewById(R.id.music_btn_next);
        musicMode = findViewById(R.id.music_btn_mode);
        musicName.setSelected(true);
    }
    private void initData() {
        vmPlayer = VMPlayer.getInstance();
    }
    private void setListener() {
        musicLast.setOnClickListener(v -> vmPlayer.playLast());
        musicNext.setOnClickListener(v -> vmPlayer.playNext());
        musicPlay.setOnClickListener(v -> vmPlayer.playOrPause());
        musicMode.setOnClickListener(v -> changPlayMode());
        musicSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                
                vmPlayer.seekTo(seekBar.getProgress());
            }
        });
    }
    private void changPlayMode() {
        int curMode = vmPlayer.getCurMode();
        curMode++;
        if (curMode > VMPlayer.MODE_RANDOM) {
            curMode = VMPlayer.MODE_SINGLE;
        }
        vmPlayer.setPlayMode(curMode);
        updateModeImg(curMode);
    }
    private void updateModeImg(int curMode) {
        switch (curMode) {
            case VMPlayer.MODE_SINGLE:
                musicMode.setImageResource(R.mipmap.icon_single);
                break;
            case VMPlayer.MODE_LOOP:
                musicMode.setImageResource(R.mipmap.icon_order);
                break;
            case VMPlayer.MODE_RANDOM:
                musicMode.setImageResource(R.mipmap.icon_random);
                break;
        }
    }
    public void notifyStateChanged(boolean isPlaying) {
        if (isPlaying) {
            musicPlay.setImageResource(R.mipmap.icon_playing);
        } else {
            musicPlay.setImageResource(R.mipmap.icon_pause);
        }
    }
    public void notifyProcessChanged(int process) {
        musicSeekbar.setProgress(process);
        musicCur.setText(ConvertUtil.formatTime(process));
    }
    public void notifySongChanged(String name, int duration) {
        musicName.setText(name);
        musicSeekbar.setMax(duration);
        musicAll.setText(ConvertUtil.formatTime(duration));
    }
    public void synInfo() {
        
        if (vmPlayer.isPlaying()) {
            AudioBean bean = vmPlayer.getCurMusic();
            notifySongChanged(bean.getName() + " - " + bean.getArtist(), bean.getDuration());
            notifyStateChanged(true);
        }else{
            
            vmPlayer.play();
        }
        
        updateModeImg(vmPlayer.getCurMode());
    }
}
 
layout_simple_virgo_player.xml
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:padding="15dp"
    android:orientation="horizontal"
    android:background="@drawable/bg_card_red">
    <ImageView
        android:layout_width="@dimen/player_img_size"
        android:layout_height="@dimen/player_img_size"
        android:src="@mipmap/img_cover_default"
        android:scaleType="centerCrop"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_marginStart="10dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:orientation="vertical">
            <TextView
                android:id="@+id/music_tv_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="left"
                android:singleLine="true"
                android:ellipsize="marquee"
                android:marqueeRepeatLimit="marquee_forever"
                android:text="歌名"
                android:textSize="@dimen/text_size_14"
                android:textColor="@color/black"
                android:paddingStart="15dp"/>
            <SeekBar
                android:id="@+id/music_seekbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:progressTint="@color/black"
                android:thumbTint="@color/color_QYH"
                android:layout_marginTop="3dp"
                android:progress="0"/>
        </LinearLayout>
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_marginLeft="5dp"
                android:orientation="horizontal">
                <TextView
                    android:id="@+id/music_cur"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="00"
                    android:textColor="@color/white"
                    android:textSize="@dimen/text_size_12"/>
                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="/"
                    android:textColor="@color/white"
                    android:textSize="@dimen/text_size_12"/>
                <TextView
                    android:id="@+id/music_all"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="00"
                    android:textColor="@color/white"
                    android:textSize="@dimen/text_size_12"/>
            </LinearLayout>
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:gravity="center"
                android:orientation="horizontal">
                <ImageButton
                    android:id="@+id/music_btn_last"
                    android:layout_width="@dimen/player_icon_size_small"
                    android:layout_height="@dimen/player_icon_size_small"
                    android:layout_margin="@dimen/player_icon_margin"
                    android:background="@drawable/img_button_transprant"
                    android:src="@mipmap/icon_last"
                    android:scaleType="centerCrop"/>
                <ImageButton
                    android:id="@+id/music_btn_paly"
                    android:layout_width="@dimen/player_icon_size_big"
                    android:layout_height="@dimen/player_icon_size_big"
                    android:background="@drawable/img_button_transprant"
                    android:src="@mipmap/icon_pause"
                    android:scaleType="centerCrop"/>
                <ImageButton
                    android:id="@+id/music_btn_next"
                    android:layout_width="@dimen/player_icon_size_small"
                    android:layout_height="@dimen/player_icon_size_small"
                    android:layout_margin="@dimen/player_icon_margin"
                    android:background="@drawable/img_button_transprant"
                    android:src="@mipmap/icon_next"
                    android:scaleType="centerCrop"/>
            </LinearLayout>
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_alignParentEnd="true">
                <ImageButton
                    android:id="@+id/music_btn_mode"
                    android:layout_width="@dimen/player_icon_size_small"
                    android:layout_height="@dimen/player_icon_size_small"
                    android:layout_margin="@dimen/player_icon_margin"
                    android:background="@drawable/img_button_transprant"
                    android:src="@mipmap/icon_order"
                    android:scaleType="centerCrop"/>
            </LinearLayout>
        </RelativeLayout>
    </LinearLayout>
</LinearLayout>
 
<dimen name="player_icon_size_big">42dp</dimen>
<dimen name="player_icon_size_small">30dp</dimen>
<dimen name="player_icon_margin">10dp</dimen>
<dimen name="player_layout_padding">10dp</dimen>
<dimen name="player_img_size">85dp</dimen>
<dimen name="text_size_10">10sp</dimen>
<dimen name="text_size_12">12sp</dimen>
<dimen name="text_size_14">14sp</dimen>
<dimen name="text_size_16">16sp</dimen>
<dimen name="text_size_18">18sp</dimen>
<dimen name="text_size_20">20sp</dimen>
<dimen name="text_size_22">22sp</dimen>
<dimen name="text_size_24">24sp</dimen>
<dimen name="text_size_32">32sp</dimen>
<dimen name="text_size_50">50sp</dimen>
<dimen name="padding_1">1dp</dimen>
<dimen name="padding_2">2dp</dimen>
<dimen name="padding_3">3dp</dimen>
<dimen name="padding_5">5dp</dimen>
<dimen name="padding_10">10dp</dimen>
<dimen name="padding_15">15dp</dimen>
<dimen name="margin_1">1dp</dimen>
<dimen name="margin_3">3dp</dimen>
<dimen name="margin_5">5dp</dimen>
<dimen name="margin_10">10dp</dimen>
<dimen name="margin_15">15dp</dimen>
 
ListView_LocalSong_Adapter.java(自定义列表适配器)
 

 
import android.content.Context;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.nepalese.harinetest.R;
import java.util.List;
public class ListView_LocalSong_Adapter extends BaseAdapter {
    private Context context;
    private LayoutInflater inflater;
    private List<AudioBean> data;
    private interListenerSongList listener;
    public ListView_LocalSong_Adapter(Context context, interListenerSongList listener, List<AudioBean> list) {
        this.context = context;
        this.inflater = LayoutInflater.from(context);
        this.listener = listener;
        this.data = list;
    }
    @Override
    public int getCount() {
        return data == null ? 0 : data.size();
    }
    @Override
    public Object getItem(int position) {
        return null;
    }
    @Override
    public long getItemId(int position) {
        return position;
    }
    static class Holder {
        private TextView tvOrder, tvName, tvArtist;
        private LinearLayout root;
        private ImageButton ibList;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        Holder holder;
        if (convertView == null) {
            holder = new Holder();
            convertView = inflater.inflate(R.layout.layout_song_list_local, null);
            holder.root = convertView.findViewById(R.id.layout_list_root);
            holder.tvOrder = convertView.findViewById(R.id.tv_order);
            holder.tvName = convertView.findViewById(R.id.tvLocalName);
            holder.tvArtist = convertView.findViewById(R.id.tvLocalArtist);
            holder.ibList = convertView.findViewById(R.id.ibLocalSongList);
            convertView.setTag(holder);
        } else {
            holder = (Holder) convertView.getTag();
        }
        holder.tvOrder.setText(String.valueOf(position + 1));
        holder.tvName.setText(data.get(position).getName());
        holder.tvArtist.setText(data.get(position).getArtist());
        if (position % 2 == 0) {
            holder.root.setBackgroundColor(Color.parseColor("#4D03A9F4"));
        }else{
            holder.root.setBackgroundColor(Color.TRANSPARENT);
        }
        if (VMPlayer.getInstance().getCurState() >= VMPlayer.STATE_PREPARED && VMPlayer.getInstance().getCurMusic().getDisplayName().equals(data.get(position).getDisplayName())) {
            holder.tvName.setTextColor(Color.RED);
        } else {
            holder.tvName.setTextColor(Color.BLACK);
        }
        
        holder.ibList.setTag(position);
        return convertView;
    }
    public interface interListenerSongList {
        void onItemClick(View view);
    }
}
 
layout_song_list_local.xml
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_list_root"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="@dimen/padding_5"
    android:gravity="center_vertical"
    android:descendantFocusability="blocksDescendants"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/tv_order"
        android:layout_margin="@dimen/margin_5"
        android:layout_width="35dp"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="@dimen/text_size_18"
        android:textColor="@color/black" />
    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">
        <TextView
            android:id="@+id/tvLocalName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:ellipsize="end"
            android:layout_marginBottom="@dimen/margin_3"
            android:textColor="@color/black"
            android:textSize="@dimen/text_size_18"/>
        <TextView
            android:id="@+id/tvLocalArtist"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:singleLine="true"
            android:ellipsize="end"
            android:textColor="@color/gray"
            android:textSize="@dimen/text_size_14"/>
    </LinearLayout>
    <ImageButton
        android:id="@+id/ibLocalSongList"
        android:layout_width="@dimen/icon_30"
        android:layout_height="@dimen/icon_30"
        android:src="@mipmap/icon_list_gray"
        android:scaleType="fitCenter"
        android:padding="@dimen/padding_2"
        android:focusable="false"
        android:background="@drawable/selector_button_transparent"/>
</LinearLayout>
 
前端使用
 
public class AudioPlayActivity extends AppCompatActivity implements ListView_LocalSong_Adapter.interListenerSongList, iPlayBack {
    private static final String TAG = "AudioPlayActivity";
    private Context context;
    private VirgoSimplePlayer simplePlayer;
    private ListView listView;
    private ListView_LocalSong_Adapter adapter;
    private final List<AudioBean> audioList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_audio_play);
        context = getApplicationContext();
        init();
    }
    private void init() {
        initUI();
        initData();
        setListener();
    }
    private void initUI() {
        simplePlayer = findViewById(R.id.simplePlayer);
        listView = findViewById(R.id.listviewAudio);
    }
    private void initData() {
        VMPlayer.getInstance().registerCallback(this);
        simplePlayer.synInfo();
        audioList.addAll(VMPlayer.getInstance().getBeanList());
        adapter = new ListView_LocalSong_Adapter(context, this, audioList);
        listView.setAdapter(adapter);
    }
    private void setListener() {
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                VMPlayer.getInstance().play(position);
            }
        });
    }
    @Override
    protected void onDestroy() {
        release();
        super.onDestroy();
    }
    private void release() {
        VMPlayer.getInstance().unregisterCallback(this);
    }
    @Override
    public void onItemClick(View view) {
        
    }
    @Override
    public void onListChange() {
        audioList.clear();
        audioList.addAll(VMPlayer.getInstance().getBeanList());
        updateListView();
    }
    
    @Override
    public void onChangeSong(@NonNull AudioBean bean) {
        if (simplePlayer != null) {
            simplePlayer.notifySongChanged(bean.getName() + " - " + bean.getArtist(), bean.getDuration());
        }
        updateListView();
    }
    @Override
    public void onPlayCompleted() {
        
        VMPlayer.getInstance().playNext();
    }
    @Override
    public void onPlayStateChanged(boolean isPlaying) {
        if (simplePlayer != null) {
            simplePlayer.notifyStateChanged(isPlaying);
        }
    }
    @Override
    public void onProcessChanged(int process) {
        if (simplePlayer != null) {
            simplePlayer.notifyProcessChanged(process);
        }
    }
    @Override
    public void onPlayError(int state, String error) {
        Log.d(TAG, "onPlayError: " + error);
    }
    private final int MSG_UPDATE_LIST = 1;
    private void updateListView(){
        handler.sendEmptyMessage(MSG_UPDATE_LIST);
    }
    private final Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message msg) {
            if(msg.what == MSG_UPDATE_LIST){
                if (adapter != null) {
                    adapter.notifyDataSetChanged();
                }
            }
            return false;
        }
    });
}
 
<?xml version="1.0" encoding="utf-8"?>
<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"
    android:orientation="vertical"
    tools:context=".AudioPlayActivity">
    <ListView
        android:id="@+id/listviewAudio"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:divider="@color/white"
        android:dividerHeight="0dp" />
    <com.nepalese.harinetest.musicplayer.VirgoSimplePlayer
        android:id="@+id/simplePlayer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>