A面页面优化,播放逻辑优化,功能增加
This commit is contained in:
parent
5b390f4bff
commit
adca6a12bc
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
// 处理其他点击事件
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(); // 拖动结束后重新启动进度更新
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
13
app/src/main/res/drawable/arrow_left_black.xml
Normal file
13
app/src/main/res/drawable/arrow_left_black.xml
Normal 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>
|
||||
12
app/src/main/res/drawable/rounded_edittext_background.xml
Normal file
12
app/src/main/res/drawable/rounded_edittext_background.xml
Normal 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>
|
||||
18
app/src/main/res/drawable/timer_option_selector.xml
Normal file
18
app/src/main/res/drawable/timer_option_selector.xml
Normal 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>
|
||||
@ -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"
|
||||
|
||||
@ -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" />
|
||||
|
||||
35
app/src/main/res/layout/options_dialog.xml
Normal file
35
app/src/main/res/layout/options_dialog.xml
Normal 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>
|
||||
74
app/src/main/res/layout/rename_dialog.xml
Normal file
74
app/src/main/res/layout/rename_dialog.xml
Normal 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>
|
||||
106
app/src/main/res/layout/timing_dialog.xml
Normal file
106
app/src/main/res/layout/timing_dialog.xml
Normal 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>
|
||||
@ -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>
|
||||
Loading…
Reference in New Issue
Block a user