package com.weishitechsub.quanminchangKmianfei.fragment.home;
import android.Manifest;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.CircleCrop;
import com.hfd.common.base.BaseFragment;
import com.hfd.common.net.HttpBuiler;
import com.hfd.common.net.JsonGenericsSerializator;
import com.hfd.common.net.Url;
import com.hfd.common.util.DensityUtil;
import com.hfd.common.util.ToastUtil;
import com.hw.lrcviewlib.LrcRow;
import com.hw.lrcviewlib.LrcView;
import com.weishitechsub.quanminchangKmianfei.bean.MusicBean;
import com.weishitechsub.quanminchangKmianfei.dialog.PermissionDialog;
import com.weishitechsub.quanminchangKmianfei.utils.MusicCache;
import com.weishitechsub.quanminchangKmianfei.utils.MusicPlayerManager;
import com.weishitechsub.quanminchangKmianfei.utils.PermissionUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import pub.devrel.easypermissions.EasyPermissions;
import com.hfd.common.net.GenericsCallback;
import com.weishitechsub.quanminchangKmianfei.R;
import com.weishitechsub.quanminchangKmianfei.utils.OnMultiClickListener;
import okhttp3.Call;
/**
* 高端语音导航首页
*/
public class HomeFragment extends BaseFragment implements EasyPermissions.PermissionCallbacks{
ImageView iv_song,iv_xh,iv_sys,iv_bf,iv_xys,iv_xz;
TextView tv_start_time,tv_end_time,tv_no_lyric,tv_title,tv_singer;
LrcView lyricView;
SeekBar seekbar;
private List<MusicBean.DataBean> restList = new ArrayList<>();
private List<MusicBean.DataBean> musicList; // 存储获取到的歌曲列表
private ObjectAnimator rotationAnimator;
private boolean isRotating = false; // 记录当前是否正在旋转
private boolean shouldPauseWhenBackground = true; // 默认返回后台时暂停
private static final String[] PERMS_CAMERA = new String[] {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}; // 需要请求的权限数组
private static final int RC_CAMERA_PERM = 123; // 请求码,唯一标识
private static final int REQUEST_CODE_WRITE_SETTINGS = 101;
private AlertDialog progressDialog;
private Handler handler;
private Runnable updateProgressRunnable;
@Override
protected int setLayout() {
return R.layout.fragment_home;
}
@Override
protected void initView() {
iv_song = fvbi(R.id.iv_song);
lyricView = fvbi(R.id.lyricView);
tv_start_time = fvbi(R.id.tv_start_time);
seekbar = fvbi(R.id.seekbar);
tv_end_time = fvbi(R.id.tv_end_time);
iv_xh = fvbi(R.id.iv_xh);
iv_sys = fvbi(R.id.iv_sys);
iv_bf = fvbi(R.id.iv_bf);
iv_xys = fvbi(R.id.iv_xys);
iv_xz = fvbi(R.id.iv_xz);
tv_no_lyric = fvbi(R.id.tv_no_lyric);
tv_title = fvbi(R.id.tv_title);
tv_singer = fvbi(R.id.tv_singer);
// 设置最大值为100
seekbar.setMax(100);
initLyricStyle();
// 初始化旋转动画
initRotationAnimation();
}
@Override
protected void initClick() {
iv_xz.setOnClickListener(new OnMultiClickListener() {
@Override
public void onMultiClick(View v) {
// 设置:弹权限时不暂停音乐
shouldPauseWhenBackground = false;
getSavePermissions();
}
});
iv_sys.setOnClickListener(v -> playPrevious()); // 上一首
iv_xys.setOnClickListener(v -> playNext()); // 下一首
iv_bf.setOnClickListener(v -> togglePlayPause()); // 播放/暂停
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
int duration = MusicPlayerManager.getInstance().getDuration();
int newPosition = (int) ((progress / 100.0) * duration);
MusicPlayerManager.getInstance().seekTo(newPosition);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
}
@Override
protected void initData() {
musicAppInfo();
}
private void musicAppInfo() {
HashMap<String, String> map = new HashMap<>();
showDialog();
HttpBuiler.getInfo(Url.music, map).build().execute(
new GenericsCallback<MusicBean>(new JsonGenericsSerializator()) {
@Override
public void onError(Call call, Exception e, int id) {
dissmiss();
ToastUtil.showShortToast(e.getMessage());
}
@Override
public void onResponse(MusicBean response, int id) {
dissmiss();
if ("0".equals(response.getCode()) && response.getData() != null && !response.getData().isEmpty()) {
restList = response.getData();
// 获取第三条之后的数据(即从索引2开始到末尾)
musicList = new ArrayList<>(restList.subList(3, restList.size()));
MusicCache.setMainMusicList(musicList); // 缓存主列表
startPlaybackIfNeeded();
} else {
ToastUtil.showShortToast("暂无歌曲数据");
}
}
});
}
/**
* 设置歌词样式
*/
private void initLyricStyle(){
// 应用样式
lyricView.getLrcSetting()
.setTimeTextSize(40)
.setSelectLineColor(Color.parseColor("#FFFFFFFF"))
.setSelectLineTextSize(25)
.setNormalRowColor(Color.parseColor("#919191"))
.setHeightRowColor(Color.parseColor("#FFFFFFFF"))
.setNormalRowTextSize(DensityUtil.sp2px(getContext(), 17))
.setHeightLightRowTextSize(DensityUtil.sp2px(getContext(), 17))
.setTrySelectRowTextSize(DensityUtil.sp2px(getContext(), 17))
.setTimeTextColor(Color.parseColor("#FFFFFFFF"))
.setTrySelectRowColor(Color.parseColor("#FFFFFFFF"));
lyricView.commitLrcSettings();
}
/**
* 旋转动画
*/
private void initRotationAnimation() {
// 创建围绕 Z 轴旋转的动画
rotationAnimator = ObjectAnimator.ofFloat(iv_song, "rotation", 0f, 360f);
rotationAnimator.setDuration(10000); // 一圈10秒,可根据需要调整速度
rotationAnimator.setRepeatCount(ObjectAnimator.INFINITE); // 无限循环
rotationAnimator.setInterpolator(new LinearInterpolator()); // 匀速转动
}
/**
* 如果还没有播放,则自动播放第一首
*/
private void startPlaybackIfNeeded() {
if (musicList == null || musicList.isEmpty()) return;
// 默认从第0首开始播放(如果还没播放过)
if (MusicPlayerManager.getInstance().getCurrentPlayingPosition() == -1) {
playMusicByPosition(0);
} else {
updateUIWithCurrentSong(); // 否则刷新当前 UI
}
setupUpdateProgressWithHandler(); // 开始定时更新进度
}
/**
* 播放指定位置的歌曲
*/
private void playMusicByPosition(int position) {
if (musicList == null || position < 0 || position >= musicList.size()) return;
MusicBean.DataBean song = musicList.get(position);
String url = song.getMusic(); // 假设 getMusic() 返回音频 URL
MusicPlayerManager.getInstance().play(url, position);
}
/**
* 上一首
*/
private void playPrevious() {
if (musicList == null || musicList.isEmpty()) return;
int pos = MusicPlayerManager.getInstance().getCurrentPlayingPosition();
int prevPos = (pos - 1 + musicList.size()) % musicList.size(); // 循环上一首
playMusicByPosition(prevPos);
}
/**
* 下一首
*/
private void playNext() {
if (musicList == null || musicList.isEmpty()) return;
int pos = MusicPlayerManager.getInstance().getCurrentPlayingPosition();
int nextPos = (pos + 1) % musicList.size(); // 循环下一首
playMusicByPosition(nextPos);
}
/**
* 播放/暂停切换
*/
private void togglePlayPause() {
if (MusicPlayerManager.getInstance().isPlaying()) {
MusicPlayerManager.getInstance().pause();
} else {
MusicPlayerManager.getInstance().resume();
}
}
/**
* 更新 UI 显示当前歌曲信息(包括歌词、标题等)
*/
@SuppressLint("StaticFieldLeak")
private void updateUIWithCurrentSong() {
MusicBean.DataBean currentSong = MusicPlayerManager.getInstance().getCurrentPlayingSong();
if (currentSong == null) return;
String lrcText = currentSong.getLrc();
tv_title.setText(currentSong.getTitle());
tv_singer.setText(currentSong.getSinger());
// 使用 Glide 加载封面
Glide.with(getContext())
.load(currentSong.getCover())
.transform(new CircleCrop())
.into(iv_song);
new AsyncTask<String, Void, List<LrcRow>>() {
@Override
protected List<LrcRow> doInBackground(String... params) {
return new LrcDataBuilder().builtFromText(params[0]);
}
@Override
protected void onPostExecute(List<LrcRow> rows) {
if (rows != null && !rows.isEmpty()) {
lyricView.setLrcData(rows);
tv_no_lyric.setVisibility(View.GONE);
} else {
lyricView.setLrcData(null);
tv_no_lyric.setVisibility(View.VISIBLE);
}
}
}.execute(lrcText);
// 更新时间(模拟或通过 MediaPlayer 获取)
int duration = MusicPlayerManager.getInstance().getDuration();
tv_end_time.setText(formatTime(duration));
}
/**
* 定时更新播放进度条和歌词
*/
private void setupUpdateProgressWithHandler() {
if (handler == null) {
handler = new Handler(Looper.getMainLooper());
}
// 定义循环任务
updateProgressRunnable = new Runnable() {
@Override
public void run() {
int currentPosition = MusicPlayerManager.getInstance().getCurrentPosition();
int duration = MusicPlayerManager.getInstance().getDuration();
if (duration > 0) {
// 更新进度条
int progress = (int) (((float) currentPosition / duration) * 100);
seekbar.setProgress(progress);
// 更新时间文本
tv_start_time.setText(formatTime(currentPosition));
//通知 LrcView 当前播放时间(毫秒)
lyricView.seekLrcToTime(currentPosition);
}
// 自动重复执行(每隔 500ms)
handler.postDelayed(this, 500);
}
};
// 开始执行
handler.post(updateProgressRunnable);
}
/**
* 格式化毫秒时间为 mm:ss
*/
private String formatTime(int timeMs) {
int totalSeconds = timeMs / 1000;
int minutes = totalSeconds / 60;
int seconds = totalSeconds % 60;
return String.format("%02d:%02d", minutes, seconds);
}
/**
* 绑定播放器状态监听器
*/
@Override
public void onResume() {
super.onResume();
MusicPlayerManager.getInstance().setOnPlaybackStateChangedListener(playbackListener);
if (musicList != null) {
// 更新当前歌曲信息和播放按钮图标
updateUIWithCurrentSong();
updatePlayButtonState(MusicPlayerManager.getInstance().isPlaying());
boolean isPlaying = MusicPlayerManager.getInstance().isPlaying();
updatePlayButtonState(isPlaying);
// 如果正在播放,则恢复旋转
if (isPlaying) {
startRotation();
} else {
pauseRotation();
}
// 恢复进度条更新
setupUpdateProgressWithHandler();
}
}
@Override
public void onPause() {
super.onPause();
// 明确判断当前是否正在播放,如果是,则暂停
// 仅当允许暂停时才真正 pause
if (shouldPauseWhenBackground && MusicPlayerManager.getInstance().isPlaying()) {
MusicPlayerManager.getInstance().pause();
Log.d("HomeFragment", "Fragment 不可见,已暂停音乐");
}
pauseRotation(); // 离开界面时停止旋转
// 停止 UI 更新任务
if (handler != null && updateProgressRunnable != null) {
handler.removeCallbacks(updateProgressRunnable);
}
// 移除播放状态监听(避免内存泄漏)
MusicPlayerManager.getInstance().setOnPlaybackStateChangedListener(null);
}
@Override
public void onStart() {
super.onStart();
// 恢复进度条更新
setupUpdateProgressWithHandler();
boolean isPlaying = MusicPlayerManager.getInstance().isPlaying();
updatePlayButtonState(isPlaying);
if (isPlaying) {
startRotation();
} else {
pauseRotation();
}
// 重新绑定监听器
MusicPlayerManager.getInstance().setOnPlaybackStateChangedListener(playbackListener);
}
@Override
public void onDestroy() {
super.onDestroy();
MusicPlayerManager.getInstance().release();
// 防止内存泄漏
if (rotationAnimator != null) {
rotationAnimator.cancel();
rotationAnimator = null;
}
if (handler != null) {
handler.removeCallbacksAndMessages(null); // 清空所有消息
handler = null;
}
}
// 播放状态监听器
private final MusicPlayerManager.OnPlaybackStateChangedListener playbackListener =
new MusicPlayerManager.OnPlaybackStateChangedListener() {
private int consecutiveErrorCount = 0; // 连续错误计数
private static final int MAX_CONSECUTIVE_ERRORS = 3;
@Override
public void onPlaying(int position) {
consecutiveErrorCount = 0; // 成功播放则清零
updatePlayButtonState(true);
updateUIWithCurrentSong();
}
@Override
public void onPaused() {
updatePlayButtonState(false);
}
@Override
public void onCompletion() {
playNext(); // 正常播放完成才切下一首
}
@Override
public void onError(String errorMsg) {
consecutiveErrorCount++;
Log.e("MusicPlayer", "播放错误 [" + consecutiveErrorCount + "]: " + errorMsg);
// 显示一次错误提示即可
if (consecutiveErrorCount == 1) {
ToastUtil.showShortToast("播放失败,跳过该歌曲");
}
// 防止无限循环:最多连续失败3次就停止
if (consecutiveErrorCount >= MAX_CONSECUTIVE_ERRORS) {
ToastUtil.showShortToast("连续播放失败,已暂停");
MusicPlayerManager.getInstance().pause();
return;
}
// 尝试播放下一首
playNext();
}
};
/**
* 更新播放按钮图标(播放/暂停)
*/
private void updatePlayButtonState(boolean isPlaying) {
if (isPlaying) {
iv_bf.setImageResource(R.mipmap.img_gc_zt); // 暂停图标
startRotation(); // 开始旋转
} else {
iv_bf.setImageResource(R.mipmap.img_bf); // 播放图标
pauseRotation(); // 暂停旋转
}
}
/**
* 开始旋转封面图
*/
private void startRotation() {
if (isRotating || rotationAnimator == null) return;
// 计算从哪里开始(支持恢复)
float currentRotation = iv_song.getRotation();
rotationAnimator.setFloatValues(currentRotation, currentRotation + 360f);
rotationAnimator.start();
isRotating = true;
}
/**
* 暂停旋转封面图
*/
private void pauseRotation() {
if (!isRotating || rotationAnimator == null) return;
rotationAnimator.cancel(); // cancel 会保留当前角度
isRotating = false;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
if(requestCode == RC_CAMERA_PERM){
saveCurrentSong();
}
// 恢复正常行为:离开界面时暂停
shouldPauseWhenBackground = true;
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
// 即使拒绝,也恢复标志
shouldPauseWhenBackground = true;
}
// 内部歌词解析器
public static class LrcDataBuilder { // 加上 static
public List<LrcRow> builtFromText(String lrcText) {
List<LrcRow> rows = new ArrayList<>();
if (lrcText == null || lrcText.trim().isEmpty()) {
return rows;
}
String[] lines = lrcText.split("\\r?\\n");
for (String line : lines) {
line = line.trim();
if (line.isEmpty()) continue;
List<LrcRow> parsed = parseLrcLine(line);
if (!parsed.isEmpty()) {
rows.addAll(parsed);
}
}
Collections.sort(rows);
return rows;
}
private List<LrcRow> parseLrcLine(String line) {
List<LrcRow> result = new ArrayList<>();
if (line.isEmpty()) return result;
// 支持多种换行符预处理(避免后续污染)
line = line.replaceAll("[\\r\\n\\u2028\\u2029]+", " ");
Pattern pattern = Pattern.compile("\\[(\\d{1,2}):(\\d{2})(?:\\.(\\d{2,3}))?\\]");
Matcher matcher = pattern.matcher(line);
while (matcher.find()) {
int min = Integer.parseInt(matcher.group(1));
int sec = Integer.parseInt(matcher.group(2));
int milli = 0;
if (matcher.group(3) != null) {
milli = Integer.parseInt(matcher.group(3));
if (matcher.group(3).length() == 2) milli *= 10; // 补齐毫秒
}
long timeMillis = (min * 60L + sec) * 1000 + milli;
int textStart = matcher.end();
int textEnd = line.length();
// 查找下一个时间标签作为结束点
Matcher nextMatcher = pattern.matcher(line);
if (nextMatcher.find(matcher.start() + 1)) {
textEnd = nextMatcher.start();
}
String rawText = line.substring(textStart, textEnd).trim();
// 再次防御性清理各种空白与特殊字符
String text = rawText
.replaceAll("[\\p{Cc}\\p{Cf}&&[^\\t]]", " ") // 移除控制字符
.replace((char) 0xA0, ' ') // 不间断空格
.replaceAll("\\s+", " ") // 多空格合并
.trim();
if (!text.isEmpty()) {
result.add(new LrcRow(text, matcher.group(0), timeMillis));
}
}
return result;
}
}
private void getSavePermissions() {
// 检查并请求权限
if (!EasyPermissions.hasPermissions(getContext(), PERMS_CAMERA)) {
new PermissionDialog(getActivity(),"权限说明:由于下载歌曲,需要相应的存储权限。拒绝将无法使用此功能").show();
EasyPermissions.requestPermissions(getActivity(), "需要相应的存储权限", RC_CAMERA_PERM, PERMS_CAMERA);
} else {
saveCurrentSong(); // 改为保存当前歌曲
}
}
private void saveCurrentSong() {
int pos = MusicPlayerManager.getInstance().getCurrentPlayingPosition();
if (pos == -1) {
ToastUtil.showShortToast("没有正在播放的歌曲");
return;
}
MusicBean.DataBean song = musicList.get(pos);
if (song == null || song.getMusic() == null) {
ToastUtil.showShortToast("歌曲信息不完整");
return;
}
showProgressDialog(getActivity());
new SaveTask(song).execute(); // 传入整个 song 对象更安全
}
private void showProgressDialog(Context context) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("正在保存...");
builder.setCancelable(false);
progressDialog = builder.create();
progressDialog.show();
}
private class SaveTask extends AsyncTask<Void, Void, Boolean> {
private final MusicBean.DataBean song;
private String targetPath;
private Exception exception;
SaveTask(MusicBean.DataBean song) {
this.song = song;
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
String urlStr = song.getMusic();
String fileName = song.getTitle();
if (fileName == null) fileName = "music_" + System.currentTimeMillis();
File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
if (!dir.exists()) dir.mkdirs();
targetPath = new File(dir, fileName + ".mp3").getAbsolutePath();
InputStream inputStream = new java.net.URL(urlStr).openStream();
FileOutputStream outStream = new FileOutputStream(targetPath);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
}
outStream.flush();
outStream.close();
inputStream.close();
// 通知媒体库刷新
Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
scanIntent.setData(Uri.fromFile(new File(targetPath)));
getActivity().sendBroadcast(scanIntent);
return true;
} catch (Exception e) {
e.printStackTrace();
exception = e;
return false;
}
}
@Override
protected void onPostExecute(Boolean success) {
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
if (success) {
Toast.makeText(getActivity(), "音乐已保存至: " + targetPath, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), "保存失败: " + (exception != null ? exception.getMessage() : "未知错误"), Toast.LENGTH_SHORT).show();
}
}
}
}package com.weishitechsub.quanminchangKmianfei.fragment.lilv;
import androidx.recyclerview.widget.RecyclerView;
import com.hfd.common.base.BaseFragment;
import com.weishitechsub.quanminchangKmianfei.R;
/**
* 个人出租屋税率
*/
public class LiLvFragment extends BaseFragment{
RecyclerView rv;
@Override
protected int setLayout() {
return R.layout.fragment_lilv;
}
@Override
protected void initView() {
}
@Override
protected void initClick() {
}
@Override
protected void initData() {
}
}
package com.weishitechsub.quanminchangKmianfei.utils;
import com.weishitechsub.quanminchangKmianfei.bean.MusicBean;
import java.util.ArrayList;
import java.util.List;
public class MusicCache {
// 主音乐列表(如首页推荐、搜索结果)
private static List<MusicBean.DataBean> mainMusicList;
// 当前正在播放的歌曲
private static MusicBean.DataBean currentPlayingSong;
// 当前播放位置(可选,用于定位)
private static int currentPosition = -1;
// === 主列表操作 ===
public static void setMainMusicList(List<MusicBean.DataBean> list) {
if (list != null) {
mainMusicList = new ArrayList<>(list);
} else {
mainMusicList = null;
}
}
public static List<MusicBean.DataBean> getMainMusicList() {
return mainMusicList;
}
public static MusicBean.DataBean getMusicAt(int position) {
if (mainMusicList == null || position < 0 || position >= mainMusicList.size()) {
return null;
}
return mainMusicList.get(position);
}
// === 当前播放歌曲操作 ===
public static void setCurrentPlayingSong(MusicBean.DataBean song, int position) {
currentPlayingSong = song;
currentPosition = position;
}
public static MusicBean.DataBean getCurrentPlayingSong() {
return currentPlayingSong;
}
public static int getCurrentPlayingPosition() {
return currentPosition;
}
// 可选:清空当前播放
public static void clearCurrentPlaying() {
currentPlayingSong = null;
currentPosition = -1;
}
}
我在LiLvFragment展示 MusicCache.setMainMusicList(musicList); // 缓存主列表直接用这个展示数据可以吗
最新发布