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.rhkb.cn/news/482305.html

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

相关文章

Python语法基础(四)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 高阶函数之map 高阶函数就是说&#xff0c;A函数作为B函数的参数&#xff0c;B函数就是高阶函数 map&#xff1a;映射 map(func,iterable) 这个是map的基本语法&#xff0c;…

大模型时代的人工智能基础与实践——基于OmniForce的应用开发教程

《大模型时代的人工智能基础与实践——基于 OmniForce 的应用开发教程》由京东探索研究院及京东教育联袂撰写&#xff0c;图文并茂地介绍传统人工智能和新一代人工智能&#xff08;基于大模型的通用人工智能技术&#xff09;&#xff0c;展示人工智能广阔的应用场景。同时&…

ESP8266 (ESP-01S)烧录固件 和 了解与单片机通信必需的AT指令

ESP8266&#xff08;ESP-01s&#xff09;烧录固件 工具&#xff1a; 需要安装的原装出厂固件库&#xff1a; ESP8266 --接线-- VCC 3.3&#xff08;外接开发板&#xff09; GND GND&#xff08;外接开发板&#xff09; IO0 GND&#xff08;外接…

【操作文档】mysql分区操作步骤.docx

1、建立分区表 执行 tb_intercept_notice表-重建-添加分区.sql 文件&#xff1b; DROP TABLE IF EXISTS tb_intercept_notice_20241101_new; CREATE TABLE tb_intercept_notice_20241101_new (id char(32) NOT NULL COMMENT id,number varchar(30) NOT NULL COMMENT 号码,cre…

S4 UPA of AA :新资产会计概览

通用并行会计&#xff08;Universal Parallel Accounting&#xff09;可以支持每个独立的分类账与其他模块集成&#xff0c;UPA主要是为了支持平行评估、多货币类型、财务合并、多准则财务报告的复杂业务需求 在ML层面UPA允许根据不同的分类账规则对物料进行评估&#xff0c;并…

ScribblePrompt 医学图像分割工具,三种标注方式助力图像处理

ScribblePrompt 的主要目标是简化医学图像的分割过程&#xff0c;这在肿瘤检测、器官轮廓描绘等应用中至关重要。相比依赖大量人工标注数据&#xff0c;该工具允许用户通过少量输入&#xff08;例如简单的涂鸦或点位&#xff09;来引导模型优化分割结果。这种方式减少了医学专家…

jdk各个版本介绍

Java Development Kit&#xff08;JDK&#xff09;是Java平台的核心组件&#xff0c;它包含了Java编程语言、Java虚拟机&#xff08;JVM&#xff09;、Java类库以及用于编译、调试和运行Java应用程序的工具。 JDK 1.0-1.4&#xff08;经典时代&#xff09; • JDK 1.0&#xff…

【Python爬虫五十个小案例】爬取猫眼电影Top100

博客主页&#xff1a;小馒头学python 本文专栏: Python爬虫五十个小案例 专栏简介&#xff1a;分享五十个Python爬虫小案例 &#x1f40d;引言 猫眼电影是国内知名的电影票务与资讯平台&#xff0c;其中Top100榜单是影迷和电影产业观察者关注的重点。通过爬取猫眼电影Top10…

Doge东哥wordpress主题

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

【力扣】541.反转字符串2

问题描述 思路解析 每当字符达到2*k的时候&#xff0c;判断&#xff0c;同时若剩余字符>k,只对前k个进行判断&#xff08;这是重点&#xff09;因为字符串是不可变变量&#xff0c;所以将其转化为字符串数组&#xff0c;最后才将结果重新转变为字符串 字符串->字符数组 …

C++练级计划-> 《IO流》iostream fstream sstream详解

如果是想全部过一遍就看完&#xff0c;如果想具体的了解某一个请点目录。因为有三种流的使用可能内容多 目录 流是什么&#xff1f; CIO流&#xff08;iostream&#xff09; io流的注意事项 cin和cout为什么能直接识别出类型和数据 fstream fstream的使用方法&#xff…

EDA软件研发的DevOps平台

1&#xff1a;什么是DevOps DevOps是十几年前&#xff0c;在互联网比较火的词&#xff0c;实际上就是ci/cd平台的另外一种说法&#xff0c;核心是说打破研发&#xff0c;测试&#xff0c;运维的边界&#xff0c;能够将整个产品开发的流程快速循环起来&#xff0c;随时可发版&a…

自动化是语法,智能化是语义与语用

自动化与智能化可以从语言学的角度来进行类比和探讨。 1. 自动化是语法 自动化可以类比为“语法”的部分&#xff0c;因为它关注的是操作过程的规则、结构和执行方式。语法是语言中关于词汇、句子结构和规则的系统&#xff0c;它提供了语言运作的框架和规范。类似地&#xff0c…

Spring源码-Bean的生命周期和模板方法

Bean的生命周期 之前我们提到过SpringApplication的run方法不光可以启动程序&#xff0c;还会返回一个容器&#xff0c;为了演示Bean的从创建到销毁的整个阶段&#xff0c;我们会关闭掉容器。 接下来我们来看想要加入到容器当中的类&#xff0c;映入眼帘的是这几个方法和其注解…

Mysql数据库基础篇笔记

目录 sql语句 DDL——数据库定义语言&#xff08;定义库&#xff0c;表&#xff0c;字段&#xff09; 数据库操作&#xff1a; 表操作&#xff1a; DML 增删改语句 DQL 语法编写顺序&#xff1a; 条件查询 DCL 用户管理&#xff1a; 权限管理&#xff1a; 函数 常见字符串内置函…

算法编程题-煎饼排序 不含AAA或者BBB的字符串

算法编程题-煎饼排序 &&不含AAA或者BBB的字符串 煎饼排序原题描述思路简述代码实现复杂度分析 不含AAA或者BBB的字符串原题描述思路简述代码实现复杂度分析 摘要&#xff1a;本文将对两道LeetCode原题进行介绍&#xff0c;分别是煎饼排序和不含AAA或者BBB的字符串。在陈…

分享一款 Vue 图片编辑插件 (推荐)

&#x1f4a5;本篇文章给大家分享一款强大到没朋友的Vue图片编辑插件&#xff0c;可以对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本等&#xff0c;快来试试并收藏吧&#xff01;&#x1f495; 这是一款对图片进行旋转、缩放、裁剪、涂鸦、标注、添加文本在线处理的图片处…

MySQL 核心基础 | Pandaer杂货铺

MySQL一个后端开发必须会接触的中间件&#xff0c;也是关系型数据库的代表。如果你希望看下去这篇文章&#xff0c;需要你有使用MySQL或者相关关系型数据库的经验&#xff0c;不然这篇文章在你眼中就会索然无味了。 这篇文章不会讲解如何使用MySQL&#xff0c;例如如何安装&am…

【网络】应用层协议HTTPHTTPcookie与sessionHTTPS协议原理

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;计算机网络原理_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.应用层协议HTTP 2.认识 URL 2.1 urlencode 和 urldecode 3.HTTP 协议请求与响应格式 3.1 HTTP 请求 3.2 HTTP 响应 …

搭建业务的性能优化指南

这是一篇搭建业务优化的心路历程&#xff0c;也是写给搭建业务的性能优化指南。 前言 直到今天&#xff0c;淘内的页面大多都迁移到了 SSR&#xff0c;从我们终端平台 - 搭建研发团队的视角看&#xff0c;业务大致可以分为两类 —— 搭建派 和 源码派。 这两者互不冲突&#xf…