diff --git a/app/src/main/java/com/example/musicplayer/view/PlayActivity.java b/app/src/main/java/com/example/musicplayer/view/PlayActivity.java index ca272c2..b9e9b6d 100644 --- a/app/src/main/java/com/example/musicplayer/view/PlayActivity.java +++ b/app/src/main/java/com/example/musicplayer/view/PlayActivity.java @@ -75,97 +75,141 @@ import butterknife.BindView; /** * 播放界面 + * 该类是音乐播放器应用中用于展示音乐播放相关界面以及处理各种播放相关操作、用户交互、与服务通信等功能的Activity类, + * 实现了IPlayContract.View接口,遵循MVP架构模式,与PlayPresenter协同工作来实现业务逻辑。 */ public class PlayActivity extends BaseMvpActivity implements IPlayContract.View { private final static String TAG = "PlayActivity"; + // 通过ButterKnife框架绑定TextView控件,用于显示歌曲名称 @BindView(R.id.tv_song) TextView mSongTv; + // 通过ButterKnife框架绑定ImageView控件,用于返回按钮,点击可返回上一界面 @BindView(R.id.iv_back) ImageView mBackIv; + // 通过ButterKnife框架绑定TextView控件,用于显示歌手名称 @BindView(R.id.tv_singer) TextView mSingerTv; + // 通过ButterKnife框架绑定Button控件,用于控制音乐的播放/暂停 @BindView(R.id.btn_player) Button mPlayBtn; + // 通过ButterKnife框架绑定Button控件,用于切换到上一首歌曲 @BindView(R.id.btn_last) Button mLastBtn; + // 通过ButterKnife框架绑定Button控件,用于切换播放模式(顺序、随机、单曲循环等) @BindView(R.id.btn_order) Button mPlayModeBtn; + // 通过ButterKnife框架绑定Button控件,用于切换到下一首歌曲 @BindView(R.id.next) Button mNextBtn; + // 通过ButterKnife框架绑定自定义的BackgroundAnimationRelativeLayout,可能用于背景相关动画效果展示等 @BindView(R.id.relative_root) BackgroundAnimationRelativeLayout mRootLayout; + // 通过ButterKnife框架绑定Button控件,用于标记歌曲是否为喜欢(收藏)状态,点击可切换收藏状态 @BindView(R.id.btn_love) Button mLoveBtn; + // 通过ButterKnife框架绑定SeekBar控件,用于显示音乐播放进度,并可拖动来调整播放位置 @BindView(R.id.seek) SeekBar mSeekBar; + // 通过ButterKnife框架绑定TextView控件,用于显示当前播放时间 @BindView(R.id.tv_current_time) TextView mCurrentTimeTv; + // 通过ButterKnife框架绑定TextView控件,用于显示歌曲总时长 @BindView(R.id.tv_duration_time) TextView mDurationTimeTv; + // 通过ButterKnife框架绑定DiscView自定义视图,用于展示唱片、唱针相关动画效果,模拟唱片播放音乐的视觉效果 @BindView(R.id.disc_view) - DiscView mDisc; //唱碟 + DiscView mDisc; + // 通过ButterKnife框架绑定ImageView控件,用于显示唱碟中的歌手头像(封面图片) @BindView(R.id.iv_disc_background) - ImageView mDiscImg; //唱碟中的歌手头像 + ImageView mDiscImg; + // 通过ButterKnife框架绑定Button控件,用于获取封面图片和歌词(可能针对本地歌曲没有相关资源时使用) @BindView(R.id.btn_get_img_lrc) - Button mGetImgAndLrcBtn;//获取封面和歌词 + Button mGetImgAndLrcBtn; + // 通过ButterKnife框架绑定LrcView自定义视图,用于展示歌词内容,并实现歌词与音乐播放进度同步等功能 @BindView(R.id.lrcView) - LrcView mLrcView; //歌词自定义View + LrcView mLrcView; + // 通过ButterKnife框架绑定ImageView控件,用于显示歌曲下载相关状态图标,点击可进行下载操作 @BindView(R.id.downloadIv) - ImageView mDownLoadIv; //下载 + ImageView mDownLoadIv; private PlayPresenter mPresenter; - - private boolean isOnline; //判断是否为网络歌曲 - private int mListType; //列表类型 + // 判断是否为网络歌曲,用于区分不同的资源获取和操作逻辑 + private boolean isOnline; + // 列表类型,可能用于标识歌曲所在的不同列表(例如本地歌曲列表、在线歌曲列表等不同分类),具体含义需结合业务逻辑确定 + private int mListType; + // 播放状态,通过常量来表示不同的播放情况(例如正在播放、暂停等),取值对应Constant类中的相关定义 private int mPlayStatus; - private int mPlayMode;//播放模式 - - private boolean isChange; //拖动进度条 - private boolean isSeek;//标记是否在暂停的时候拖动进度条 - private boolean flag; //用做暂停的标记 - private int time; //记录暂停的时间 + // 播放模式,同样通过常量来表示不同的播放模式(顺序播放、随机播放、单曲循环等),取值对应Constant类中的相关定义 + private int mPlayMode; + + // 标记是否正在拖动进度条,用于防止进度条更新冲突,避免在拖动时后台线程等同时更新进度导致异常 + private boolean isChange; + // 标记是否在暂停的时候拖动进度条,用于后续恢复播放时正确设置播放位置等操作 + private boolean isSeek; + // 用作暂停的标记,用于区分不同的播放/暂停操作场景,辅助控制播放相关逻辑 + private boolean flag; + // 记录暂停的时间,单位可能是毫秒,用于恢复播放时定位播放位置,使播放能从暂停位置继续 + private int time; + // 标记当前是否正在播放音乐,用于界面展示和一些操作的判断依据 private boolean isPlaying; + // 当前播放的歌曲对象,包含歌曲的各种信息,如歌曲名称、歌手、时长、是否在线、是否下载等属性 private Song mSong; + // MediaPlayer对象,用于实际的音频播放控制,不过这里看起来主要通过服务中的MediaPlayer来操作 private MediaPlayer mMediaPlayer; - + // 用于显示播放相关控件的RelativeLayout布局,可能用于整体布局管理和一些布局相关操作 private RelativeLayout mPlayRelative; + // 存储歌词内容的字符串,用于在获取歌词后展示到界面的LrcView中,并实现歌词同步等功能 private String mLrc = null; - private boolean isLove;//是否已经在我喜欢的列表中 + // 标记歌曲是否已经在“我喜欢”的列表中,用于控制收藏按钮的显示状态以及相关操作逻辑 + private boolean isLove; + // 用于存储歌手头像(封面图片)对应的Bitmap对象,方便在界面展示和进行一些图片相关处理 private Bitmap mImgBmp; - private List mLocalSong;//用来判断是否有本地照片 - //服务 + // 存储本地歌曲列表,可能用于判断歌曲是否有本地照片等相关操作,具体用途需结合业务逻辑确定 + private List mLocalSong; + + // 用于与PlayerService建立连接并获取服务端的Binder对象,从而实现与播放服务的交互,获取播放状态等信息以及调用播放相关方法 private PlayerService.PlayStatusBinder mPlayStatusBinder; + // 用于与DownloadService建立连接并获取服务端的Binder对象,以便调用下载相关的操作方法(如开始下载歌曲等) private DownloadService.DownloadBinder mDownloadBinder; - //播放 + // 播放服务的服务连接对象,实现了ServiceConnection接口,用于处理与PlayerService连接建立、断开时的回调逻辑 private ServiceConnection mPlayConnection = new ServiceConnection() { + // 当与PlayerService成功连接时调用,获取服务端传递过来的Binder对象,并进行一系列初始化和界面设置操作 @Override public void onServiceConnected(ComponentName name, IBinder service) { mPlayStatusBinder = (PlayerService.PlayStatusBinder) service; - //播放模式 - mPlayMode = mPresenter.getPlayMode();//得到播放模式 - mPlayStatusBinder.setPlayMode(mPlayMode);//通知服务播放模式 + // 获取播放模式,通过Presenter获取当前设置的播放模式(例如顺序播放、随机播放等) + mPlayMode = mPresenter.getPlayMode(); + // 将获取到的播放模式通知给PlayerService,使服务按照该模式进行音乐播放 + mPlayStatusBinder.setPlayMode(mPlayMode); + // 判断当前歌曲是否为在线歌曲,通过FileUtil获取歌曲信息并判断其是否在线 isOnline = FileUtil.getSong().isOnline(); if (isOnline) { + // 如果是在线歌曲,隐藏获取封面和歌词的按钮,因为在线歌曲可能已经有相关资源或者通过其他方式获取 mGetImgAndLrcBtn.setVisibility(View.GONE); + // 设置歌手头像(封面图片),通过调用setSingerImg方法传入歌曲的图片URL来加载显示图片 setSingerImg(FileUtil.getSong().getImgUrl()); + // 如果当前播放状态为正在播放(Constant.SONG_PLAY),则启动唱片动画、设置播放按钮为选中状态(表示正在播放),并开始更新SeekBar进度 if (mPlayStatus == Constant.SONG_PLAY) { mDisc.play(); mPlayBtn.setSelected(true); startUpdateSeekBarProgress(); } } else { + // 如果是本地歌曲,设置本地图片(可能是本地存储的封面图片或者根据歌手等信息生成的默认图片),调用setLocalImg方法传入歌手名称来设置图片 setLocalImg(mSong.getSinger()); + // 设置SeekBar的二级进度为歌曲的总时长,二级进度可能用于显示缓冲进度等其他用途(具体需看业务逻辑) mSeekBar.setSecondaryProgress((int) mSong.getDuration()); } + // 设置显示歌曲总时长的TextView文本内容,通过MediaUtil工具类将歌曲时长格式化为合适的时间格式(例如"03:30"这种形式)进行显示 mDurationTimeTv.setText(MediaUtil.formatTime(mSong.getDuration())); - //缓存进度条 + // 为MediaPlayer设置缓冲更新监听器,当缓冲进度更新时,会根据缓冲百分比来更新SeekBar的二级进度,用于展示缓冲情况 mPlayStatusBinder.getMediaPlayer().setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { @Override public void onBufferingUpdate(MediaPlayer mp, int percent) { @@ -174,577 +218,628 @@ public class PlayActivity extends BaseMvpActivity implements IPla }); } + // 当与PlayerService意外断开连接时调用(比如服务崩溃),这里暂时没做具体处理 @Override public void onServiceDisconnected(ComponentName name) { - - } }; - //绑定下载服务 + + // 下载服务的服务连接对象,实现了ServiceConnection接口,用于处理与DownloadService连接建立、断开时的回调逻辑 private ServiceConnection mDownloadConnection = new ServiceConnection() { + // 当与DownloadService成功连接时调用,获取服务端传递过来的Binder对象,用于后续调用下载相关操作方法 @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mDownloadBinder = (DownloadService.DownloadBinder) iBinder; } + // 当与DownloadService意外断开连接时调用(比如服务崩溃),这里暂时没做具体处理 @Override public void onServiceDisconnected(ComponentName componentName) { - } }; + // 用于处理音乐播放进度更新相关逻辑的Handler对象,通过定时发送消息来更新SeekBar进度以及当前播放时间的显示等 @SuppressLint("HandlerLeak") private Handler mMusicHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); + // 如果当前没有正在拖动进度条,执行以下更新操作,避免拖动进度条时与后台更新冲突 if (!isChange) { + // 设置SeekBar的进度为从PlayerService获取到的当前播放时间(单位可能需要和SeekBar的进度单位一致,这里看起来是以秒为单位) mSeekBar.setProgress((int) mPlayStatusBinder.getCurrentTime()); + // 设置显示当前播放时间的TextView文本内容,通过MediaUtil工具类将当前进度时间格式化为合适的时间格式进行显示 mCurrentTimeTv.setText(MediaUtil.formatTime(mSeekBar.getProgress())); + // 再次启动更新SeekBar进度的操作,形成定时更新的循环,每隔一定时间(下面代码中是1秒)更新一次进度显示 startUpdateSeekBarProgress(); } - } }; - + // 初始化视图相关操作,在Activity创建时调用,进行一系列的初始化设置,如注册EventBus、隐藏状态栏、设置页面过渡动画等 @Override protected void initView() { super.initView(); + // 在EventBus上注册,以便接收事件通知,例如下载成功、歌曲状态改变等相关事件 EventBus.getDefault().register(this); + // 调用工具类方法隐藏状态栏,传入true可能表示完全隐藏状态栏(具体需看CommonUtil中方法的实现逻辑) CommonUtil.hideStatusBar(this, true); - //设置进入退出动画 + // 设置Activity进入时的过渡动画为从侧边滑动进入的效果,通过创建Slide对象来设置 getWindow().setEnterTransition(new Slide()); + // 设置Activity退出时的过渡动画为从侧边滑动退出的效果,同样通过创建Slide对象来设置 getWindow().setExitTransition(new Slide()); - //判断播放状态 + // 获取从Intent传递过来的播放状态参数,通过Constant.PLAYER_STATUS作为键获取对应的值,若没有则默认取值为2(具体含义需看Constant类定义) mPlayStatus = getIntent().getIntExtra(Constant.PLAYER_STATUS, 2); - //绑定服务,播放和下载的服务 + // 创建启动PlayerService的Intent,用于与播放服务建立连接并交互 Intent playIntent = new Intent(PlayActivity.this, PlayerService.class); + // 创建启动DownloadService的Intent,用于与下载服务建立连接并交互 Intent downIntent = new Intent(PlayActivity.this, DownloadService.class); + // 绑定PlayerService,建立与播放服务的连接,传入连接对象、绑定模式(自动创建服务如果不存在的话) bindService(playIntent, mPlayConnection, Context.BIND_AUTO_CREATE); + // 绑定DownloadService,建立与下载服务的连接,传入连接对象、绑定模式(自动创建服务如果不存在的话) bindService(downIntent, mDownloadConnection, Context.BIND_AUTO_CREATE); - //界面填充 + // 获取当前播放的歌曲对象,通过FileUtil工具类获取存储的歌曲信息 mSong = FileUtil.getSong(); + // 获取歌曲的列表类型,可能用于区分歌曲来源或所属分类等,存储到mListType变量中 mListType = mSong.getListType(); + // 设置显示歌手名称的TextView文本内容为歌曲的歌手信息 mSingerTv.setText(mSong.getSinger()); + // 设置显示歌曲名称的TextView文本内容为歌曲的歌曲名称信息 mSongTv.setText(mSong.getSongName()); + // 设置显示当前播放时间的TextView文本内容,通过MediaUtil工具类将歌曲当前时间格式化为合适的时间格式进行显示 mCurrentTimeTv.setText(MediaUtil.formatTime(mSong.getCurrentTime())); + // 设置SeekBar的最大进度为歌曲的总时长(单位可能需要转换,这里看起来是以秒为单位存储时长) mSeekBar.setMax((int) mSong.getDuration()); + // 设置SeekBar的当前进度为歌曲的当前播放时间 mSeekBar.setProgress((int) mSong.getCurrentTime()); - mDownLoadIv.setVisibility(mSong.isOnline() ? View.VISIBLE : View.GONE); //下载按钮是否隐藏 - mDownLoadIv.setImageDrawable(mSong.isDownload() ? getDrawable(R.drawable.downloaded) : getDrawable(R.drawable.download_song)); - - mPlayMode = mPresenter.getPlayMode();//得到播放模式 - if (mPlayMode == Constant.PLAY_ORDER) { - mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_order)); - } else if (mPlayMode == Constant.PLAY_RANDOM) { - mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_random)); - } else { - mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_single)); - } - - - } - - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onDownloadSuccessEvent(DownloadEvent event){ - if(event.getDownloadStatus() == Constant.TYPE_DOWNLOAD_SUCCESS){ - mDownLoadIv.setImageDrawable( - LitePal.where("songId=?", mSong.getSongId()).find(DownloadSong.class).size() != 0 - ? getDrawable(R.drawable.downloaded) - : getDrawable(R.drawable.download_song)); - } - } - - @Override - protected PlayPresenter getPresenter() { - //与Presenter建立关系 - mPresenter = new PlayPresenter(); - return mPresenter; - } - - @Override - protected int getLayoutId() { - return R.layout.activity_play; - } - - @Override - protected void initData() { - mPresenter.queryLove(mSong.getSongId()); //查找歌曲是否为我喜欢的歌曲 + // 根据歌曲是否为在线歌曲来设置下载按钮的显示状态,如果是在线歌曲则显示下载按钮,否则隐藏 + mDownLoadIv.setVisibility(mSong.isOnline()? View.VISIBLE + // 根据歌曲是否为在线歌曲来设置下载按钮的显示状态,如果是在线歌曲则显示下载按钮,否则隐藏 +mDownLoadIv.setVisibility(mSong.isOnline()? View.VISIBLE : View.GONE); +// 根据歌曲是否已经下载来设置下载按钮显示的图标,如果已下载则显示已下载图标,否则显示下载歌曲图标 +mDownLoadIv.setImageDrawable(mSong.isDownload()? getDrawable(R.drawable.downloaded) : getDrawable(R.drawable.download_song)); + +// 获取当前的播放模式,通过Presenter获取当前设置的播放模式(例如顺序播放、随机播放等) +mPlayMode = mPresenter.getPlayMode(); +// 根据获取到的播放模式设置播放模式按钮的背景图片,以展示对应的播放模式图标 +if (mPlayMode == Constant.PLAY_ORDER) { + mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_order)); +} else if (mPlayMode == Constant.PLAY_RANDOM) { + mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_random)); +} else { + mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_single)); +} - if (mPlayStatus == Constant.SONG_PLAY) { - mDisc.play(); - mPlayBtn.setSelected(true); - startUpdateSeekBarProgress(); - } +// 接收DownloadEvent事件的方法,在主线程中处理,当下载成功事件发生且下载状态为成功时,更新下载按钮的图标显示 +@Subscribe(threadMode = ThreadMode.MAIN) +public void onDownloadSuccessEvent(DownloadEvent event) { + if (event.getDownloadStatus() == Constant.TYPE_DOWNLOAD_SUCCESS) { + mDownLoadIv.setImageDrawable( + // 通过LitePal查询数据库中是否存在对应歌曲ID的已下载歌曲记录,如果存在则显示已下载图标,否则显示下载歌曲图标 + LitePal.where("songId=?", mSong.getSongId()).find(DownloadSong.class).size()!= 0 + ? getDrawable(R.drawable.downloaded) + : getDrawable(R.drawable.download_song)); } +} +// 获取与该Activity对应的Presenter对象,创建并返回PlayPresenter实例,用于处理业务逻辑,实现MVP模式中的Presenter层关联 +@Override +protected PlayPresenter getPresenter() { + // 创建PlayPresenter实例,建立与Presenter的关联,以便后续调用Presenter中的方法来处理业务逻辑 + mPresenter = new PlayPresenter(); + return mPresenter; +} - private void try2UpdateMusicPicBackground(final Bitmap bitmap) { - new Thread(() -> { - final Drawable drawable = getForegroundDrawable(bitmap); - runOnUiThread(() -> { - mRootLayout.setForeground(drawable); - mRootLayout.beginAnimation(); - }); - }).start(); - } +// 获取Activity对应的布局资源ID,这里返回activity_play布局的ID,用于设置Activity的界面布局 +@Override +protected int getLayoutId() { + return R.layout.activity_play; +} - private Drawable getForegroundDrawable(Bitmap bitmap) { - /*得到屏幕的宽高比,以便按比例切割图片一部分*/ - final float widthHeightSize = (float) (DisplayUtil.getScreenWidth(PlayActivity.this) - * 1.0 / DisplayUtil.getScreenHeight(this) * 1.0); - - int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight()); - int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0); - - /*切割部分图片*/ - Bitmap cropBitmap = Bitmap.createBitmap(bitmap, cropBitmapWidthX, 0, cropBitmapWidth, - bitmap.getHeight()); - /*缩小图片*/ - Bitmap scaleBitmap = Bitmap.createScaledBitmap(cropBitmap, bitmap.getWidth() / 50, bitmap - .getHeight() / 50, false); - /*模糊化*/ - final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 8, true); - - final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap); - /*加入灰色遮罩层,避免图片过亮影响其他控件*/ - foregroundDrawable.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY); - return foregroundDrawable; +// 初始化数据相关操作,在Activity创建时调用,进行一些数据查询和初始化播放相关操作 +@Override +protected void initData() { + // 调用Presenter的方法查询歌曲是否为“我喜欢”列表中的歌曲,以便后续根据查询结果更新界面显示等操作 + mPresenter.queryLove(mSong.getSongId()); + // 如果当前播放状态为正在播放(Constant.SONG_PLAY),则启动唱片动画、设置播放按钮为选中状态(表示正在播放),并开始更新SeekBar进度 + if (mPlayStatus == Constant.SONG_PLAY) { + mDisc.play(); + mPlayBtn.setSelected(true); + startUpdateSeekBarProgress(); } +} +// 在新线程中尝试更新音乐图片背景,传入一个Bitmap对象作为背景图片,进行图片处理后设置为界面的背景相关效果 +private void try2UpdateMusicPicBackground(final Bitmap bitmap) { + new Thread(() -> { + // 获取处理后的Drawable对象,用于设置为背景,调用getForegroundDrawable方法进行图片处理等操作 + final Drawable drawable = getForegroundDrawable(bitmap); + // 在UI线程中执行设置背景和启动相关动画的操作,因为涉及到UI更新,必须在UI线程中进行 + runOnUiThread(() -> { + mRootLayout.setForeground(drawable); + mRootLayout.beginAnimation(); + }); + }).start(); +} - @Override - protected void onClick() { - //返回按钮 - mBackIv.setOnClickListener(v -> finish()); - //获取本地音乐的图片和歌词 - mGetImgAndLrcBtn.setOnClickListener(v -> getSingerAndLrc()); - - //进度条的监听事件 - mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - } +// 对传入的Bitmap图片进行一系列处理,生成用于设置为界面背景相关效果的Drawable对象,包括按比例切割、缩小、模糊以及添加颜色滤镜等操作 +private Drawable getForegroundDrawable(Bitmap bitmap) { + /*得到屏幕的宽高比,以便按比例切割图片一部分,通过获取屏幕宽度和高度并计算比例,用于后续图片裁剪操作,使图片适应界面展示效果*/ + final float widthHeightSize = (float) (DisplayUtil.getScreenWidth(PlayActivity.this) + * 1.0 / DisplayUtil.getScreenHeight(this) * 1.0); + + // 根据屏幕宽高比计算裁剪后的图片宽度,用于从原始图片中裁剪出合适的部分 + int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight()); + // 计算裁剪图片时在X轴方向的起始位置,使裁剪出的图片部分在水平方向居中 + int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0); + + /*切割部分图片,从原始Bitmap对象中按照计算好的位置和尺寸裁剪出一部分图片,用于后续处理和作为界面背景相关效果展示*/ + Bitmap cropBitmap = Bitmap.createBitmap(bitmap, cropBitmapWidthX, 0, cropBitmapWidth, + bitmap.getHeight()); + /*缩小图片,将裁剪后的图片按照一定比例进行缩小,可能是为了优化性能或者适应界面显示需求等原因,生成一个尺寸更小的Bitmap对象*/ + Bitmap scaleBitmap = Bitmap.createScaledBitmap(cropBitmap, bitmap.getWidth() / 50, bitmap + .getHeight() / 50, false); + /*模糊化,对缩小后的图片进行模糊处理,使图片作为背景时呈现出一种模糊的视觉效果,通过调用FastBlurUtil工具类的方法来实现模糊操作*/ + final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 8, true); + + // 创建一个基于模糊处理后的Bitmap对象的BitmapDrawable对象,作为最终要返回的Drawable,用于设置为界面背景相关效果 + final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap); + /*加入灰色遮罩层,避免图片过亮影响其他控件,通过设置颜色滤镜,将图片颜色与灰色进行混合相乘的模式,使图片整体色调变暗,达到不影响其他控件显示的效果*/ + foregroundDrawable.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY); + return foregroundDrawable; +} - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - //防止在拖动进度条进行进度设置时与Thread更新播放进度条冲突 - isChange = true; - } +// 处理各种点击事件的方法,为各个按钮、视图等设置点击监听器,实现对应的业务逻辑,如返回、获取图片歌词、播放控制、模式切换、收藏等操作 +@Override +protected void onClick() { + // 为返回按钮设置点击监听器,点击时调用finish方法关闭当前Activity,返回上一界面 + mBackIv.setOnClickListener(v -> finish()); + // 为获取本地音乐的图片和歌词按钮设置点击监听器,点击时调用getSingerAndLrc方法获取相关资源 + mGetImgAndLrcBtn.setOnClickListener(v -> getSingerAndLrc()); + + // 为SeekBar设置进度变化监听器,用于处理进度条拖动等操作,实现根据拖动情况调整播放位置以及更新相关界面显示等功能 + mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + // 当进度改变时调用(拖动过程中会多次调用),这里暂时没做具体处理 + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (mPlayStatusBinder.isPlaying()) { - mMediaPlayer = mPlayStatusBinder.getMediaPlayer(); - mMediaPlayer.seekTo(seekBar.getProgress() * 1000); - startUpdateSeekBarProgress(); - } else { - time = seekBar.getProgress(); - isSeek = true; - } - isChange = false; - mCurrentTimeTv.setText(MediaUtil.formatTime(seekBar.getProgress())); - } - }); + } - mPlayModeBtn.setOnClickListener(v -> changePlayMode()); + // 当开始拖动进度条时调用,设置标记防止与后台更新进度冲突,标记当前正在拖动进度条 + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + isChange = true; + } - //播放,暂停的实现 - mPlayBtn.setOnClickListener(v -> { - mMediaPlayer = mPlayStatusBinder.getMediaPlayer(); + // 当停止拖动进度条时调用,根据播放状态设置新的播放位置,并更新当前播放时间显示,重新启动SeekBar进度更新等操作 + @Override + public void onStopTrackingTouch(SeekBar seekBar) { if (mPlayStatusBinder.isPlaying()) { - mPlayStatusBinder.pause(); - stopUpdateSeekBarProgress(); - flag = true; - mPlayBtn.setSelected(false); - mDisc.pause(); - } else if (flag) { - mPlayStatusBinder.resume(); - flag = false; - if (isSeek) { - Log.d(TAG, "onClick: " + time); - mMediaPlayer.seekTo(time * 1000); - isSeek = false; - } - mDisc.play(); - mPlayBtn.setSelected(true); + mMediaPlayer = mPlayStatusBinder.getMediaPlayer(); + mMediaPlayer.seekTo(seekBar.getProgress() * 1000); startUpdateSeekBarProgress(); } else { - if (isOnline) { - mPlayStatusBinder.playOnline(); - } else { - mPlayStatusBinder.play(mListType); - } - mMediaPlayer.seekTo((int) mSong.getCurrentTime() * 1000); - mDisc.play(); - mPlayBtn.setSelected(true); - startUpdateSeekBarProgress(); + time = seekBar.getProgress(); + isSeek = true; } - }); - mNextBtn.setOnClickListener(v -> { - mPlayStatusBinder.next(); - if (mPlayStatusBinder.isPlaying()) { - mPlayBtn.setSelected(true); - } else { - mPlayBtn.setSelected(false); + isChange = false; + mCurrentTimeTv.setText(MediaUtil.formatTime(seekBar.getProgress())); + } + }); + + // 为播放模式按钮设置点击监听器,点击时调用changePlayMode方法切换播放模式,弹出选择播放模式的弹窗供用户选择 + mPlayModeBtn.setOnClickListener(v -> changePlayMode()); + + // 为播放/暂停按钮设置点击监听器,根据当前播放状态进行播放、暂停、恢复播放等操作,并相应地更新界面显示和相关动画效果 + mPlayBtn.setOnClickListener(v -> { + mMediaPlayer = mPlayStatusBinder.getMediaPlayer(); + if (mPlayStatusBinder.isPlaying()) { + mPlayStatusBinder.pause(); + stopUpdateSeekBarProgress(); + flag = true; + mPlayBtn.setSelected(false); + mDisc.pause(); + } else if (flag) { + mPlayStatusBinder.resume(); + flag = false; + if (isSeek) { + Log.d(TAG, "onClick: " + time); + mMediaPlayer.seekTo(time * 1000); + isSeek = false; } - mDisc.next(); - }); - mLastBtn.setOnClickListener(v -> { - mPlayStatusBinder.last(); + mDisc.play(); mPlayBtn.setSelected(true); - mDisc.last(); - }); - - mLoveBtn.setOnClickListener(v -> { - showLoveAnim(); - if (isLove) { - mLoveBtn.setSelected(false); - mPresenter.deleteFromLove(FileUtil.getSong().getSongId()); + startUpdateSeekBarProgress(); + } else { + if (isOnline) { + mPlayStatusBinder.playOnline(); } else { - mLoveBtn.setSelected(true); - mPresenter.saveToLove(FileUtil.getSong()); + mPlayStatusBinder.play(mListType); } - isLove = !isLove; - }); + mMediaPlayer.seekTo((int) mSong.getCurrentTime() * 1000); + mDisc.play(); + mPlayBtn.setSelected(true); + startUpdateSeekBarProgress(); + } + }); - //唱碟点击效果 - mDisc.setOnClickListener(v -> { - if (!isOnline) { - String lrc = FileUtil.getLrcFromNative(mSong.getSongName()); - if (null == lrc) { - String qqId = mSong.getQqId(); - if (Constant.SONG_ID_UNFIND.equals(qqId)) {//匹配不到歌词 - getLrcError(null); - } else if (null == qqId) {//歌曲的id还未匹配 - mPresenter.getSongId(mSong.getSongName(), mSong.getDuration()); - } else {//歌词还未匹配 - mPresenter.getLrc(qqId, Constant.SONG_LOCAL); - } + // 为下一首歌曲按钮设置点击监听器,点击时调用PlayerService的next方法切换到下一首歌曲,同时更新播放按钮状态和唱片相关动画效果 + mNextBtn.setOnClickListener(v -> { + mPlayStatusBinder.next(); + if (mPlayStatusBinder.isPlaying()) { + mPlayBtn.setSelected(true); + } else { + mPlayBtn.setSelected(false); + } + mDisc.next(); + }); + + // 为上一首歌曲按钮设置点击监听器,点击时调用PlayerService的last方法切换到上一首歌曲,同时更新唱片相关动画效果 + mLastBtn.setOnClickListener(v -> { + mPlayStatusBinder.last(); + mPlayBtn.setSelected(true); + mDisc.last(); + }); + + // 为收藏按钮设置点击监听器,点击时切换收藏状态,调用相关方法实现添加或移除收藏操作,并更新按钮的选中状态显示以及通过EventBus发送收藏相关事件 + mLoveBtn.setOnClickListener(v -> { + showLoveAnim(); + if (isLove) { + mLoveBtn.setSelected(false); + mPresenter.deleteFromLove(FileUtil.getSong().getSongId()); + } else { + mLoveBtn.setSelected(true); + mPresenter.saveToLove(FileUtil.getSong()); + } + isLove =!isLove; + }); + + // 为唱碟视图设置点击监听器,根据歌曲是否为在线歌曲进行不同的歌词获取操作,如果是本地歌曲还需进一步判断歌词是否存在等情况来决定获取歌词的方式 + mDisc.setOnClickListener(v -> { + if (!isOnline) { + String lrc = FileUtil.getLrcFromNative(mSong.getSongName()); + if (null == lrc) { + String qqId = mSong.getQqId(); + if (Constant.SONG_ID_UNFIND.equals(qqId)) { + // 如果歌曲ID匹配不到歌词(可能是歌词资源不存在等原因),调用getLrcError方法显示获取歌词失败提示 + getLrcError(null); + } else if (null == qqId) { + // 如果歌曲的ID还未匹配(可能是刚添加的本地歌曲等情况),调用Presenter的方法获取歌曲ID + mPresenter.getSongId(mSong.getSongName(), mSong.getDuration()); } else { - showLrc(lrc); + // 如果歌词还未匹配(已有歌曲ID但歌词未获取到),调用Presenter的方法根据歌曲ID获取本地歌词 + mPresenter.getLrc(qqId, Constant.SONG_LOCAL); } } else { - mPresenter.getLrc(mSong.getSongId(), Constant.SONG_ONLINE); + // 如果本地已经存在歌词,调用showLrc方法展示歌词内容到界面的LrcView中 + showLrc(lrc); } + } else { + // 如果是在线歌曲,调用Presenter的方法根据歌曲ID获取在线歌词 + mPresenter.getLrc(mSong.getSongId(), Constant.SONG_ONLINE); } - ); - //歌词点击效果 - mLrcView.setOnClickListener(v -> { - mLrcView.setVisibility(View.GONE); - mDisc.setVisibility(View.VISIBLE); - }); - //歌曲下载 - mDownLoadIv.setOnClickListener(v -> { - if (mSong.isDownload()) { - showToast(getString(R.string.downloded)); - } else { - mDownloadBinder.startDownload(getDownloadInfoFromSong(mSong)); } - }); - - } - - @Override - public String getSingerName() { - Song song = FileUtil.getSong(); - if (song.getSinger().contains("/")) { - String[] s = song.getSinger().split("/"); - return s[0].trim(); + ); + + // 为歌词视图设置点击监听器,点击时隐藏歌词视图,显示唱碟视图,实现歌词视图和唱碟视图的切换显示效果 + mLrcView.setOnClickListener(v -> { + mLrcView.setVisibility(View.GONE); + mDisc.setVisibility(View.VISIBLE); + }); + + // 为歌曲下载按钮设置点击监听器,点击时根据歌曲是否已经下载进行不同操作,如果已下载则显示提示信息,如果未下载则调用下载服务的方法开始下载歌曲 + mDownLoadIv.setOnClickListener(v -> { + if (mSong.isDownload()) { + showToast(getString(R.string.downloded)); } else { - return song.getSinger().trim(); + mDownloadBinder.startDownload(getDownloadInfoFromSong(mSong)); } + }); - } +} - private String getSongName() { - Song song = FileUtil.getSong(); - assert song != null; - return song.getSongName().trim(); +// 获取歌手名称的方法,对获取到的歌手名称进行处理,如果歌手名称包含斜杠(可能是多个歌手等情况),则取第一个歌手名称并去除空格后返回,否则直接去除空格后返回 +@Override +public String getSingerName() { + Song song = FileUtil.getSong(); + if (song.getSinger().contains("/")) { + String[] s = song.getSinger().split("/"); + return s[0].trim(); + } else { + return song.getSinger().trim(); } +} - @Override - public void getSingerAndLrc() { - mGetImgAndLrcBtn.setText("正在获取..."); - mPresenter.getSingerImg(getSingerName(), getSongName(), mSong.getDuration()); - } +// 获取歌曲名称的方法,获取当前播放歌曲的名称,并去除空格后返回 +private String getSongName() { + Song song = FileUtil.getSong(); + assert song!= null; + return song.getSongName().trim(); +} - @Override - public void setSingerImg(String ImgUrl) { - Glide.with(this) - .load(ImgUrl) - .apply(RequestOptions.placeholderOf(R.drawable.welcome)) - .apply(RequestOptions.errorOf(R.drawable.welcome)) - .into(new SimpleTarget() { - @Override - public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { - mImgBmp = ((BitmapDrawable) resource).getBitmap(); - //如果是本地音乐 - if (!isOnline) { - //保存图片到本地 - FileUtil.saveImgToNative(PlayActivity.this, mImgBmp, getSingerName()); - //将封面地址放到数据库中 - LocalSong localSong = new LocalSong(); - localSong.setPic(Api.STORAGE_IMG_FILE + FileUtil.getSong().getSinger() + ".jpg"); - localSong.updateAll("songId=?", FileUtil.getSong().getSongId()); - } +// 获取本地音乐的图片和歌词的方法,设置按钮文本为“正在获取...”,然后调用Presenter的方法根据歌手名称和歌曲名称以及歌曲时长来获取歌手图片和歌词资源 +@Override +public void getSingerAndLrc() { + mGetImgAndLrcBtn.setText("正在获取..."); + mPresenter.getSingerImg(getSingerName(), getSongName(), mSong.getDuration()); +} - try2UpdateMusicPicBackground(mImgBmp); - setDiscImg(mImgBmp); - mGetImgAndLrcBtn.setVisibility(View.GONE); +// 设置歌手头像(封面图片)的方法,使用Glide库加载图片,设置占位图和加载失败时显示的图片,在图片加载成功后进行一系列处理,如保存本地、更新数据库、设置唱碟中的图片等,并更新界面相关显示 +@Override +public void setSingerImg(String ImgUrl) { + Glide.with(this) + .load(ImgUrl) + .apply(RequestOptions.placeholderOf(R.drawable.welcome)) + .apply(RequestOptions.errorOf(R.drawable.welcome)) + .into(new SimpleTarget() { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + mImgBmp = ((BitmapDrawable) resource).getBitmap(); + //如果是本地音乐 + if (!isOnline) { + //保存图片到本地,调用FileUtil的方法将图片保存到本地存储中,方便后续使用 + FileUtil.saveImgToNative(PlayActivity.this, mImgBmp, getSingerName()); + //将封面地址放到数据库中,创建一个LocalSong对象,设置其图片路径属性,并根据歌曲ID更新数据库中对应记录的图片路径信息 + LocalSong localSong = new LocalSong(); + localSong.setPic(Api.STORAGE_IMG_FILE + FileUtil.getSong().getSinger() + ".jpg"); + localSong.updateAll("songId=?", FileUtil.getSong().getSongId()); } - }); - - } - - - @Override - public void showLove(final boolean love) { - isLove = love; - runOnUiThread(() -> { - if (love) { - mLoveBtn.setSelected(true); - } else { - mLoveBtn.setSelected(false); - } - }); - - } - - @Override - public void showLoveAnim() { - AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(PlayActivity.this, R.animator.favorites_anim); - animatorSet.setTarget(mLoveBtn); - animatorSet.start(); - } - - @Override - public void saveToLoveSuccess() { - EventBus.getDefault().post(new SongCollectionEvent(true)); - CommonUtil.showToast(PlayActivity.this, getString(R.string.love_success)); - } - - @Override - public void sendUpdateCollection() { - EventBus.getDefault().post(new SongCollectionEvent(false)); - } - - - //设置唱碟中歌手头像 - private void setDiscImg(Bitmap bitmap) { - mDiscImg.setImageDrawable(mDisc.getDiscDrawable(bitmap)); - int marginTop = (int) (DisplayUtil.SCALE_DISC_MARGIN_TOP * CommonUtil.getScreenHeight(PlayActivity.this)); - RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mDiscImg - .getLayoutParams(); - layoutParams.setMargins(0, marginTop, 0, 0); - mDiscImg.setLayoutParams(layoutParams); - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onSongChanageEvent(SongStatusEvent event) { - if (event.getSongStatus() == Constant.SONG_CHANGE) { - mDisc.setVisibility(View.VISIBLE); - mLrcView.setVisibility(View.GONE); - mSong = FileUtil.getSong(); - mSongTv.setText(mSong.getSongName()); - mSingerTv.setText(mSong.getSinger()); - mDurationTimeTv.setText(MediaUtil.formatTime(mSong.getDuration())); - mPlayBtn.setSelected(true); - mSeekBar.setMax((int) mSong.getDuration()); - startUpdateSeekBarProgress(); - //缓存进度条 - mPlayStatusBinder.getMediaPlayer().setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { - @Override - public void onBufferingUpdate(MediaPlayer mp, int percent) { - mSeekBar.setSecondaryProgress(percent * mSeekBar.getProgress()); + try2UpdateMusicPicBackground(mImgBmp); + setDiscImg(mImgBmp); + mGetImgAndLrcBtn.setVisibility(View.GONE); } }); - mPresenter.queryLove(mSong.getSongId()); //查找歌曲是否为我喜欢的歌曲 - if (mSong.isOnline()) { - setSingerImg(mSong.getImgUrl()); - } else { - setLocalImg(mSong.getSinger());//显示照片 - } - } - } - - private void startUpdateSeekBarProgress() { - /*避免重复发送Message*/ - stopUpdateSeekBarProgress(); - mMusicHandler.sendEmptyMessageDelayed(0, 1000); - } - - private void stopUpdateSeekBarProgress() { - mMusicHandler.removeMessages(0); - } - - private void setLocalImg(String singer) { - String imgUrl = Api.STORAGE_IMG_FILE + MediaUtil.formatSinger(singer) + ".jpg"; - Glide.with(this) - .load(imgUrl) - .listener(new RequestListener() { - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - mGetImgAndLrcBtn.setVisibility(View.VISIBLE); - mGetImgAndLrcBtn.setText("获取封面和歌词"); - setDiscImg(BitmapFactory.decodeResource(getResources(), R.drawable.default_disc)); - mRootLayout.setBackgroundResource(R.drawable.background); - return true; - } +} - @Override - public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - }) - .apply(RequestOptions.placeholderOf(R.drawable.background)) - .apply(RequestOptions.errorOf(R.drawable.background)) - .into(new SimpleTarget() { - @Override - public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { - mGetImgAndLrcBtn.setVisibility(View.GONE); - mImgBmp = ((BitmapDrawable) resource).getBitmap(); - try2UpdateMusicPicBackground(mImgBmp); - setDiscImg(mImgBmp); - } - }); - } +// 根据传入的参数在UI线程中更新歌曲是否为“喜欢”(收藏)状态的显示,通过设置收藏按钮的选中状态来展示 +@Override +public void showLove(final boolean love) { + isLove = love; + runOnUiThread(() -> { + if (love) { + mLoveBtn.setSelected(true); + } else { + mLoveBtn.setSelected(false); + } + }); +} +// 展示收藏动画的方法,通过AnimatorInflater加载动画资源,设置动画作用的目标为收藏按钮,然后启动动画,实现收藏按钮的动画效果 +@Override +public void showLoveAnim() { + AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(PlayActivity.this, R.animator.favorites_anim); + animatorSet.setTarget(mLoveBtn); + animatorSet.start(); +} - /** - * 展示歌词 - * - * @param lrc - */ - @Override - public void showLrc(final String lrc) { - mDisc.setVisibility(View.GONE); - mLrcView.setVisibility(View.VISIBLE); - Log.d(TAG, "showLrc: "+mPlayStatusBinder.getMediaPlayer().getCurrentPosition()); - mLrcView.setLrc(lrc).setPlayer(mPlayStatusBinder.getMediaPlayer()).draw(); +// 保存到“喜欢”(收藏)列表成功后的处理方法,通过EventBus发送一个SongCollectionEvent事件(传入true表示收藏成功),并显示一个提示收藏成功的Toast信息 +@Override +public void saveToLoveSuccess() { + EventBus.getDefault().post(new SongCollectionEvent(true)); + CommonUtil.showToast(PlayActivity.this, getString(R.string.love_success)); +} - } +// 发送更新收藏相关信息的方法,通过EventBus发送一个SongCollectionEvent事件(传入false表示其他与收藏相关的更新情况),具体用途需结合业务逻辑确定 +@Override +public void sendUpdateCollection() { + EventBus.getDefault().post(new SongCollectionEvent(false)); +} - @Override - public void getLrcError(String content) { - showToast(getString(R.string.get_lrc_fail)); - mSong.setQqId(content); - FileUtil.saveSong(mSong); - } +// 设置唱碟中歌手头像的方法,调用DiscView的方法获取合成后的Drawable对象(包含音乐专辑图片等合成效果),并设置唱碟图片ImageView的显示图片,同时调整其布局参数中的上边距 +private void setDiscImg(Bitmap bitmap) { + mDiscImg.setImageDrawable(mDisc.getDiscDrawable(bitmap)); + int marginTop = (int) (DisplayUtil.SCALE_DISC_MARGIN_TOP * CommonUtil.getScreenHeight(PlayActivity.this)); + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) mDiscImg + .getLayoutParams(); + layoutParams.setMargins(0, marginTop, 0, 0); - @Override - public void setLocalSongId(String songId) { - mSong.setQqId(songId); - FileUtil.saveSong(mSong); //保存 - } + mDiscImg.setLayoutParams(layoutParams); +} - @Override - public void getSongIdSuccess(String songId) { - setLocalSongId(songId);//保存音乐信息 - mPresenter.getLrc(songId, Constant.SONG_LOCAL);//获取歌词 +// 接收SongStatusEvent事件的方法,在主线程中处理,当歌曲状态改变(Constant.SONG_CHANGE)时,进行一系列界面更新操作,如显示唱碟、隐藏歌词视图、更新歌曲相关信息显示、重新启动SeekBar进度更新等 +@Subscribe(threadMode = ThreadMode.MAIN) +public void onSongChanageEvent(SongStatusEvent event) { + if (event.getSongStatus() == Constant.SONG_CHANGE) { + mDisc.setVisibility(View.VISIBLE); + mLrcView.setVisibility(View.GONE); + mSong = FileUtil.getSong(); + mSongTv.setText(mSong.getSongName()); + mSingerTv.setText(mSong.getSinger()); + mDurationTimeTv.setText(MediaUtil.formatTime(mSong.getDuration())); + mPlayBtn.setSelected(true); + mSeekBar.setMax((int) mSong.getDuration()); + startUpdateSeekBarProgress(); + //缓存进度条 + mPlayStatusBinder.getMediaPlayer().setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() { + @Override + public void onBufferingUpdate(MediaPlayer mp, int percent) { + mSeekBar.setSecondaryProgress(percent * mSeekBar.getProgress()); + } + }); + mPresenter.queryLove(mSong.getSongId()); //查找歌曲是否为我喜欢的歌曲 + if (mSong.isOnline()) { + setSingerImg(mSong.getImgUrl()); + } else { + setLocalImg(mSong.getSinger());//显示照片 + } } +} - @Override - public void saveLrc(String lrc) { - FileUtil.saveLrcToNative(lrc, mSong.getSongName()); - } +// 启动更新SeekBar进度的方法,先停止可能正在进行的更新操作(避免重复更新),然后通过Handler发送一个延迟消息,每隔1秒触发一次进度更新操作(由Handler的handleMessage方法处理) +private void startUpdateSeekBarProgress() { + /*避免重复发送Message*/ + stopUpdateSeekBarProgress(); + mMusicHandler.sendEmptyMessageDelayed(0, 1000); +} +// 停止更新SeekBar进度的方法,通过Handler移除所有待处理的消息,从而停止进度更新操作,常用于暂停播放、退出界面等情况 +private void stopUpdateSeekBarProgress() { + mMusicHandler.removeMessages(0); +} - //改变播放模式 - private void changePlayMode() { - View playModeView = LayoutInflater.from(this).inflate(R.layout.play_mode, null); - ConstraintLayout orderLayout = playModeView.findViewById(R.id.orderLayout); - ConstraintLayout randomLayout = playModeView.findViewById(R.id.randomLayout); - ConstraintLayout singleLayout = playModeView.findViewById(R.id.singleLayout); - TextView orderTv = playModeView.findViewById(R.id.orderTv); - TextView randomTv = playModeView.findViewById(R.id.randomTv); - TextView singleTv = playModeView.findViewById(R.id.singleTv); - - //显示弹窗 - PopupWindow popupWindow = new PopupWindow(playModeView, ScreenUtil.dip2px(this, 130), ScreenUtil.dip2px(this, 150)); - //设置背景色 - popupWindow.setBackgroundDrawable(getDrawable(R.color.transparent)); - //设置焦点 - popupWindow.setFocusable(true); - //设置可以触摸框以外的地方 - popupWindow.setOutsideTouchable(true); - popupWindow.update(); - //设置弹出的位置 - popupWindow.showAsDropDown(mPlayModeBtn, 0, -50); - - - //显示播放模式 - int mode = mPresenter.getPlayMode(); - if (mode == Constant.PLAY_ORDER) { - orderTv.setSelected(true); - randomTv.setSelected(false); - singleTv.setSelected(false); - } else if (mode == Constant.PLAY_RANDOM) { - randomTv.setSelected(true); - orderTv.setSelected(false); - singleTv.setSelected(false); - } else { - singleTv.setSelected(true); - randomTv.setSelected(false); - orderTv.setSelected(false); - } +// 设置本地图片(可能是本地存储的封面图片或者根据歌手等信息生成的默认图片)的方法,使用Glide库加载图片,设置占位图、加载失败时显示的图片以及加载成功和失败的监听器,在加载成功后进行一系列处理,如更新界面显示、设置背景等 +private void setLocalImg(String singer) { + String imgUrl = Api.STORAGE_IMG_FILE + MediaUtil.formatSinger(singer) + ".jpg"; + Glide.with(this) + .load(imgUrl) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + mGetImgAndLrcBtn.setVisibility(View.VISIBLE); + mGetImgAndLrcBtn.setText("获取封面和歌词"); + setDiscImg(BitmapFactory.decodeResource(getResources(), R.drawable.default_disc)); + mRootLayout.setBackgroundResource(R.drawable.background); + return true; + } + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + return false; + } + }) + .apply(RequestOptions.placeholderOf(R.drawable.background)) + .apply(RequestOptions.errorOf(R.drawable.background)) + .into(new SimpleTarget() { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + mGetImgAndLrcBtn.setVisibility(View.GONE); + mImgBmp = ((BitmapDrawable) resource).getBitmap(); + try2UpdateMusicPicBackground(mImgBmp); + setDiscImg(mImgBmp); + } + }); +} - //顺序播放 - orderLayout.setOnClickListener(view -> { - mPlayStatusBinder.setPlayMode(Constant.PLAY_ORDER); //通知服务 - mPresenter.setPlayMode(Constant.PLAY_ORDER); - popupWindow.dismiss(); - mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_order)); +// 展示歌词的方法,隐藏唱碟视图,显示歌词视图,然后调用LrcView的方法设置歌词内容,并传入MediaPlayer对象以实现歌词与音乐播放进度同步,最后调用draw方法进行歌词绘制显示 +@Override +public void showLrc(final String lrc) { + mDisc.setVisibility(View.GONE); + mLrcView.setVisibility(View.VISIBLE); + Log.d(TAG, "showLrc: " + mPlayStatusBinder.getMediaPlayer().getCurrentPosition()); + mLrcView.setLrc(lrc).setPlayer(mPlayStatusBinder.getMediaPlayer()).draw(); +} - }); - //随机播放 - randomLayout.setOnClickListener(view -> { - mPlayStatusBinder.setPlayMode(Constant.PLAY_RANDOM); - mPresenter.setPlayMode(Constant.PLAY_RANDOM); - popupWindow.dismiss(); - mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_random)); - }); - //单曲循环 - singleLayout.setOnClickListener(view -> { - mPlayStatusBinder.setPlayMode(Constant.PLAY_SINGLE); - mPresenter.setPlayMode(Constant.PLAY_SINGLE); - popupWindow.dismiss(); - mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_single)); - }); +// 获取歌词失败的处理方法,显示一个获取歌词失败的Toast提示信息,同时将相关错误内容(可能是歌曲ID等信息)保存到歌曲对象中,并调用FileUtil的方法保存歌曲信息更新到本地存储 +@Override +public void getLrcError(String content) { + showToast(getString(R.string.get_lrc_fail)); + mSong.setQqId(content); + FileUtil.saveSong(mSong); +} +// 设置本地歌曲ID的方法,将传入的歌曲ID设置到歌曲对象中,并调用FileUtil的方法保存歌曲信息更新到本地存储,用于保存本地歌曲相关的ID信息 +@Override +public void setLocalSongId(String songId) { + mSong.setQqId(songId); + FileUtil.saveSong(mSong); //保存 +} - } +// 获取歌曲ID成功的处理方法,调用setLocalSongId方法保存获取到的歌曲ID信息到本地存储,然后调用Presenter的方法根据歌曲ID获取本地歌词 +@Override +public void getSongIdSuccess(String songId) { + setLocalSongId(songId);//保存音乐信息 + mPresenter.getLrc(songId, Constant.SONG_LOCAL);//获取歌词 +} +// 保存歌词到本地的方法,调用FileUtil的方法将歌词内容保存到本地存储中,方便后续本地歌曲播放时直接使用歌词信息 +@Override +public void saveLrc(String lrc) { + FileUtil.saveLrcToNative(lrc, mSong.getSongName()); +} - @Override - public void onDestroy() { - super.onDestroy(); - unbindService(mPlayConnection); - unbindService(mDownloadConnection); - EventBus.getDefault().unregister(this); - stopUpdateSeekBarProgress(); - - //避免内存泄漏 - mMusicHandler.removeCallbacksAndMessages(null); +// 改变播放模式的方法,创建一个包含播放模式选项的布局视图,设置弹出窗口的相关属性(大小、背景、焦点、可触摸外部等),然后根据当前播放模式设置对应选项的选中状态,最后为各个播放模式选项设置点击监听器,实现切换播放模式的功能 +private void changePlayMode() { + // 从布局资源中加载播放模式选择的布局视图 + View playModeView = LayoutInflater.from(this).inflate(R.layout.play_mode, null); + // 获取顺序播放模式对应的布局容器 + ConstraintLayout orderLayout = playModeView.findViewById(R.id.orderLayout); + // 获取随机播放模式对应的布局容器 + ConstraintLayout randomLayout = playModeView.findViewById(R.id.randomLayout); + // 获取单曲循环播放模式对应的布局容器 + ConstraintLayout singleLayout = playModeView.findViewById(R.id.singleLayout); + // 获取顺序播放模式对应的TextView,用于显示和设置选中状态 + TextView orderTv = playModeView.findViewById(R.id.orderTv); + // 获取随机播放模式对应的TextView,用于显示和设置选中状态 + TextView randomTv = playModeView.findViewById(R.id.randomTv); + // 获取单曲循环播放模式对应的TextView,用于显示和设置选中状态 + TextView singleTv = playModeView.findViewById(R.id.singleTv); + + // 创建一个PopupWindow弹出窗口对象,设置其内容视图、宽度和高度(通过dip单位转换为像素) + PopupWindow popupWindow = new PopupWindow(playModeView, ScreenUtil.dip2px(this, 130), ScreenUtil.dip2px(this, 150)); + // 设置弹出窗口的背景Drawable,传入透明背景的Drawable资源,使其背景透明 + popupWindow.setBackgroundDrawable(getDrawable(R.color.transparent)); + // 设置弹出窗口获取焦点,使其能够接收按键等操作事件 + popupWindow.setFocusable(true); + // 设置弹出窗口可以触摸框以外的地方,点击外部区域可关闭弹出窗口 + popupWindow.setOutsideTouchable(true); + popupWindow.update(); + // 设置弹出窗口显示的位置,使其在播放模式按钮下方弹出(垂直偏移一定距离) + popupWindow.showAsDropDown(mPlayModeBtn, 0, -50); + + // 显示当前播放模式对应的选项为选中状态,根据Presenter获取到的当前播放模式,设置对应TextView的选中状态 + int mode = mPresenter.getPlayMode(); + if (mode == Constant.PLAY_ORDER) { + orderTv.setSelected(true); + randomTv.setSelected(false); + singleTv.setSelected(false); + } else if (mode == Constant.PLAY_RANDOM) { + randomTv.setSelected(true); + orderTv.setSelected(false); + singleTv.setSelected(false); + } else { + singleTv.setSelected(true); + randomTv.setSelected(false); + orderTv.setSelected(false); } - private DownloadInfo getDownloadInfoFromSong(Song song){ - DownloadInfo downloadInfo = new DownloadInfo(); - downloadInfo.setSinger(song.getSinger()); - downloadInfo.setProgress(0); - downloadInfo.setSongId(song.getSongId()); - downloadInfo.setUrl(song.getUrl()); - downloadInfo.setSongName(song.getSongName()); - downloadInfo.setDuration(song.getDuration()); - return downloadInfo; - } + // 顺序播放模式选项的点击监听器,设置播放模式为顺序播放,通知PlayerService和Presenter更新播放模式,关闭弹出窗口,并更新播放模式按钮的背景图片为顺序播放模式图标 + orderLayout.setOnClickListener(view -> { + mPlayStatusBinder.setPlayMode(Constant.PLAY_ORDER); //通知服务 + mPresenter.setPlayMode(Constant.PLAY_ORDER); + popupWindow.dismiss(); + mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_order)); + + }); + // 随机播放模式选项的点击监听器,设置播放模式为随机播放,通知PlayerService和Presenter更新播放模式,关闭弹出窗口,并更新播放模式按钮的背景图片为随机播放模式图标 + randomLayout.setOnClickListener(view -> { + mPlayStatusBinder.setPlayMode(Constant.PLAY_RANDOM); + mPresenter.setPlayMode(Constant.PLAY_RANDOM); + popupWindow.dismiss(); + mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_random)); + }); + // 单曲循环播放模式选项的点击监听器,设置播放模式为单曲循环,通知PlayerService和Presenter更新播放模式,关闭弹出窗口,并更新播放模式按钮的背景图片为单曲循环模式图标 + singleLayout.setOnClickListener(view -> { + mPlayStatusBinder.setPlayMode(Constant.PLAY_SINGLE); + mPresenter.setPlayMode(Constant.PLAY_SINGLE); + popupWindow.dismiss(); + mPlayModeBtn.setBackground(getDrawable(R.drawable.play_mode_single)); + }); +} +// Activity销毁时的回调方法,进行一些资源释放和清理操作,如解除与服务的绑定、注销EventBus注册、停止SeekBar进度更新、移除Handler中的所有回调和消息,避免内存泄漏 +@Override +public void onDestroy() { + super.onDestroy(); + unbindService(mPlayConnection); + unbindService(mDownloadConnection); + EventBus.getDefault().unregister(this); + stopUpdateSeekBarProgress(); + + //避免内存泄漏 + mMusicHandler.removeCallbacksAndMessages(null); +} +// 根据传入的歌曲对象创建一个DownloadInfo对象,用于传递歌曲下载相关的信息(如歌手、进度、歌曲ID、歌曲名称、时长等),方便后续传递给下载服务来启动下载任务。 +private DownloadInfo getDownloadInfoFromSong(Song song) { + DownloadInfo downloadInfo = new DownloadInfo(); + downloadInfo.setSinger(song.getSinger()); + downloadInfo.setProgress(0); + downloadInfo.setSongId(song.getSongId()); + downloadInfo.setUrl(song.getUrl()); + downloadInfo.setSongName(song.getSongName()); + downloadInfo.setDuration(song.getDuration()); + return downloadInfo; }