Android -- 简易音乐播放器

Android – 简易音乐播放器

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

效果:

在这里插入图片描述

模块一:处理音频文件(后台服务内)
/**
* 同步指定文件夹下音频文件
* * @param autoPlay 是否自动播放
*/
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 {//closeDialogSyn("本地无有效音频文件!", 3000);}} catch (Exception e) {e.printStackTrace();}}).start();}/*** 同步指定文件夹下音频文件:仅一层* @param dir 文件夹*/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;}/*** 文件绝对路径,校验放在外面* 文件名格式:歌手 - 歌名.mp3*/private AudioBean getAudioFileInfo(String path, long size, int duration) {AudioBean songsInfo = new AudioBean();//xxx/Music/歌手 - 歌名.mp3//filenameString displayName = path.substring(path.lastIndexOf("/") + 1);//歌手 - 歌名.mp3String 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;}
/*** Created by Administrator on 2024/11/24.* Usage: 简单自定义音频文件bean类*/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;}@Overridepublic 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;/*** Created by Administrator on 2024/11/25.* Usage: virgo music player* 1. 播放模式:单曲、列表循环、列表随机;* 2. 后台播放(单例模式);* 3. 多位置同步状态回调;*/
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);//记忆播放模式 默认列表循环 1curIndex = ShareDao.getAudioIndex(context);//记忆播放位置 0errTime = 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;}@Overridepublic 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();}}/*** 播放|继续播放*/@Overridepublic 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());}}/*** 播放当前列表指定位置** @param index index*/@Overridepublic void play(int index) {if (isValid()) {curIndex = index;prepareAndPlay();} else {notifyError("未设置播放列表!");}}/*** 临时播放某个音频文件** @param bean AudioBean*/@Overridepublic void play(AudioBean bean) {if (bean == null) {notifyError("指定歌曲为空!");return;}curState = STATE_PREPARED;playResource(bean);}/*** 更换播放列表** @param list  新列表* @param index 开始位置,默认:0*/@Overridepublic void play(List<AudioBean> list, int index) {if (list == null || list.isEmpty()) {notifyError("新列表为空!");return;}curIndex = index;setPlayList(list);prepareAndPlay();}/*** 上一首*/@Overridepublic 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("未设置播放列表!");}}/*** 下一首*/@Overridepublic 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("未设置播放列表!");}}/*** 暂停播放*/@Overridepublic void pause() {if (isPlaying()) {curState = STATE_PAUSE;mediaPlayer.pause();notifyStateChanged(false);}}/*** 跳转播放进度** @param progress p*/@Overridepublic void seekTo(int progress) {if (isValid()) {if (curState > STATE_PREPARED) {aimSeek = 0;mediaPlayer.seekTo(progress);} else {aimSeek = progress;}}}/*** 设置播放列表** @param beans b*/@Overridepublic 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);}/*** 设置播放模式,外部校验** @param mode m*/@Overridepublic void setPlayMode(int mode) {if (mode == this.curMode) {return;}this.curMode = mode;ShareDao.setAudioMode(context, mode);Log.d(TAG, "setPlayMode: " + curMode);}/*** 是否正在播放*/@Overridepublic boolean isPlaying() {return isValid() && mediaPlayer.isPlaying();}/*** 当前播放进度*/@Overridepublic int getCurProgress() {return mediaPlayer.getCurrentPosition();}/*** 当前播放器状态*/@Overridepublic int getCurState() {return curState;}@Overridepublic int getCurMode() {return curMode;}/*** 获取当前播放音频信息* 可空*/@Overridepublic AudioBean getCurMusic() {if (isValid()) {return curBean;}return null;}/*** 注销播放器*/@Overridepublic void releasePlayer() {stopTask();if (iPlayBacks != null) {iPlayBacks.clear();iPlayBacks = null;}if (beanList != null) {beanList.clear();beanList = null;}try {if (mediaPlayer != null) {//stop 可能会有异常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;}/*** 注册播放器回调*/@Overridepublic void registerCallback(iPlayBack callback) {iPlayBacks.add(callback);}/*** 注销播放器回调*/@Overridepublic void unregisterCallback(iPlayBack callback) {iPlayBacks.remove(callback);}@Overridepublic 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() {@Overridepublic 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 (功能接口)
/*** Created by Administrator on 2024/11/24.* Usage: 音乐播放器公开接口*/
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;/*** Created by Administrator on 2024/11/24.* Usage: 简单音乐播放器控件*/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() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic 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"><ImageViewandroid:layout_width="@dimen/player_img_size"android:layout_height="@dimen/player_img_size"android:src="@mipmap/img_cover_default"android:scaleType="centerCrop"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:layout_marginStart="10dp"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:orientation="vertical"><TextViewandroid: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"/><SeekBarandroid: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><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_marginLeft="5dp"android:orientation="horizontal"><TextViewandroid: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"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="/"android:textColor="@color/white"android:textSize="@dimen/text_size_12"/><TextViewandroid: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><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerInParent="true"android:gravity="center"android:orientation="horizontal"><ImageButtonandroid: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"/><ImageButtonandroid: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"/><ImageButtonandroid: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><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_alignParentEnd="true"><ImageButtonandroid: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>
<!--============dimens.xml=============-->
<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><!--text size sp-->
<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;/*** @author nepalese on 2024/11/24* @usage*/
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;}@Overridepublic int getCount() {return data == null ? 0 : data.size();}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return position;}static class Holder {private TextView tvOrder, tvName, tvArtist;private LinearLayout root;private ImageButton ibList;}@Overridepublic 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.setOnClickListener(v -> {
//            listener.onItemClick(v);
//        });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"><TextViewandroid: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" /><LinearLayoutandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="vertical"><TextViewandroid: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"/><TextViewandroid: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><ImageButtonandroid: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<>();@Overrideprotected 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() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {VMPlayer.getInstance().play(position);}});}@Overrideprotected void onDestroy() {release();super.onDestroy();}private void release() {VMPlayer.getInstance().unregisterCallback(this);}@Overridepublic void onItemClick(View view) {//}@Overridepublic void onListChange() {audioList.clear();audioList.addAll(VMPlayer.getInstance().getBeanList());updateListView();}//@Overridepublic void onChangeSong(@NonNull AudioBean bean) {if (simplePlayer != null) {simplePlayer.notifySongChanged(bean.getName() + " - " + bean.getArtist(), bean.getDuration());}//刷新列表updateListView();}@Overridepublic void onPlayCompleted() {//自动播放下一首VMPlayer.getInstance().playNext();}@Overridepublic void onPlayStateChanged(boolean isPlaying) {if (simplePlayer != null) {simplePlayer.notifyStateChanged(isPlaying);}}@Overridepublic void onProcessChanged(int process) {if (simplePlayer != null) {simplePlayer.notifyProcessChanged(process);}}@Overridepublic void onPlayError(int state, String error) {
//        Toast.makeText(context, error, Toast.LENGTH_LONG).show();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() {@Overridepublic 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"><ListViewandroid: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.VirgoSimplePlayerandroid:id="@+id/simplePlayer"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/61403.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

常用端口与Udp协议

目录 1.再谈端口 1.1 五元组 1.2 端口号范围划分 1.3 两个指令 1.3.1 netstat 1.3.2 pidof 2.UDP协议 2.1 协议整体格式 2.2 udp特点 2.3 udo缓冲区 1.再谈端口 1.1 五元组 端口号表示了一个主机上进行通信的不同的应用程序&#xff1b;在Tcp/IP协议中&#xff0c;用…

计算机毕业设计SpringCloud+大模型微服务高考志愿填报推荐系统 高考大数据 SparkML机器学习 深度学习 人工智能 Python爬虫 知识图谱

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

C/C++ 数据结构与算法【线性表】 顺序表+链表详细解析【日常学习,考研必备】带图+详细代码

1&#xff09;线性表的定义 线性表&#xff08;List&#xff09;&#xff1a;零个或多个数据元素的有限序列。 线性表的数据集合为{a1,a2,…,an}&#xff0c;假设每个元素的类型均为DataType。其中&#xff0c;除第一个元素a1外&#xff0c;每一个元素有且只有一个直接前驱元素…

搭建AD域服务器

搭建AD域服务器 使用深信服HCI搭建AD域服务器 1、新建虚拟机 2、填写参数 3、省略安装过程 4、进入服务器管理器 5、 6、 7、 8、 9、 10、 11、 12、 13、 14、 15、 16、 17、 18、 19、 20、 21、 22、 23、

【算法day4】链表:应用拓展与快慢指针

题目引用 两两交换链表节点删除链表的倒数第n个节点链表相交环形链表 1.两两交换链表节点 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&am…

【Gitlab】gitrunner并发配置

并发介绍 涉及到并发控制的一共有4个参数: concurrent , limit ,request_concurrency,parallel 全局的配置: [rootiZ2vc6igbukkxw6rbl64ljZ config]# vi config.toml concurrent 4 #这是一个总的全局控制&#xff0c;它限制了所有pipline&#xff0c;所有runner执行器…

vue 2 父组件根据注册事件,控制相关按钮显隐

目标效果 我不注册事件&#xff0c;那么就不显示相关的按钮 注册了事件&#xff0c;才会显示相关内容 实现思路 组件在 mounted 的时候可以拿到父组件注册监听的方法 拿到这个就可以做事情了 mounted() {console.log(this.$listeners, this.$listeners);this.show.search !…

四:工具、环境准备-compute node

一&#xff1a;工具、环境准备-controller node 二&#xff1a;OpenStack环境准备-controller node 三&#xff1a;安装服务-controller node 四&#xff1a;工具、环境准备-compute node 五&#xff1a;OpenStack环境准备-compute node 六&#xff1a;安装服务-compute node 七…

Vue3在PC端接入萤石云监控

参考文档&#xff1a;文档概述 萤石开放平台API文档 1.安装依赖 npm i ezuikit-js 2.封装组件 src/components/PlayerVideo/index.vue <template><div id"video-container" style"width: 100%;"></div> </template> <scrip…

快排详解(4种写法:霍尔/挖坑法/双指针/非递归)

//本文所写快排的结果都是从小到大排序 思路 快排就是把数组的第一个值记为key,然后定义两个指针,一个叫begin,一个叫end. begin指针从数组的头部出发,寻找比key大的值;end指针从数组的尾部出发,寻找比key小的值; 然后交换begin和end的值 ......最后,begin和end相遇就停下…

ESP32驱动PCM5102A播放SD卡音频

文章目录 简介模块参数功能框图引脚定义通信接口和通信数据格式通信流程引脚接线ESP32和PCM5102AESP32和SD模块 主要代码spi.hSD.hAudio.h实验效果总结 简介 PCM5102A模块是一款立体声DAC模块、音频数模转换器&#xff0c;内部集成了PCM5102芯片。PCM5102芯片是集成了立体声模…

ProtonBase 教育行业解决方案

01/方案概述 当前&#xff0c;大数据、云计算等技术正加速教育行业的数字化转型&#xff0c;教学模式从线下转向线上&#xff0c;传统教育企业向具有互联网性质的新型教育企业转变。在此背景下&#xff0c;教育企业亟需探索多源数据的融合扩展&#xff0c;以应对复杂的业务场景…

2025年人工智能,自动化与机械工程国际学术会议(AIAME2025)

早鸟通道开启&#xff1a; 2025年人工智能&#xff0c;自动化与机械工程国际学术会议&#xff08;AIAME2025&#xff09; 2025 International Conference on Artificial Intelligence, Automation, and Mechanical Engineering 【重要日期】 早鸟征稿截止日期&#xff1a;…

C++ STL 容器系列(三)list —— 编程世界的万能胶,数据结构中的百变精灵

STL系列学习参考&#xff1a; C STL系列__zwy的博客-CSDN博客https://blog.csdn.net/bite_zwy/category_12838593.html 学习C STL的三个境界&#xff0c;会用&#xff0c;明理&#xff0c;能扩展&#xff0c;STL中的所有容器都遵循这个规律&#xff0c;下面我们就按照这三个境…

【AI技术赋能有限元分析应用实践】Abaqus有限元分析到深度学习方法应用全过程——汽车刹车片热力耦合分析

目录 一、项目实现介绍**项目背景****项目目标****项目流程概述****技术融合****项目价值** 二、实现流程**Step 1: 分析问题构建方法&#xff0c;寻找主要分析目标&#xff0c;确定初步目标****Step 2: 使用 Abaqus 完成有限元仿真&#xff0c;后处理并保存数据为 odb 格式***…

从0开始linux(38)——线程(1)线程概念

欢迎来到博主专栏&#xff1a;从0开始linux 博主ID&#xff1a;代码小豪 文章目录 进程与线程线程概念线程的优点线程的独立数据 进程与线程 如果要理解线程&#xff0c;那么进程将会时绕不开的点。首先我们回顾一下我们之前在进程章节当中是如何描述进程的&#xff1f; 进程&…

Doge东哥wordpress主题

Doge东哥wordpress主题是一款专为中小型企业设计的WordPress外贸网站模板&#xff0c;它以其现代、专业且用户友好的界面&#xff0c;为企业提供了一个展示产品和服务的理想平台。以下是对该模板的详细描述&#xff1a; 首页设计概览 首页的设计简洁而不失大气&#xff0c;顶…

keil 5. Flash Timeout. Reset the Target and try it again.

使用官方STM32 ST-LINK Utility 烧写软件 KEIL 5, 设置DFP 包支持FLASH烧写算法 Keil 5, Flash Timeout. Reset the Target and try it again.-CSDN博客

MySQL源码编译

华子目录 下载源码包上传并解压安装cmake环境检测make编译make install安装 部署 下载源码包 下载相应源码包mysql5.7编译安装需要boost库&#xff0c;这里官网下载含boost的源码包https://downloads.mysql.com/archives/community/ 上传并解压 [rootmysql-node1 ~]# du -sh …

【Canvas与化学】枣红实心球钙元素图标

【成图】 120*120 大小图 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>钙元素图标 Draft2</title><style type&qu…