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>
相关文章:
Android -- 简易音乐播放器
Android – 简易音乐播放器 播放器功能:* 1. 播放模式:单曲、列表循环、列表随机;* 2. 后台播放(单例模式);* 3. 多位置同步状态回调;处理模块:* 1. 提取文件信息:音频文…...
【开源免费】基于Vue和SpringBoot的技术交流分享平台(附论文)
博主说明:本文项目编号 T 053 ,文末自助获取源码 \color{red}{T053,文末自助获取源码} T053,文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…...
Python异步编程新写法:asyncio模块的最新实践
Python异步编程新写法:asyncio模块的最新实践 引言1. 异步编程基础2. 旧写法的问题3. 最新的写法4. 代码解析5. 最佳实践6. 总结7. 参考资料 引言 在现代编程中,异步编程已经成为提高程序性能和响应能力的重要手段。Python的asyncio模块为开发者提供了一…...
【Docker】Docker配置远程访问
配置Docker的远程访问,你需要按照以下步骤进行操作: 1. 在Docker宿主机上配置Docker守护进程监听TCP端口 Docker守护进程默认只监听UNIX套接字,要实现远程访问,需要修改配置以监听TCP端口。 方法一:修改Docker服务…...
网络安全入门之网络安全工具分享-含初期所有工具(附百度网盘链接)
网络安全基础工具 抓包工具 burpsuite 这是一款十分经典的抓包改包工具,在全球范围内使用十分广泛,并且其内置各种插件,具有爆破,自动识别验证码,加解密发包等多种功能 专业版破解网盘链接: 通过百度网…...
玩转 uni-app 静态资源 static 目录的条件编译
一. 前言 老生常谈,了解 uni-app 的开发都知道,uni-app 可以同时支持编译到多个平台,如小程序、H5、移动端 App 等。它的多端编译能力是 uni-app 的一大特点,让开发者可以使用同一套代码基于 Vue.js 的语法编写程序,然…...
Docker 容器隔离关键技术:Seccomp
Docker 容器隔离关键技术:Seccomp 在 Docker 容器中,Seccomp(Secure Computing Mode) 是一种内核安全机制,用来限制容器内的程序可以调用哪些系统调用(Syscalls)。通过列清单的方式,…...
【大模型】深度解析 NLP 模型5大评估指标及 应用案例:从 BLEU、ROUGE、PPL 到METEOR、BERTScore
在自然语言处理(NLP)领域,无论是机器翻译、文本生成,还是问答系统开发,模型性能评估指标始终是开发者绕不开的工具。BLEU、ROUGE、PPL(困惑度)、METEOR 和 BERTScore 是五个最具代表性的指标&am…...
LinuxC高级
gdb调试工具 gdb调试的作用 gdb用于调试代码中逻辑错误,而非语法错误 gdb调试流程 生成可以使用gdb调试的执行文件 gcc -g xxx.c ---> 生成的文件可以使用gdb调试 进入gdb工具 gdb 可执行文件 ---> 使用gdb工具开始调试可执行文件 r/run:运行代码 …...
实现PDF文档加密,访问需要密码
01. 背景 今天下午老板神秘兮兮的来问我,能不能做个文档加密功能,就是那种用户下载打开需要密码才能打开的那种效果。boss都发话了,那必须可以。 需求:将pdf文档经过加密处理,客户下载pdf文档,打开文档需要…...
LangChain——加载知识库文本文档 PDF文档
文档加载 这涵盖了如何加载目录中的所有文档。 在底层,默认情况下使用 UnstructedLoader。需要安装依赖 pip install unstructuredpython导入方式 from langchain_community.document_loaders import DirectoryLoader我们可以使用 glob 参数来控制加载特定类型文…...
深度学习2:从零开始掌握PyTorch:数据操作不再是难题
文章目录 一、导读二、张量的定义与基本操作三、广播机制四、索引与切片五、内存管理六、与其他Python对象的转换本文是经过严格查阅相关权威文献和资料,形成的专业的可靠的内容。全文数据都有据可依,可回溯。特别申明:数据和资料已获得授权。本文内容,不涉及任何偏颇观点,…...
MyBatis的if标签的基本使用
在MyBatis框架中,if标签用于在构建SQL语句时,根据参数条件判断的结果,动态地选择加入或不加where条件中。 一 常见使用 在使用MyBatis处理查询逻辑的时候,常用的是判断一些参数是否为空,列举常用的几种情况展示 1.1…...
【Azure Cache for Redis】Redis的导出页面无法配置Storage SAS时通过az cli来完成
问题描述 在Azure Redis的导出页面,突然不能配置Storage Account的SAS作为授权方式。 image.png 那么是否可以通过AZ CLI或者是Powershell来实现SAS的配置呢? 问题解答 可以的。使用 az redis export 可以实现 az redis export --container --prefix[--a…...
【微服务】Nacos
一、安装 1、官网地址:https://nacos.io/download/nacos-server/ 2、启动:找到bin目录下的startup.cmd双击启动,或者打开一个命令窗口输入: startup.cmd -m standalone双击启动后如下:可以访问控制台地址 访问后的…...
5、定义与调用函数
大家好,欢迎来到Python函数入门课程! 在编程中,函数就像一个可以重复使用的代码块,它接受输入(参数),执行特定的任务,并可能返回一个结果。想象一下,函数就像一个厨房里的搅拌机,你放入水果(参数),按下按钮(调用函数),它就会帮你制作出美味的果汁(返回值)。…...
Linux 网络编程之TCP套接字
前言 上一期我们对UDP套接字进行了介绍并实现了简单的UDP网络程序,本期我们来介绍TCP套接字,以及实现简单的TCP网络程序! 🎉目录 前言 1、TCP 套接字API详解 1.1 socket 1.2 bind 1.3 listen 1.4 accept 1.5 connect 2、…...
前海湾地铁的腾通数码大厦背后的临时免费停车点探寻
临时免费停车点:前海湾地铁的腾通数码大厦背后的桂湾大街,目前看不仅整条桂湾大街停了车,而且还有工地餐点。可能是这个区域还是半工地状态,故暂时还不会有罚单的情况出现。 中建三局腾讯数码大厦项目部A栋 广东省深圳市南山…...
OpenCV相机标定与3D重建(7)鱼眼镜头立体校正的函数stereoRectify()的使用
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::fisheye::stereoRectify 是 OpenCV 中用于鱼眼镜头立体校正的函数。该函数计算两个相机之间的校正变换,使得从两个相机拍摄的图像…...
前端如何获取unpkg的资源链接
在现代前端开发中,快速获取和使用npm包是一个常见需求。unpkg是一个全球性的CDN服务,它为npm上的每个包提供了快速访问。通过unpkg,你可以轻松地通过URL获取任何npm包的文件。本文将介绍如何获取unpkg的资源链接。 unpkg简介 unpkg是一个快…...
Flink 离线计算
文章目录 一、样例一:读 csv 文件生成 csv 文件二、样例二:读 starrocks 写 starrocks三、样例三:DataSet、Table Sql 处理后写入 StarRocks四、遇到的坑 <dependency><groupId>org.apache.flink</groupId><artifactId&…...
Git | 理解团队合作中Git分支的合并操作
合并操作 团队合作中Git分支的合并操作分支合并过程1.创建feature/A分支的过程2. 创建分支feature/A-COPY3.合并分支查看代码是否改变 团队合作中Git分支的合并操作 需求 假设团队项目中的主分支是main,团队成员A基于主分支main创建了feature/A,而我又在团队成员A创…...
C++多态的实现原理
【欢迎关注编码小哥,学习更多实用的编程方法和技巧】 1、类的继承 子类对象在创建时会首先调用父类的构造函数 父类构造函数执行结束后,执行子类的构造函数 当父类的构造函数有参数时,需要在子类的初始化列表中显式调用 Child(int i) : …...
[极客大挑战 2019]PHP--详细解析
信息搜集 想查看页面源代码,但是右键没有这个选项。 我们可以ctrlu或者在url前面加view-source:查看: 没什么有用信息。根据页面的hint,我们考虑扫一下目录看看能不能扫出一些文件. 扫到了备份文件www.zip,解压一下查看网站源代码…...
map用于leetcode
//第一种map方法 function groupAnagrams(strs) {let map new Map()for (let str of strs) {let key str ? : str.split().sort().join()if (!map.has(key)) {map.set(key, [])}map.get(key).push(str)} //此时map为Map(3) {aet > [ eat, tea, ate ],ant > [ tan,…...
CommonJS 和 ES Modules 的 区别
CommonJS 和 ES Modules 的 区别 1. CommonJS 和 ES Modules 区别?1.1 语法差异CommonJS:ES Modules: 1.2. 加载机制CommonJS:ES Modules: 1.3. 运行时行为CommonJS:ES Modules: 1.4. 兼容性和使用场景Com…...
科技为翼 助残向新 高德地图无障碍导航规划突破1.5亿次
今年12月03日是第33个国际残疾人日。在当下科技发展日新月异的时代,如何让残障人士共享科技红利、平等地参与社会生活,成为当前社会关注的热点。 中国有超过8500万残障人士,其中超过2400万为肢残人群,视力障碍残疾人数超过1700万…...
Flink四大基石之Time (时间语义) 的使用详解
目录 一、引言 二、Time 的分类及 EventTime 的重要性 Time 分类详述 EventTime 重要性凸显 三、Watermark 机制详解 核心原理 Watermark能解决什么问题,如何解决的? Watermark图解原理 举例 总结 多并行度的水印触发 Watermark代码演示 需求 代码演示ÿ…...
Spring WebFlux与Spring MVC
Spring WebFlux 是对 Spring Boot 项目中传统 Spring MVC 部分的一种替代选择,主要是为了解决现代 Web 应用在高并发和低延迟场景下的性能瓶颈。 1.WebFlux 是对 Spring MVC 的替代 架构替代: Spring MVC 使用的是基于 Servlet 规范的阻塞式模型…...
【深度学习基础】一篇入门模型评估指标(分类篇)
🌈 个人主页:十二月的猫-CSDN博客 🔥 系列专栏: 🏀深度学习_十二月的猫的博客-CSDN博客 💪🏻 十二月的寒冬阻挡不了春天的脚步,十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. 模…...
注销公司的步骤和流程/seo软件推荐
处理发来的URL只是MVC中的一部分,我们也需要生成一些URL植入到我们的view中,让用户点击,并提交表单到目标controller和action,下面会介绍一些生成URL的技巧。 最快速直接的定义外链URL的方法就是手动拼写。比如下面的URL会被放置在…...
wordpress get term/原创文章代写平台
(1)注册登录:注册普通账号登录;登录后可以修改用户的基本信息,也可以退出。 (2)浏览资讯:浏览网站管理发布的资讯,可以评论,评论后需要管理员审核和查看。也可以收藏资讯。 (3)关于我们:浏览网…...
网站开发及app开发公司/短视频代运营合作方案
免费获得《2017阿里技术年度精选》(678页),下载地址见文中说明2017年,在技术发展的历史上,一定是个特别的一年:柯洁与AlphaGo的惊世大战,无人咖啡店开放体验,AI设计师“鲁班”横空出…...
上海网站推广很好/成人职业技能培训学校
做为系统管理员可能会面对的任务:1.自动批量安装操作系统2.完成系统的本地化 (配置现成的发行版或者软件包,以求符合自己的需要,本地安全规定、文 件存放和网络拓扑的需要,这个过程称为“本地化”)3.给系统打补丁且保持系统的更新 4.管理附加的软件包 程…...
郑州小程序开发多少钱/宁波seo营销
C中的::的作用 2018-06-08 13:47:46 一米阳光-ing 阅读数 8036更多 分类专栏: C/C (1)作用域限定符,当在类体中直接定义函数时,不需要在函数名字的前面加上类名,但是在类体外实现函数定义的时候,必须加上类名并且加…...
专业网站建设推荐/seo网站排名优化服务
对于搜索引擎, 在索引量和搜索量大到一定程度的时候, 索引更新的效率会逐渐降低, 服务器的压力逐渐升高, 因此基本上整个搜索引擎的利用率可以说是越来越低了, 并且随着海量数据存储带来的困难, 设计一个良好的分布式搜索引擎将是一个搜索引擎能否面相未来发展的关键因素了. 那…...