A面页面优化,播放逻辑优化,功能增加

This commit is contained in:
lihongwei 2024-10-09 18:19:43 +08:00
parent 5b390f4bff
commit adca6a12bc
15 changed files with 872 additions and 336 deletions

View File

@ -24,7 +24,8 @@ public class A_ImportFragmentAdapter extends RecyclerView.Adapter<A_ImportFragme
private Context context;
private List<AudioItem> audioFiles = new ArrayList<>();
private OnDeleteClickListener onDeleteClickListener;
private AudioItem audioItem;
private OnOptionClickListener onOptionClickListener;;
public A_ImportFragmentAdapter(Context context) {
this.context = context;
@ -38,18 +39,12 @@ public class A_ImportFragmentAdapter extends RecyclerView.Adapter<A_ImportFragme
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
AudioItem audioItem = audioFiles.get(position);
audioItem = audioFiles.get(position);
holder.title.setText(audioItem.getName());
holder.time.setText(audioItem.getDuration());
Log.d("Adapter", "onBindViewHolder: " + audioItem.getDuration());
holder.deleteButton.setOnClickListener(v -> {
if (onDeleteClickListener != null) {
onDeleteClickListener.onDeleteClick(audioItem.getFile());
}
});
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -71,27 +66,45 @@ public class A_ImportFragmentAdapter extends RecyclerView.Adapter<A_ImportFragme
notifyDataSetChanged(); // Update UI when the data changes
}
public void setOnDeleteClickListener(OnDeleteClickListener listener) {
this.onDeleteClickListener = listener;
public void updateTitle(int position, String newTitle) {
if (position >= 0 && position < audioFiles.size()) {
audioFiles.get(position).setName(newTitle); // 假设 AudioItem 有一个 setName 方法
notifyItemChanged(position); // 通知 RecyclerView 更新该项
}
}
public interface OnDeleteClickListener {
void onDeleteClick(String filePath);
public void setOnOptionClickListener(OnOptionClickListener listener) {
this.onOptionClickListener = listener;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public interface OnOptionClickListener {
void onOptionClick(int position, String filePath, View anchorView);
}
ImageView deleteButton;
class ViewHolder extends RecyclerView.ViewHolder {
ImageView option;
TextView title;
TextView time;
public ViewHolder(@NonNull View itemView) {
super(itemView);
deleteButton = itemView.findViewById(R.id.options);
option = itemView.findViewById(R.id.options);
title = itemView.findViewById(R.id.title);
time = itemView.findViewById(R.id.time);
option.setOnClickListener(v -> {
int position = getBindingAdapterPosition();
if (position != RecyclerView.NO_POSITION && onOptionClickListener != null) {
onOptionClickListener.onOptionClick(position, audioItem.getFile(), option);
}
});
}
}

View File

@ -5,6 +5,7 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
@ -12,6 +13,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;
import androidx.annotation.Nullable;
@ -19,6 +21,7 @@ import androidx.core.app.NotificationCompat;
import androidx.lifecycle.MutableLiveData;
import com.hi.music.player.R;
import com.hi.music.player.javabean.A_data.AudioItem;
import com.hi.music.player.ui.activity.A_PlayActivity;
import java.io.File;
@ -26,15 +29,18 @@ import java.io.IOException;
public class MusicPlayerForegroundService extends Service {
// 通知渠道ID用于创建通知渠道
// 通知渠道ID
public static final String CHANNEL_ID = "MusicPlayerChannel";
private MediaPlayer mediaPlayer; // MediaPlayer实例用于播放音频
private MediaPlayer mediaPlayer; // 播放音频的MediaPlayer实例
private final IBinder binder = new MusicBinder(); // 用于绑定服务的Binder
private final MutableLiveData<Boolean> isPlaying = new MutableLiveData<>(false); // 用于跟踪播放状态的LiveData
private final MutableLiveData<String> fileName = new MutableLiveData<>(); // 保存当前播放的文件名
private String currentAudioPath = null; // 用于保存当前正在播放的音频路径
private final MutableLiveData<Boolean> isPlaying = new MutableLiveData<>(false); // 播放状态
private final MutableLiveData<String> fileName = new MutableLiveData<>(); // 当前播放的文件名
private AudioItem audioItem; // 当前播放的音频项
private String currentAudioPath = null; // 当前播放的音频路径
private PowerManager.WakeLock wakeLock; // 用于防止设备休眠
private boolean isLooping = true; // 循环播放标志
// 自定义Binder类允许客户端绑定到服务
// 自定义Binder类
public class MusicBinder extends Binder {
public MusicPlayerForegroundService getService() {
return MusicPlayerForegroundService.this; // 返回当前服务实例
@ -44,192 +50,203 @@ public class MusicPlayerForegroundService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder; // 返回用于绑定服务的Binder
return binder; // 返回Binder
}
// 服务启动时调用该方法
// 服务启动时调用
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 从Intent中获取传入的音频路径
String newAudioPath = intent.getStringExtra("Path");
Log.d("MusicPlayerService", "Service started");
audioItem = (AudioItem) intent.getSerializableExtra("Path"); // 获取音频路径
if (newAudioPath != null) {
// 如果传入的音频路径与当前播放路径不同则重新初始化播放器
if (currentAudioPath == null || !newAudioPath.equals(currentAudioPath)) {
if (audioItem != null) {
String newAudioPath = audioItem.getFile();
// 如果新音频路径不同于当前路径则重新初始化播放器
if (newAudioPath != null && !newAudioPath.equals(currentAudioPath)) {
Log.d("MusicPlayerService", "检测到新的音频路径,初始化播放器");
currentAudioPath = newAudioPath; // 更新当前播放路径
stopAndResetMediaPlayer(); // 停止并重置当前的播放器状态
initializePlayer(newAudioPath); // 初始化播放器并开始播放新的音频
currentAudioPath = newAudioPath; // 更新路径
stopAndResetMediaPlayer(); // 停止并重置播放器
initializePlayer(newAudioPath); // 初始化播放器
} else {
Log.d("MusicPlayerService", "音频路径相同,无需重新初始化播放器");
}
} else {
stopSelf(); // 如果路径为空停止服务
stopSelf(); // 路径为空停止服务
}
// 启动前台服务并显示通知
startForeground(1, createNotification());
return START_NOT_STICKY; // 返回START_NOT_STICKY表示服务不会在被系统杀死后自动重启
startForeground(1, createNotification()); // 启动前台服务并显示通知
acquireWakeLock(); // 获取WakeLock
return START_NOT_STICKY; // 服务不会在被杀死后自动重启
}
// 创建通知用于前台服务显示
// 创建通知
private Notification createNotification() {
createNotificationChannel(); // 创建通知渠道适用于Android O及以上版本
// 创建点击通知时打开的Intent
createNotificationChannel(); // 创建通知渠道
Intent notificationIntent = new Intent(this, A_PlayActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_IMMUTABLE : 0); // 适配Android 12
// 构建通知设置标题内容和小图标
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Playing Audio") // 通知标题
.setContentText("Your audio is playing") // 通知内容
.setSmallIcon(R.drawable.home_select) // 通知的小图标
.setContentIntent(pendingIntent) // 点击通知的Intent
.setPriority(NotificationCompat.PRIORITY_LOW) // 低优先级避免干扰用户
.setContentTitle("正在播放音频") // 通知标题
.setContentText("您的音频正在播放") // 通知内容
.setSmallIcon(R.drawable.home_select) // 小图标
.setContentIntent(pendingIntent) // 点击通知的Intent
.setPriority(NotificationCompat.PRIORITY_LOW) // 低优先级
.build();
}
// 创建通知渠道适用于Android O及以上版本
// 创建通知渠道
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID, "Music Player Channel", NotificationManager.IMPORTANCE_LOW);
CHANNEL_ID, "音乐播放器频道", NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(serviceChannel); // 创建通知渠道
manager.createNotificationChannel(serviceChannel); // 创建渠道
}
}
}
// 停止并重置播放器确保在播放新的音频时播放器状态正确
private void stopAndResetMediaPlayer() {
// 停止并重置播放器
public void stopAndResetMediaPlayer() {
if (mediaPlayer != null) {
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop(); // 停止正在播放的音频
mediaPlayer.stop(); // 停止播放
}
mediaPlayer.reset(); // 重置播放器状态使其可以播放新的音频
mediaPlayer.reset(); // 重置播放器
}
}
// 初始化媒体播放器并设置数据源
// 初始化播放器
private void initializePlayer(String path) {
try {
Log.d("MusicPlayerService", "正在初始化播放器,音频路径: " + path);
if (mediaPlayer == null) {
mediaPlayer = new MediaPlayer(); // 如果MediaPlayer为空则创建一个新的实例
mediaPlayer = new MediaPlayer(); // 创建新的MediaPlayer实例
}
// 设置当前播放的文件名
fileName.setValue(getFileNameFromPath(path));
fileName.setValue(getFileNameFromPath(path)); // 设置当前文件名
setDataSource(path); // 设置数据源
// 根据传入的音频路径类型设置数据源
setDataSource(path);
mediaPlayer.prepareAsync(); // 异步准备播放器
mediaPlayer.prepareAsync(); // 异步准备
mediaPlayer.setOnPreparedListener(mp -> {
mediaPlayer.start(); // 播放器准备好后开始播放
isPlaying.postValue(true); // 更新播放状态播放中
isPlaying.postValue(true); // 更新播放状态
});
// 设置播放完成后的监听
// 播放完成监听
mediaPlayer.setOnCompletionListener(mp -> {
isPlaying.postValue(false); // 更新播放状态为未播放
stopSelf(); // 播放完成后停止服务
if (isLooping) {
mediaPlayer.seekTo(0); // 循环播放
mediaPlayer.start();
} else {
isPlaying.postValue(false); // 更新播放状态
stopSelf(); // 播放完成后停止服务
}
});
// 设置播放错误的监听器
// 播放错误监听
mediaPlayer.setOnErrorListener((mp, what, extra) -> {
Log.e("MusicPlayerService", "播放时发生错误: " + what + ", 额外信息: " + extra);
isPlaying.postValue(false); // 更新播放状态未播放
isPlaying.postValue(false); // 更新状态
stopSelf(); // 播放出错后停止服务
return true; // 返回true表示错误已经被处理
return true; // 表示错误已处理
});
} catch (IOException | IllegalArgumentException | IllegalStateException e) {
Log.e("MusicPlayerService", "初始化播放器失败,路径: " + path, e);
isPlaying.postValue(false); // 如果初始化失败更新状态未播放
stopSelf(); // 初始化失败后停止服务
isPlaying.postValue(false); // 更新状态
stopSelf(); // 停止服务
}
}
// 设置数据源根据音频路径的类型来加载音频
// 设置数据源
private void setDataSource(String path) throws IOException {
if (path.startsWith("content://")) {
setDataSourceFromContentUri(path); // 如果是内容URI使用内容URI加载
setDataSourceFromContentUri(path); // 使用内容URI加载
} else {
setDataSourceFromFileOrAsset(path); // 否则尝试从文件或资产目录中加载
setDataSourceFromFileOrAsset(path); // 从文件或资产加载
}
}
// 使用内容URI加载音频数据源
// 使用内容URI加载音频
private void setDataSourceFromContentUri(String path) throws IOException {
Uri contentUri = Uri.parse(path); // 将字符串路径解析URI
Uri contentUri = Uri.parse(path); // 解析URI
try (AssetFileDescriptor afd = getContentResolver().openAssetFileDescriptor(contentUri, "r")) {
if (afd != null) {
// 使用文件描述符和偏移量设置MediaPlayer的数据源
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); // 设置数据源
} else {
throw new IOException("无法打开内容URI: " + path);
}
} catch (SecurityException e) {
Log.e("MusicPlayerService", "权限被拒绝无法访问内容URI: " + path, e);
throw new IOException("权限被拒绝", e); // 处理权限异常
}
}
// 尝试从文件或assets目录加载音频数据源
// 从文件或assets加载音频
private void setDataSourceFromFileOrAsset(String path) throws IOException {
try {
// 尝试从assets目录加载音频
AssetFileDescriptor afd = getAssets().openFd(path);
AssetFileDescriptor afd = getAssets().openFd(path); // 从assets加载
mediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
} catch (IOException e) {
// 如果assets加载失败则尝试从文件系统加载音频
File file = new File(path);
if (file.exists()) {
mediaPlayer.setDataSource(file.getAbsolutePath()); // 从文件路径加载
mediaPlayer.setDataSource(file.getAbsolutePath()); // 从文件加载
} else {
throw new IOException("文件未找到: " + path);
}
}
}
// 根据路径获取文件名
// 获取文件名
// 获取文件名不带扩展名
private String getFileNameFromPath(String path) {
try {
return new File(path).getName(); // 从路径中提取文件名
// 创建File对象并获取文件名
String fileName = new File(path).getName();
// 找到最后一个点的位置
int dotIndex = fileName.lastIndexOf('.');
// 如果找到了点返回不带扩展名的文件名
return (dotIndex > 0) ? fileName.substring(0, dotIndex) : fileName;
} catch (Exception e) {
Log.e("MusicPlayerService", "获取文件名失败", e);
return "未知文件"; // 如果失败返回默认文件名
return "未知文件"; // 默认文件名
}
}
// 暂停音频播放
// 暂停播放
public void pauseAudio() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause(); // 如果当前正在播放暂停音频
isPlaying.postValue(false); // 更新播放状态暂停
mediaPlayer.pause(); // 暂停
isPlaying.postValue(false); // 更新状态
}
}
// 恢复音频播放
// 恢复播放
public void resumeAudio() {
if (mediaPlayer != null && !mediaPlayer.isPlaying()) {
mediaPlayer.start(); // 如果当前未播放恢复音频播放
isPlaying.postValue(true); // 更新播放状态播放中
mediaPlayer.start(); // 恢复播放
isPlaying.postValue(true); // 更新状态
}
}
// 获取当前播放状态
// 获取播放状态
public MutableLiveData<Boolean> getIsPlaying() {
return isPlaying;
}
// 获取当前播放的文件名
// 获取当前文件名
public MutableLiveData<String> getFileName() {
return fileName;
}
public String getName() {
return mediaPlayer != null ? audioItem.getName() : null;
}
// 获取当前播放位置毫秒
public int getCurrentPosition() {
return mediaPlayer != null ? mediaPlayer.getCurrentPosition() : 0;
@ -243,17 +260,32 @@ public class MusicPlayerForegroundService extends Service {
// 跳转到指定播放位置
public void seekTo(int position) {
if (mediaPlayer != null) {
mediaPlayer.seekTo(position); // 将播放器跳转到指定位置
mediaPlayer.seekTo(position); // 跳转到指定位置
}
}
// 当服务销毁时释放MediaPlayer资源
// 获取WakeLock
private void acquireWakeLock() {
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager != null) {
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"MusicPlayer::WakeLock");
wakeLock.acquire(10 * 60 * 1000L); // 获取WakeLock防止设备休眠
}
}
// 销毁服务时释放资源
@Override
public void onDestroy() {
super.onDestroy();
// 释放WakeLock
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
wakeLock = null; // 释放后设置为null
}
if (mediaPlayer != null) {
mediaPlayer.release(); // 释放MediaPlayer资源
mediaPlayer = null; // 将播放器设置为null防止再次使用
mediaPlayer = null; // 设置为null
}
}
}

View File

@ -22,194 +22,198 @@ import java.util.Locale;
public class A_HomeActivity extends BaseActivity<ActivityAhomeBinding> {
private final int[] defaultIcons = { R.drawable.home_unselect, R.drawable.import_unselect };
private final int[] selectedIcons = { R.drawable.home_select, R.drawable.import_select };
private MusicPlayerForegroundService musicService; // 音乐播放服务
private boolean isBound = false; // 服务是否绑定
private Handler progressHandler; // 用于更新进度的Handler
private MusicPlayerForegroundService musicService;
private boolean isBound = false;
private Handler progressHandler;
private final int[] defaultIcons = {R.drawable.home_unselect, R.drawable.import_unselect}; // 默认图标
private final int[] selectedIcons = {R.drawable.home_select, R.drawable.import_select}; // 选中图标
@Override
protected ActivityAhomeBinding getViewBinding() {
return ActivityAhomeBinding.inflate(getLayoutInflater()); // 获取视图绑定
}
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
musicService = ((MusicPlayerForegroundService.MusicBinder) service).getService();
isBound = true;
setupObservers();
startUpdatingProgress();
musicService = ((MusicPlayerForegroundService.MusicBinder) service).getService(); // 获取服务实例
isBound = true; // 服务已绑定
updatePlayButtonState(); // 更新播放按钮状态
startUpdatingProgress(); // 启动进度更新
observeIsPlaying(); // 观察播放状态变化
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
isBound = false; // 服务已断开
}
};
@Override
protected ActivityAhomeBinding getViewBinding() {
return ActivityAhomeBinding.inflate(getLayoutInflater());
}
@Override
protected void onCreateInit() {
Intent serviceIntent = new Intent(this, MusicPlayerForegroundService.class);
startService(serviceIntent);
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
initData();
startService(serviceIntent); // 启动音乐播放服务
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); // 绑定服务
// 初始化界面
vb.pause.setOnClickListener(v -> togglePlayPause()); // 设置暂停/播放按钮点击事件
setupProgressBar(); // 设置进度条
setupViewPager(); // 设置ViewPager
setupTabLayout(); // 设置TabLayout
}
@Override
protected void onInitClick() {
// 处理其他点击事件
}
@Override
public boolean isFullScreen() {
return false;
return false; // 不是全屏模式
}
@Override
public boolean statusBarLight() {
return false;
return false; // 状态栏不为浅色
}
private void initData() {
setupViewPager();
setupTabLayout();
setupProgressBar();
setupPauseButton();
// 设置进度条
private void setupProgressBar() {
vb.circularProgressBar.setMaxProgress(100); // 设置进度条最大值
}
// 切换播放/暂停
private void togglePlayPause() {
if (isBound && musicService != null) {
// 判断当前播放状态并切换
if (musicService.getIsPlaying().getValue() != null && musicService.getIsPlaying().getValue()) {
musicService.pauseAudio(); // 暂停播放
} else {
musicService.resumeAudio(); // 恢复播放
}
updatePlayButtonState(); // 更新播放按钮图标
}
}
// 更新播放按钮的状态
private void updatePlayButtonState() {
if (isBound && musicService != null && musicService.getIsPlaying().getValue() != null) {
boolean isPlaying = musicService.getIsPlaying().getValue(); // 获取播放状态
vb.pause.setImageResource(isPlaying ? R.drawable.pause : R.drawable.play); // 更新图标
}
}
// 更新进度
private void updateProgress() {
if (isBound && musicService != null) {
int currentPosition = musicService.getCurrentPosition(); // 当前播放位置
int duration = musicService.getDuration(); // 音频总时长
String fileName = musicService.getFileName().getValue(); // 当前文件名
String name = musicService.getName();//// 当前文件名
vb.bottomText1.setText(formatTime(currentPosition)); // 更新当前时间文本
vb.bottomText2.setText(formatTime(duration)); // 更新总时间文本
vb.topText.setText(name); // 更新文件名文本
if (duration > 0) {
int progress = (int) ((currentPosition / (float) duration) * 100); // 计算进度
vb.circularProgressBar.setProgress(progress); // 更新进度条
}
}
}
// 启动进度更新
private void startUpdatingProgress() {
if (progressHandler != null) return; // 如果已存在Handler则返回
progressHandler = new Handler(Looper.getMainLooper());
progressHandler.postDelayed(new Runnable() {
@Override
public void run() {
updateProgress(); // 更新进度
progressHandler.postDelayed(this, 1000); // 每秒更新一次
}
}, 1000);
}
// 观察播放状态变化
private void observeIsPlaying() {
if (isBound && musicService != null) {
musicService.getIsPlaying().observe(this, isPlaying -> {
if (isPlaying != null) {
vb.pause.setImageResource(isPlaying ? R.drawable.pause : R.drawable.play); // 更新播放按钮图标
}
});
}
}
// 设置ViewPager
private void setupViewPager() {
A_HomeViewPagerAdapter adapter = new A_HomeViewPagerAdapter(this);
vb.homeViewpager.setAdapter(adapter);
vb.homeViewpager.setUserInputEnabled(false);
A_HomeViewPagerAdapter adapter = new A_HomeViewPagerAdapter(this); // 创建适配器
vb.homeViewpager.setAdapter(adapter); // 设置适配器
vb.homeViewpager.setUserInputEnabled(false); // 禁用用户输入
}
// 设置TabLayout
private void setupTabLayout() {
new TabLayoutMediator(vb.homeTabLayout, vb.homeViewpager, (tab, position) -> {
HomeTabCustomBinding tabBinding = HomeTabCustomBinding.inflate(LayoutInflater.from(this));
tab.setCustomView(tabBinding.getRoot());
tabBinding.homeIcon.setImageResource(defaultIcons[position]);
}).attach();
HomeTabCustomBinding tabBinding = HomeTabCustomBinding.inflate(LayoutInflater.from(this)); // 自定义Tab布局
tab.setCustomView(tabBinding.getRoot()); // 设置自定义视图
tabBinding.homeIcon.setImageResource(defaultIcons[position]); // 设置默认图标
}).attach(); // 附加TabLayout和ViewPager
vb.homeTabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
updateTabIcon(tab, true);
updateTabIcon(tab, true); // 更新选中图标
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
updateTabIcon(tab, false);
updateTabIcon(tab, false); // 更新未选中图标
}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
public void onTabReselected(TabLayout.Tab tab) {
// 不需要处理
}
});
// 默认选择第一个标签
TabLayout.Tab firstTab = vb.homeTabLayout.getTabAt(0);
if (firstTab != null) {
firstTab.select();
updateTabIcon(firstTab, true);
firstTab.select(); // 选择第一个标签
updateTabIcon(firstTab, true); // 更新选中图标
}
}
private void setupProgressBar() {
vb.circularProgressBar.setMaxProgress(100);
vb.circularProgressBar.setOnProgressChangeListener((progressBar, progress) -> {
if (isBound && musicService != null) {
musicService.seekTo((int) ((progress / 100.0) * musicService.getDuration()));
}
});
}
private void setupPauseButton() {
vb.pause.setOnClickListener(v -> {
if (isBound && musicService != null) {
if (Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) {
musicService.pauseAudio();
} else {
musicService.resumeAudio();
}
}
});
}
private void setupObservers() {
if (musicService == null || isBound == false) return; // 确保只在服务绑定时设置观察者
musicService.getIsPlaying().observe(this, isPlaying -> {
if (isPlaying != null) {
vb.pause.setImageResource(isPlaying ? R.drawable.pause : R.drawable.play);
if (isPlaying) {
startUpdatingProgress(); // 只有在播放时才开始更新进度
} else {
stopUpdatingProgress(); // 暂停更新
}
}
});
}
private void startUpdatingProgress() {
if (progressHandler != null) return; // 确保只有一个 Handler 在工作
progressHandler = new Handler(Looper.getMainLooper());
progressHandler.postDelayed(updateProgressRunnable, 1000);
}
private void stopUpdatingProgress() {
if (progressHandler != null) {
progressHandler.removeCallbacks(updateProgressRunnable);
progressHandler = null;
}
}
private final Runnable updateProgressRunnable = new Runnable() {
@Override
public void run() {
if (isBound && musicService != null && Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) {
updateProgressAndDuration();
progressHandler.postDelayed(this, 1000);
}
}
};
private void updateProgressAndDuration() {
int currentPosition = musicService.getCurrentPosition();
int duration = musicService.getDuration();
if (duration > 0) {
vb.circularProgressBar.setProgress((int) ((currentPosition / (float) duration) * 100));
vb.bottomText.setText(formatTime(currentPosition));
vb.topText.setText(formatTime(duration));
}
}
private String formatTime(int milliseconds) {
int minutes = (milliseconds / 1000) / 60;
int seconds = (milliseconds / 1000) % 60;
return String.format(Locale.getDefault(), "%d:%02d", minutes, seconds);
}
// 更新Tab图标
private void updateTabIcon(TabLayout.Tab tab, boolean isSelected) {
View customView = tab.getCustomView();
if (customView != null) {
HomeTabCustomBinding tabBinding = HomeTabCustomBinding.bind(customView);
tabBinding.homeIcon.setImageResource(isSelected ? selectedIcons[tab.getPosition()] : defaultIcons[tab.getPosition()]);
tabBinding.homeIcon.setImageResource(isSelected ? selectedIcons[tab.getPosition()] : defaultIcons[tab.getPosition()]); // 更新图标
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (isBound) {
unbindService(serviceConnection);
isBound = false;
unbindService(serviceConnection); // 解绑服务
if (progressHandler != null) {
progressHandler.removeCallbacksAndMessages(null); // 移除所有回调
}
stopUpdatingProgress(); // 确保停止更新进度
}
// 格式化时间
private String formatTime(int milliseconds) {
int minutes = (milliseconds / 1000) / 60; // 计算分钟
int seconds = (milliseconds / 1000) % 60; // 计算秒数
return String.format(Locale.getDefault(), "%d:%02d", minutes, seconds); // 格式化为"分钟:秒"
}
@Override
public void onClick(View v) {
// Handle additional click events if needed
// 处理其他点击事件
}
}

View File

@ -4,45 +4,58 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.hi.music.player.R;
import com.hi.music.player.databinding.ActivityAplayBinding;
import com.hi.music.player.javabean.A_data.AudioItem;
import com.hi.music.player.service.MusicPlayerForegroundService;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class A_PlayActivity extends BaseActivity<ActivityAplayBinding> {
private MusicPlayerForegroundService musicService;
private boolean isBound = false;
private Handler progressHandler;
private MusicPlayerForegroundService musicService; // 音乐播放服务
private boolean isBound = false; // 服务绑定状态
private boolean isBackground; // 背景状态
private Handler progressHandler; // 用于更新进度的Handler
private List<TextView> textViews = new ArrayList<>(); // 存储变换颜色的文字
// 服务连接对象
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MusicPlayerForegroundService.MusicBinder binder = (MusicPlayerForegroundService.MusicBinder) service;
musicService = binder.getService();
musicService = ((MusicPlayerForegroundService.MusicBinder) service).getService();
isBound = true;
// 设置一次观察者
// 设置观察者
musicService.getIsPlaying().observe(A_PlayActivity.this, this::updatePlayButton);
musicService.getFileName().observe(A_PlayActivity.this, vb.songTitle::setText);
// musicService.getFileName().observe(A_PlayActivity.this, vb.songTitle::setText);
vb.songTitle.setText(musicService.getName());
startUpdatingProgress();
startUpdatingProgress(); // 启动进度更新
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
isBound = false; // 服务断开时更新状态
}
// 更新播放按钮状态
private void updatePlayButton(Boolean isPlaying) {
if (isPlaying != null) {
vb.playButton.setBackgroundResource(isPlaying ? R.drawable.pause : R.drawable.play);
@ -57,34 +70,46 @@ public class A_PlayActivity extends BaseActivity<ActivityAplayBinding> {
@Override
protected void onCreateInit() {
Intent intent = getIntent();
AudioItem audioItem = (AudioItem) intent.getSerializableExtra("Path");
textViews.add(vb.songTitle);
textViews.add(vb.artistAlbumText);
textViews.add(vb.current);
textViews.add(vb.time);
textViews.add(vb.nowPlayingText);
loadBackgroundPreference(); // 加载之前保存的背景状态
// 从Intent获取音频项
AudioItem audioItem = (AudioItem) getIntent().getSerializableExtra("Path");
if (audioItem == null) {
finish();
finish(); // 若未获取到音频路径则结束Activity
return;
}
startMusicService(audioItem.getFile());
setupPlayButtonClickListener();
vb.songSeekbar.setOnSeekBarChangeListener(new SeekBarListener());
startMusicService(audioItem); // 启动音乐服务
setupPlayButtonClickListener(); // 设置播放按钮的点击事件
}
@Override
protected void onInitClick() {
vb.songSeekbar.setOnSeekBarChangeListener(new SeekBarListener()); // 设置进度条监听
vb.repeatButton.setOnClickListener(v -> showTimerDialog()); // 设置定时器按钮的点击事件
vb.prevButton.setOnClickListener(v -> toggleBackground()); // 设置切换背景按钮的点击事件
}
private void startMusicService(String path) {
// 启动音乐服务并绑定
private void startMusicService(AudioItem audioItem) {
Intent serviceIntent = new Intent(this, MusicPlayerForegroundService.class);
serviceIntent.putExtra("Path", path);
Log.d("A_PlayActivity", "Path: " + path);
startService(serviceIntent);
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
serviceIntent.putExtra("Path", audioItem);
Log.d("A_PlayActivity", "Path: " + audioItem);
startService(serviceIntent); // 启动服务
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); // 绑定服务
}
// 设置播放按钮的点击事件
private void setupPlayButtonClickListener() {
vb.playButton.setOnClickListener(v -> {
if (isBound && musicService != null) {
// 根据播放状态切换播放/暂停
if (Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) {
musicService.pauseAudio();
} else {
@ -93,27 +118,30 @@ public class A_PlayActivity extends BaseActivity<ActivityAplayBinding> {
}
});
vb.backButton.setOnClickListener(v -> finish());
vb.backButton.setOnClickListener(v -> finish()); // 返回按钮点击事件
}
// 启动进度更新
private void startUpdatingProgress() {
if (progressHandler == null) {
progressHandler = new Handler(Looper.getMainLooper());
progressHandler.postDelayed(new Runnable() {
@Override
public void run() {
updateSeekBarAndTime();
progressHandler.postDelayed(this, 1000);
updateSeekBarAndTime(); // 更新进度条和时间
progressHandler.postDelayed(this, 1000); // 每秒更新
}
}, 1000);
}
}
// 更新进度条和时间显示
private void updateSeekBarAndTime() {
if (isBound && musicService != null && Boolean.TRUE.equals(musicService.getIsPlaying().getValue())) {
int currentPosition = musicService.getCurrentPosition();
int duration = musicService.getDuration();
int currentPosition = musicService.getCurrentPosition(); // 当前播放进度
int duration = musicService.getDuration(); // 音频总时长
if (duration > 0) {
// 更新进度条和时间显示
vb.songSeekbar.setProgress((int) ((currentPosition / (float) duration) * 100));
vb.current.setText(formatTime(currentPosition));
vb.time.setText(formatTime(duration));
@ -121,6 +149,90 @@ public class A_PlayActivity extends BaseActivity<ActivityAplayBinding> {
}
}
// 切换背景
private void toggleBackground() {
isBackground = !isBackground; // 切换背景状态
if (isBackground) {
vb.rootLayout.setBackgroundResource(R.color.dark_music); // 切换为暗色背景
vb.backButton.setImageResource(R.drawable.arrow_left);
setTextColor(Color.WHITE); // 设置文字颜色为白色
} else {
vb.rootLayout.setBackgroundResource(R.color.white); // 切换为亮色背景
vb.backButton.setImageResource(R.drawable.arrow_left_black);
setTextColor(Color.BLACK); // 设置文字颜色为黑色
}
saveBackgroundPreference(); // 保存当前背景状态
}
// 保存背景状态到SharedPreferences
private void saveBackgroundPreference() {
SharedPreferences sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean("isBackground", isBackground);
editor.apply();
}
// 从SharedPreferences加载背景状态
private void loadBackgroundPreference() {
SharedPreferences sharedPreferences = getSharedPreferences("AppPreferences", MODE_PRIVATE);
isBackground = sharedPreferences.getBoolean("isBackground", true); // 默认值为 true
// 根据保存的状态设置背景
vb.rootLayout.setBackgroundResource(isBackground ? R.color.dark_music : R.color.white);
vb.backButton.setImageResource(isBackground ? R.drawable.arrow_left : R.drawable.arrow_left_black);
setTextColor(isBackground ? Color.WHITE : Color.BLACK);
}
// 设置文字颜色
private void setTextColor(int color) {
for (TextView textView : textViews) {
textView.setTextColor(color);
}
}
// 显示定时器对话框
private void showTimerDialog() {
BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(this);
View dialogView = LayoutInflater.from(this).inflate(R.layout.timing_dialog, null);
bottomSheetDialog.setContentView(dialogView);
RadioGroup timerOptions = dialogView.findViewById(R.id.timerOptions);
timerOptions.setOnCheckedChangeListener((group, checkedId) -> {
int duration = 0; // 用于保存定时时长
if (checkedId == R.id.radioOff) {
Toast.makeText(getApplicationContext(), "定时关闭已取消", Toast.LENGTH_SHORT).show();
return; // 取消定时
} else if (checkedId == R.id.radio10) {
duration = 10 * 60 * 1000; // 10分钟
} else if (checkedId == R.id.radio20) {
duration = 20 * 60 * 1000; // 20分钟
} else if (checkedId == R.id.radio30) {
duration = 30 * 60 * 1000; // 30分钟
} else if (checkedId == R.id.radio60) {
duration = 60 * 60 * 1000; // 60分钟
} else if (checkedId == R.id.radio90) {
duration = 90 * 60 * 1000; // 90分钟
}
if (duration > 0) {
Toast.makeText(getApplicationContext(), "定时设置为 " + (duration / 60000) + " 分钟", Toast.LENGTH_SHORT).show();
startTimer(duration); // 启动定时器
}
});
bottomSheetDialog.show(); // 显示 BottomSheetDialog
}
// 启动定时器
private void startTimer(int duration) {
// 使用 Handler 来设置定时器
new Handler().postDelayed(() -> {
musicService.pauseAudio();
Toast.makeText(getApplicationContext(), "定时器结束", Toast.LENGTH_SHORT).show();
}, duration);
}
// 格式化时间显示
private String formatTime(int milliseconds) {
int minutes = (milliseconds / 1000) / 60;
int seconds = (milliseconds / 1000) % 60;
@ -131,51 +243,52 @@ public class A_PlayActivity extends BaseActivity<ActivityAplayBinding> {
protected void onDestroy() {
super.onDestroy();
if (isBound) {
unbindService(serviceConnection);
unbindService(serviceConnection); // 解绑服务
isBound = false;
}
if (progressHandler != null) {
progressHandler.removeCallbacksAndMessages(null);
progressHandler.removeCallbacksAndMessages(null); // 移除所有更新任务
progressHandler = null; // 释放引用
}
}
// 进度条的监听器
private class SeekBarListener implements SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser && musicService != null && isBound) {
int duration = musicService.getDuration();
if (duration > 0) {
int newPosition = (int) ((progress / 100.0) * duration);
musicService.seekTo(newPosition); // 设置音频进度
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// 用户开始拖动进度条时触发
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
startUpdatingProgress(); // 拖动结束后重新启动进度更新
}
}
@Override
public boolean isFullScreen() {
return true;
return true; // 设置为全屏
}
@Override
public boolean statusBarLight() {
return false;
return false; // 设置状态栏为非亮色
}
@Override
public void onClick(View v) {
// 处理其他点击事件
}
private class SeekBarListener implements android.widget.SeekBar.OnSeekBarChangeListener {
@Override
public void onProgressChanged(android.widget.SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser && musicService != null && isBound) {
int duration = musicService.getDuration();
if (duration > 0) {
int newPosition = (int) ((progress / 100.0) * duration);
musicService.seekTo(newPosition);
}
}
}
@Override
public void onStartTrackingTouch(android.widget.SeekBar seekBar) {
// 用户开始拖动进度条时触发
}
@Override
public void onStopTrackingTouch(android.widget.SeekBar seekBar) {
// 用户停止拖动进度条时触发
startUpdatingProgress(); // 拖动结束后重新启动进度更新
}
}
}

View File

@ -0,0 +1,18 @@
package com.hi.music.player.ui.activity.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.hi.music.player.service.MusicPlayerForegroundService;
public class A_VMHome extends AndroidViewModel {
public A_VMHome(@NonNull Application application) {
super(application);
}
}

View File

@ -3,11 +3,22 @@ package com.hi.music.player.ui.fragmnt;
import static android.app.Activity.RESULT_OK;
import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -16,6 +27,7 @@ import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.hi.music.player.R;
import com.hi.music.player.adapter.A_ImportFragmentAdapter;
import com.hi.music.player.databinding.FragmentAImportBinding;
import com.hi.music.player.javabean.A_data.AudioItem;
@ -24,11 +36,13 @@ import com.hi.music.player.ui.fragmnt.viewmodel.A_VMImport;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
public class A_ImportFragment extends BaseFragment<FragmentAImportBinding> {
private static final int REQUEST_CODE_READ_MEDIA_AUDIO = 1001;
private static final int REQUEST_CODE_PICK_AUDIO = 1002;
private A_ImportFragmentAdapter adapter;
private A_VMImport viewModel;
@ -42,65 +56,137 @@ public class A_ImportFragment extends BaseFragment<FragmentAImportBinding> {
viewModel = new ViewModelProvider(this).get(A_VMImport.class);
adapter = new A_ImportFragmentAdapter(requireContext());
Vb.importRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
Vb.importRecycler.setAdapter(adapter);
// 监听音频文件列表变化
viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList);
setupRecyclerView();
observeAudioFiles();
initEvent();
}
// 更新音频文件列表
private void setupRecyclerView() {
Vb.importRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
Vb.importRecycler.setAdapter(adapter);
}
private void observeAudioFiles() {
viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList);
}
private void updateAudioList(List<AudioItem> audioFiles) {
adapter.setAudioFiles(audioFiles);
}
// 初始化事件
public void initEvent() {
Vb.setting.setOnClickListener(v -> {
Intent intent = new Intent(getContext(), A_SettingActivity.class);
startActivity(intent);
});
// 添加按钮的点击事件打开文件选择器
private void initEvent() {
Vb.setting.setOnClickListener(v -> startActivity(new Intent(getContext(), A_SettingActivity.class)));
Vb.add.setOnClickListener(v -> checkAndOpenAudioPicker());
// 适配器中的删除按钮点击事件
adapter.setOnDeleteClickListener(filePath -> {
viewModel.markAudioAsDeleted(filePath);
Toast.makeText(getContext(), "Audio marked as deleted", Toast.LENGTH_SHORT).show();
});
adapter.setOnOptionClickListener(this::showDialog);
}
private void showDialog(int position, String filePath, View anchorView) {
Dialog dialog = createOptionsDialog(anchorView);
Button buttonOne = dialog.findViewById(R.id.rename);
Button buttonTwo = dialog.findViewById(R.id.delete);
buttonOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
showRenameDialog(position);
}
});
buttonTwo.setOnClickListener(v -> deleteAudioFile(position, filePath, dialog));
dialog.show();
}
private Dialog createOptionsDialog(View anchorView) {
Dialog dialog = new Dialog(requireContext());
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.options_dialog);
dialog.setCancelable(true);
Objects.requireNonNull(dialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
setDialogPosition(dialog, anchorView); // 设置对话框的位置
return dialog;
}
private void setDialogPosition(Dialog dialog, View anchorView) {
Window window = dialog.getWindow();
if (window != null) {
WindowManager.LayoutParams layoutParams = window.getAttributes();
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(layoutParams);
// 获取按钮在屏幕上的位置
int[] location = new int[2];
anchorView.getLocationOnScreen(location);
// 设置对话框在按钮旁边显示
layoutParams.x = location[0] + anchorView.getWidth(); // 在按钮右侧显示
layoutParams.y = location[1]; // 与按钮的 Y 坐标对齐
window.setAttributes(layoutParams);
}
}
private void showRenameDialog(int position) {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
View dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.rename_dialog, null);
builder.setView(dialogView);
AlertDialog subDialog = builder.create();
Objects.requireNonNull(subDialog.getWindow()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
EditText inputField = dialogView.findViewById(R.id.input_field);
TextView cancel = dialogView.findViewById(R.id.cancel);
TextView confirm = dialogView.findViewById(R.id.confirm);
confirm.setOnClickListener(view -> {
String newName = inputField.getText().toString();
// 更新适配器中的音频文件名称
adapter.updateTitle(position, newName);
Toast.makeText(requireContext(), "新名称: " + newName + ", 项目: " + position, Toast.LENGTH_SHORT).show();
subDialog.dismiss();
});
cancel.setOnClickListener(view -> subDialog.dismiss());
subDialog.show();
}
private void deleteAudioFile(int position, String filePath, Dialog dialog) {
viewModel.markAudioAsDeleted(filePath);
Toast.makeText(requireContext(), "删除按钮被点击, 项目: " + position, Toast.LENGTH_SHORT).show();
dialog.dismiss();
}
// 检查并请求权限然后打开音频选择器
private void checkAndOpenAudioPicker() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_AUDIO)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(requireActivity(),
new String[]{Manifest.permission.READ_MEDIA_AUDIO}, REQUEST_CODE_READ_MEDIA_AUDIO);
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_MEDIA_AUDIO) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(Manifest.permission.READ_MEDIA_AUDIO);
} else {
openAudioPicker(); // 权限已授予打开选择器
openAudioPicker();
}
} else {
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(requireActivity(),
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_CODE_READ_MEDIA_AUDIO);
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
openAudioPicker(); // 权限已授予打开选择器
openAudioPicker();
}
}
}
// 处理权限请求结果
private void requestPermissions(String permission) {
ActivityCompat.requestPermissions(requireActivity(), new String[]{permission}, REQUEST_CODE_READ_MEDIA_AUDIO);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_READ_MEDIA_AUDIO) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限已授予打开音频选择器
openAudioPicker();
} else {
Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_SHORT).show();
@ -108,50 +194,39 @@ public class A_ImportFragment extends BaseFragment<FragmentAImportBinding> {
}
}
// 打开音频选择器
private void openAudioPicker() {
// 检查外部存储状态
String state = Environment.getExternalStorageState();
if (!Environment.MEDIA_MOUNTED.equals(state)) {
if (!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
Toast.makeText(getContext(), "外部存储不可用", Toast.LENGTH_LONG).show();
return;
}
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // 持久 URI 权限
intent.setType("audio/*"); // 设置 MIME 类型为音频
intent.addCategory(Intent.CATEGORY_OPENABLE); // 限制为可打开的文件
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 授予 URI 读取权限
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setType("audio/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, REQUEST_CODE_PICK_AUDIO);
}
// 处理音频选择器结果
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_PICK_AUDIO && resultCode == RESULT_OK) {
if (data != null) {
Uri selectedAudioUri = data.getData();
if (selectedAudioUri != null) {
// 授予 URI 读取权限
requireActivity().grantUriPermission(requireActivity().getPackageName(), selectedAudioUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
// 将音频添加到 ViewModel
try {
viewModel.addAudioFile(selectedAudioUri);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (requestCode == REQUEST_CODE_PICK_AUDIO && resultCode == RESULT_OK && data != null) {
Uri selectedAudioUri = data.getData();
if (selectedAudioUri != null) {
requireActivity().grantUriPermission(requireActivity().getPackageName(), selectedAudioUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
try {
viewModel.addAudioFile(selectedAudioUri);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
// 刷新音频列表
@Override
public void onResume() {
super.onResume();
// 重新获取音频文件列表并更新 UI
viewModel.getAudioFiles().observe(getViewLifecycleOwner(), this::updateAudioList);
observeAudioFiles();
}
}

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M12.917,15.833L7.083,10L12.917,4.167"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#00FFFFFF" /> <!-- 背景颜色,可以修改为透明或其他颜色 -->
<corners android:radius="16dp" /> <!-- 圆角半径 -->
<stroke
android:width="2dp"
android:color="#1C1C1E" /> <!-- 边框颜色 -->
</shape>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Selected state -->
<item android:state_checked="true">
<shape android:shape="rectangle">
<solid android:color="#00FF00"/> <!-- 选中时的背景颜色 -->
<corners android:radius="20dp"/> <!-- 圆角效果 -->
</shape>
</item>
<!-- Default state -->
<item android:state_checked="false">
<shape android:shape="rectangle">
<solid android:color="#00FFFFFF"/> <!-- 未选中时的背景颜色 -->
<corners android:radius="20dp"/>
</shape>
</item>
</selector>

View File

@ -34,6 +34,7 @@
android:layout_marginBottom="-5dp"
android:background="@drawable/round_rectangle"
android:backgroundTint="#80F988"
android:clickable="true"
app:layout_constraintBottom_toTopOf="@+id/home_tab_layout">
<!-- 包裹 ProgressBar 和 ImageView 的 FrameLayout -->
@ -54,7 +55,6 @@
android:layout_height="wrap_content"
android:layout_gravity="center" />
<!-- 图片视图 -->
<ImageView
android:id="@+id/left_image"
android:layout_width="48dp"
@ -69,24 +69,46 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="顶部文本"
android:text="音乐名"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/bottom_text"
app:layout_constraintBottom_toTopOf="@+id/bottom_text1"
app:layout_constraintStart_toEndOf="@+id/progress_container"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/bottom_text"
android:id="@+id/bottom_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="底部文本"
android:text="当前时长"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/progress_container"
app:layout_constraintTop_toBottomOf="@+id/top_text" />
<TextView
android:id="@+id/separator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="/"
android:layout_marginStart="6dp"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/bottom_text1"
app:layout_constraintTop_toBottomOf="@+id/top_text" />
<TextView
android:id="@+id/bottom_text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="总时长"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/separator"
app:layout_constraintTop_toBottomOf="@+id/top_text" />
<!-- 右侧水平排列的两个 ImageView -->
<ImageView
android:id="@+id/clock"

View File

@ -3,7 +3,8 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/cardview_dark_background"
android:id="@+id/root_layout"
android:background="@color/dark_music"
tools:context=".ui.activity.A_PlayActivity">
<ImageView
@ -12,7 +13,6 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="28dp"
android:contentDescription="Back Button"
android:src="@drawable/arrow_left"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@ -0,0 +1,35 @@
<!-- res/layout/dialog_buttons.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/round_rectangle"
android:backgroundTint="#BF1E1E1E"
android:orientation="vertical">
<Button
android:id="@+id/rename"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Rename"
android:backgroundTint="@android:color/transparent"
android:textSize="16sp"
android:textColor="@color/black"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="8dp"
android:background="@color/dark_music" />
<Button
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Delete"
android:backgroundTint="@android:color/transparent"
android:textColor="@color/black"
android:textSize="16sp" />
</LinearLayout>

View File

@ -0,0 +1,74 @@
<?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:background="@drawable/round_rectangle"
android:backgroundTint="#BF1E1E1E"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/top_text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="Name Your Voice"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/top_text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="The value contains a maximum
of 60 characters"
android:textColor="@color/white"
android:textSize="13sp" />
<!-- 输入框 -->
<EditText
android:id="@+id/input_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:background="@drawable/rounded_edittext_background"
android:hint="Placeholder"
android:textColor="@color/white"
android:padding="12dp"
android:textSize="13sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:gravity="center"
android:text="Cancel"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/confirm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:gravity="center"
android:text="Confirm"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,106 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:background="#282A2C">
<TextView
android:id="@+id/timerLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Timing off"
android:textColor="@android:color/white"
android:layout_marginBottom="16dp"
android:layout_marginTop="16dp"
android:textSize="18sp"/>
<RadioGroup
android:id="@+id/timerOptions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:padding="2dp"
android:layout_marginBottom="32dp"
android:background="@drawable/round_rectangle"
android:backgroundTint="@color/cardview_dark_background">
<RadioButton
android:id="@+id/radioOff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="OFF"
android:background="@drawable/timer_option_selector"
android:textColor="@android:color/white"
android:gravity="center"
android:layout_marginStart="5dp"
android:button="@null"
android:padding="10dp"/>
<RadioButton
android:id="@+id/radio10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="10"
android:background="@drawable/timer_option_selector"
android:textColor="@android:color/white"
android:gravity="center"
android:button="@null"
android:padding="10dp"/>
<RadioButton
android:id="@+id/radio20"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="20"
android:background="@drawable/timer_option_selector"
android:textColor="@android:color/white"
android:gravity="center"
android:button="@null"
android:padding="10dp"/>
<RadioButton
android:id="@+id/radio30"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="30"
android:background="@drawable/timer_option_selector"
android:textColor="@android:color/white"
android:gravity="center"
android:button="@null"
android:padding="10dp"/>
<RadioButton
android:id="@+id/radio60"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="60"
android:background="@drawable/timer_option_selector"
android:textColor="@android:color/white"
android:gravity="center"
android:button="@null"
android:padding="10dp"/>
<RadioButton
android:id="@+id/radio90"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="90"
android:layout_marginEnd="5dp"
android:background="@drawable/timer_option_selector"
android:textColor="@android:color/white"
android:gravity="center"
android:button="@null"
android:padding="10dp"/>
</RadioGroup>
</LinearLayout>

View File

@ -11,6 +11,7 @@
<color name="progress_buffer_color">#59FFFFFF</color>
<color name="default_play_list_color">#1A1A1A</color>
<color name="cur_play_music">#4DFFFFFF</color>
<color name="dark_music">#FF424242</color>
<color name="test">#2D9C31</color>
</resources>