Compare commits

...

19 Commits
master ... cw

@ -1,6 +1,5 @@
package com.example.musicplayer.view;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.ActivityOptions;
@ -53,218 +52,287 @@ import org.litepal.LitePal;
import butterknife.BindView;
import de.hdodenhof.circleimageview.CircleImageView;
// MainActivity类继承自BaseActivity是音乐播放器应用的主界面类负责处理界面展示、用户交互以及与音乐播放相关服务的交互等功能
public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivity";
// 通过ButterKnife框架绑定SeekBar控件用于显示音乐播放进度
@BindView(R.id.sb_progress)
SeekBar mSeekBar;
// 通过ButterKnife框架绑定TextView控件用于显示歌曲名称
@BindView(R.id.tv_song_name)
TextView mSongNameTv;
// 通过ButterKnife框架绑定TextView控件用于显示歌手名称
@BindView(R.id.tv_singer)
TextView mSingerTv;
// 通过ButterKnife框架绑定RippleView控件点击可切换到下一首歌曲
@BindView(R.id.song_next)
RippleView mNextIv;
// 通过ButterKnife框架绑定Button控件用于控制音乐的播放/暂停
@BindView(R.id.btn_player)
Button mPlayerBtn;
// 通过ButterKnife框架绑定CircleImageView控件用于显示歌曲封面图片
@BindView(R.id.circle_img)
CircleImageView mCoverIv;
// 通过ButterKnife框架绑定LinearLayout控件可能是包含播放相关控件的布局容器
@BindView(R.id.linear_player)
LinearLayout mLinear;
private boolean isChange; //拖动进度条
private boolean isSeek;//标记是否在暂停的时候拖动进度条
private boolean flag; //用做暂停的标记
private int time; //记录暂停的时间
private boolean isExistService;//服务是否存活
private ObjectAnimator mCircleAnimator;//动画
// 标记是否正在拖动进度条,用于防止进度条更新冲突
private boolean isChange;
// 标记是否在暂停的时候拖动进度条,用于后续恢复播放时正确设置播放位置
private boolean isSeek;
// 用作暂停的标记,用于区分不同的播放/暂停操作场景
private boolean flag;
// 记录暂停的时间,单位可能是毫秒,用于恢复播放时定位播放位置
private int time;
// 标记音乐播放相关服务是否存活,用于判断一些操作是否可行
private boolean isExistService;
// 属性动画对象,用于实现歌曲封面图片的旋转动画效果
private ObjectAnimator mCircleAnimator;
// 当前播放的歌曲对象,包含歌曲的相关信息如名称、歌手、时长等
private Song mSong;
// MediaPlayer对象用于实际的音频播放控制不过这里看起来主要通过服务中的MediaPlayer来操作
private MediaPlayer mMediaPlayer;
// 线程对象用于在后台更新SeekBar的进度显示
private Thread mSeekBarThread;
// 用于获取PlayerService中的播放状态相关信息和操作方法的Binder对象
private PlayerService.PlayStatusBinder mPlayStatusBinder;
// 用于获取DownloadService中的下载相关操作方法的Binder对象这里未看到详细使用
private DownloadService.DownloadBinder mDownloadBinder;
// 服务连接对象用于与PlayerService建立连接并获取服务端的Binder对象
private ServiceConnection connection = new ServiceConnection() {
// 当与服务成功连接时调用获取服务端传递过来的Binder对象
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayStatusBinder = (PlayerService.PlayStatusBinder) service;
}
// 当与服务意外断开连接时调用(比如服务崩溃),这里暂时没做具体处理
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
//绑定下载服务
// 下载服务的服务连接对象用于与DownloadService建立连接并获取服务端的Binder对象
private ServiceConnection mDownloadConnection = new ServiceConnection() {
// 当与下载服务成功连接时调用获取服务端传递过来的Binder对象
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mDownloadBinder = (DownloadService.DownloadBinder) iBinder;
if(isExistService) seekBarStart();
// 如果服务存在启动SeekBar进度更新
if (isExistService) seekBarStart();
}
// 当与下载服务意外断开连接时调用(比如服务崩溃),这里暂时没做具体处理
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
// 重写onDestroy方法在Activity销毁时执行清理和保存相关操作
@Override
public void onDestroy() {
super.onDestroy();
//解绑
// 除与PlayerService的
unbindService(connection);
// 解除与DownloadService的绑定
unbindService(mDownloadConnection);
//将播放的服务提升至前台服务
// 创建一个启动PlayerService的Intent目的是将播放的服务提升至前台服务针对Android 8.0及以上系统的处理)
Intent playIntent = new Intent(MainActivity.this, PlayerService.class);
//Android 8.0以上
// Android 8.0以上,以前台服务的方式启动服务
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(playIntent);
} else {
// Android 8.0以下,普通启动服务
startService(playIntent);
}
// 取消在EventBus上的注册避免内存泄漏等问题
EventBus.getDefault().unregister(this);
if (mSeekBarThread != null || mSeekBarThread.isAlive()) mSeekBarThread.interrupt();
// 如果SeekBar线程存在且正在运行中断该线程
if (mSeekBarThread!= null || mSeekBarThread.isAlive()) mSeekBarThread.interrupt();
// 获取当前播放的歌曲对象
Song song = FileUtil.getSong();
// 将歌曲当前播放时间设置为从服务获取到的当前时间,保存歌曲信息
song.setCurrentTime(mPlayStatusBinder.getCurrentTime());
FileUtil.saveSong(song);
}
// 获取Activity对应的布局资源ID这里返回activity_main布局的ID
@Override
protected int getLayoutId() {
return R.layout.activity_main;
}
// 初始化视图相关操作在Activity创建时调用
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected void initView() {
// 在EventBus上注册以便接收事件通知
EventBus.getDefault().register(this);
// 初始化LitePal数据库可能用于数据持久化等操作比如保存歌曲相关信息
LitePal.getDatabase();
//设置属性动画
// 创建属性动画设置CircleImageView歌曲封面图片的旋转动画从0度旋转到360度
mCircleAnimator = ObjectAnimator.ofFloat(mCoverIv, "rotation", 0.0f, 360.0f);
// 设置动画持续时间为30秒
mCircleAnimator.setDuration(30000);
// 设置线性插值器,使动画匀速进行
mCircleAnimator.setInterpolator(new LinearInterpolator());
// 设置动画重复次数为无限次
mCircleAnimator.setRepeatCount(-1);
// 设置动画重复模式为重新开始
mCircleAnimator.setRepeatMode(ValueAnimator.RESTART);
// 获取当前播放的歌曲对象
mSong = FileUtil.getSong();
if (mSong.getSongName() != null) {
// 如果歌曲名称不为空,说明有正在播放的歌曲,进行相关界面设置
if (mSong.getSongName()!= null) {
Log.d(TAG, "initView: " + mSong.toString());
// 显示包含播放相关控件的布局容器
mLinear.setVisibility(View.VISIBLE);
// 设置TextView显示歌曲名称
mSongNameTv.setText(mSong.getSongName());
// 设置TextView显示歌手名称
mSingerTv.setText(mSong.getSinger());
// 设置SeekBar的最大进度为歌曲的总时长单位可能需要转换这里看起来是以秒为单位存储时长
mSeekBar.setMax((int) mSong.getDuration());
// 设置SeekBar的当前进度为歌曲的当前播放时间
mSeekBar.setProgress((int) mSong.getCurrentTime());
// 如果歌曲封面图片的URL为空通过CommonUtil工具类根据歌手名称设置默认图片
if (mSong.getImgUrl() == null) {
CommonUtil.setSingerImg(MainActivity.this, mSong.getSinger(), mCoverIv);
} else {
// 使用Glide库加载歌曲封面图片设置占位图和加载失败时显示的图片
Glide.with(this)
.load(mSong.getImgUrl())
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(mCoverIv);
.load(mSong.getImgUrl())
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(mCoverIv);
}
} else {
// 如果没有正在播放的歌曲,设置默认的显示文本和图片
mSongNameTv.setText(getString(R.string.app_name));
mSingerTv.setText(getString(R.string.welcome_start));
mCoverIv.setImageResource(R.drawable.jay);
}
//如果播放服务还存活
if(ServiceUtil.isServiceRunning(this,PlayerService.class.getName())){
// 如果PlayerService正在运行服务存活
if (ServiceUtil.isServiceRunning(this, PlayerService.class.getName())) {
// 设置播放按钮为选中状态(表示正在播放)
mPlayerBtn.setSelected(true);
// 启动歌曲封面图片的旋转动画
mCircleAnimator.start();
// 标记服务存活
isExistService = true;
}
//处理服务
// 初始化相关服务(启动并绑定服务)
initService();
// 添加主界面的Fragment到Activity中
addMainFragment();
}
private void initService(){
//启动服务
// 启动和绑定音乐播放相关服务PlayerService和DownloadService
private void initService() {
// 创建启动PlayerService的Intent
Intent playIntent = new Intent(MainActivity.this, PlayerService.class);
// 创建启动DownloadService的Intent
Intent downIntent = new Intent(MainActivity.this, DownloadService.class);
//退出程序后依然能播放
// 如果是Android 8.0及以上系统以前台服务的方式启动PlayerService
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(playIntent);
}else {
} else {
// Android 8.0以下普通启动PlayerService
startService(playIntent);
}
// 绑定PlayerService建立与服务的连接并获取Binder对象
bindService(playIntent, connection, Context.BIND_AUTO_CREATE);
// 绑定DownloadService建立与服务的连接并获取Binder对象
bindService(downIntent, mDownloadConnection, Context.BIND_AUTO_CREATE);
}
// 接收OnlineSongErrorEvent事件的方法在主线程中处理这里只是简单弹出一个版权相关的提示Toast
@Subscribe(threadMode = ThreadMode.MAIN)
public void onOnlineSongErrorEvent(OnlineSongErrorEvent event) {
showToast(getString(R.string.error_out_of_copyright));
}
// 接收SongStatusEvent事件的方法在主线程中处理根据歌曲状态进行不同的界面和播放控制操作
@Subscribe(threadMode = ThreadMode.MAIN)
public void onSongStatusEvent(SongStatusEvent event) {
int status = event.getSongStatus();
// 如果歌曲状态是恢复播放
if (status == Constant.SONG_RESUME) {
// 设置播放按钮为选中状态(表示正在播放)
mPlayerBtn.setSelected(true);
// 恢复歌曲封面图片的旋转动画
mCircleAnimator.resume();
// 启动SeekBar进度更新
seekBarStart();
} else if (status == Constant.SONG_PAUSE) {
// 设置播放按钮为未选中状态(表示暂停)
mPlayerBtn.setSelected(false);
// 暂停歌曲封面图片的旋转动画
mCircleAnimator.pause();
} else if (status == Constant.SONG_CHANGE) {
// 获取当前播放的歌曲对象(可能歌曲切换了)
mSong = FileUtil.getSong();
// 更新TextView显示的歌曲名称
mSongNameTv.setText(mSong.getSongName());
// 更新TextView显示的歌手名称
mSingerTv.setText(mSong.getSinger());
// 设置SeekBar的最大进度为歌曲的总时长
mSeekBar.setMax((int) mSong.getDuration());
// 设置播放按钮为选中状态(表示正在播放)
mPlayerBtn.setSelected(true);
// 启动歌曲封面图片的旋转动画
mCircleAnimator.start();
// 启动SeekBar进度更新
seekBarStart();
// 如果歌曲不是在线歌曲可能是本地歌曲通过CommonUtil工具类根据歌手名称设置默认图片
if (!mSong.isOnline()) {
CommonUtil.setSingerImg(MainActivity.this, mSong.getSinger(), mCoverIv);
} else {
// 如果是在线歌曲使用Glide库加载歌曲封面图片设置占位图和加载失败时显示的图片
Glide.with(MainActivity.this)
.load(mSong.getImgUrl())
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(mCoverIv);
.load(mSong.getImgUrl())
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(mCoverIv);
}
}
}
// 初始化数据相关操作,这里暂时没有具体实现内容
@Override
protected void initData() {
}
// 处理各种点击事件的方法
@Override
protected void onClick() {
//进度条的监听事件
// 为SeekBar设置进度变化监听器用于处理进度条拖动等操作
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
// 当进度改变时调用(拖动过程中会多次调用),这里暂时没做具体处理
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
// 当开始拖动进度条时调用,设置标记防止与后台更新进度冲突
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
//防止在拖动进度条进行进度设置时与Thread更新播放进度条冲突
isChange = true;
}
// 当停止拖动进度条时调用根据播放状态设置新的播放位置并重新启动SeekBar进度更新
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (mPlayStatusBinder.isPlaying()) {
@ -278,7 +346,7 @@ public class MainActivity extends BaseActivity {
}
});
//控制按钮,播放,暂停
// 为播放/暂停按钮设置点击监听器,根据当前播放状态进行播放、暂停、恢复播放等操作
mPlayerBtn.setOnClickListener(v -> {
mMediaPlayer = mPlayStatusBinder.getMediaPlayer();
if (mPlayStatusBinder.isPlaying()) {
@ -292,7 +360,8 @@ public class MainActivity extends BaseActivity {
mMediaPlayer.seekTo(time * 1000);
isSeek = false;
}
} else {//退出程序后重新打开后的情况
} else {
// 退出程序后重新打开后的情况,根据歌曲是否在线选择不同的播放方式
if (FileUtil.getSong().isOnline()) {
mPlayStatusBinder.playOnline();
} else {
@ -302,71 +371,88 @@ public class MainActivity extends BaseActivity {
mMediaPlayer.seekTo((int) mSong.getCurrentTime() * 1000);
}
});
//下一首
mNextIv.setOnClickListener(v -> {
if (FileUtil.getSong().getSongName() != null) mPlayStatusBinder.next();
if (mPlayStatusBinder.isPlaying()) {
mPlayerBtn.setSelected(true);
} else {
mPlayerBtn.setSelected(false);
}
});
//点击播放栏,跳转到播放的主界面
mLinear.setOnClickListener(v -> {
if (FileUtil.getSong().getSongName() != null) {
Intent toPlayActivityIntent = new Intent(MainActivity.this, PlayActivity.class);
// 为下一首歌曲按钮设置点击监听器,点击时切换到下一首歌曲,并根据播放状态更新播放按钮状态
mNextIv.setOnClickListener(v -> {
// 如果当前有正在播放的歌曲歌曲名称不为空则调用PlayerService的next方法切换到下一首歌曲
if (FileUtil.getSong().getSongName()!= null) mPlayStatusBinder.next();
// 如果PlayerService正在播放歌曲设置播放按钮为选中状态表示正在播放否则设置为未选中状态表示暂停
if (mPlayStatusBinder.isPlaying()) {
mPlayerBtn.setSelected(true);
} else {
mPlayerBtn.setSelected(false);
}
});
//播放情况
if (mPlayStatusBinder.isPlaying()) {
Song song = FileUtil.getSong();
song.setCurrentTime(mPlayStatusBinder.getCurrentTime());
FileUtil.saveSong(song);
toPlayActivityIntent.putExtra(Constant.PLAYER_STATUS, Constant.SONG_PLAY);
} else {
//暂停情况
Song song = FileUtil.getSong();
song.setCurrentTime(mSeekBar.getProgress());
FileUtil.saveSong(song);
}
if (FileUtil.getSong().getImgUrl() != null) {
toPlayActivityIntent.putExtra(SearchContentFragment.IS_ONLINE, true);
}
//如果版本大于21
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(toPlayActivityIntent,
ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
} else {
startActivity(toPlayActivityIntent);
}
// 为包含播放相关控件的LinearLayout设置点击监听器点击时跳转到播放的主界面PlayActivity并传递相关播放状态等信息
mLinear.setOnClickListener(v -> {
// 如果当前有正在播放的歌曲(歌曲名称不为空),执行跳转操作
if (FileUtil.getSong().getSongName()!= null) {
Intent toPlayActivityIntent = new Intent(MainActivity.this, PlayActivity.class);
// 如果PlayerService正在播放歌曲获取当前歌曲对象设置当前播放时间并将播放状态设置为正在播放Constant.SONG_PLAY传递给目标Activity
if (mPlayStatusBinder.isPlaying()) {
Song song = FileUtil.getSong();
song.setCurrentTime(mPlayStatusBinder.getCurrentTime());
FileUtil.saveSong(song);
toPlayActivityIntent.putExtra(Constant.PLAYER_STATUS, Constant.SONG_PLAY);
} else {
showToast(getString(R.string.welcome_start));
// 如果歌曲处于暂停状态获取当前歌曲对象将SeekBar当前进度作为歌曲当前播放时间设置给歌曲对象并保存
Song song = FileUtil.getSong();
song.setCurrentTime(mSeekBar.getProgress());
FileUtil.saveSong(song);
}
});
}
// 如果歌曲封面图片的URL不为空说明是在线歌曲传递一个标识SearchContentFragment.IS_ONLINE为true给目标Activity
if (FileUtil.getSong().getImgUrl()!= null) {
toPlayActivityIntent.putExtra(SearchContentFragment.IS_ONLINE, true);
}
// 如果系统版本大于等于LOLLIPOPAndroid 5.0使用转场动画启动目标Activity
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
startActivity(toPlayActivityIntent,
ActivityOptions.makeSceneTransitionAnimation(MainActivity.this).toBundle());
} else {
// 如果系统版本小于Android 5.0直接启动目标Activity
startActivity(toPlayActivityIntent);
}
} else {
// 如果没有正在播放的歌曲弹出一个提示Toast显示默认的欢迎开始之类的文本
showToast(getString(R.string.welcome_start));
}
});
}
// 添加主界面的Fragment到Activity中的方法
private void addMainFragment() {
// 创建MainFragment实例
MainFragment mainFragment = new MainFragment();
// 获取FragmentManager对象用于管理Fragment事务
FragmentManager fragmentManager = getSupportFragmentManager();
// 开启Fragment事务
FragmentTransaction transaction = fragmentManager.beginTransaction();
// 将MainFragment添加到指定的布局容器R.id.fragment_container
transaction.add(R.id.fragment_container, mainFragment);
// 提交Fragment事务使添加操作生效
transaction.commit();
}
// 启动SeekBar进度更新的方法创建并启动一个线程来更新SeekBar的进度显示
private void seekBarStart() {
mSeekBarThread = new Thread(new SeekBarThread());
mSeekBarThread.start();
}
// 实现了Runnable接口的内部类用于在后台线程中更新SeekBar的进度显示
class SeekBarThread implements Runnable {
@Override
public void run() {
if (mPlayStatusBinder != null) {
// 只要没有正在拖动进度条且PlayerService正在播放歌曲就持续更新SeekBar的进度
if (mPlayStatusBinder!= null) {
while (!isChange && mPlayStatusBinder.isPlaying()) {
// 设置SeekBar的进度为从PlayerService获取到的当前播放时间单位可能需要和SeekBar的进度单位一致这里看起来是以秒为单位
mSeekBar.setProgress((int) mPlayStatusBinder.getCurrentTime());
try {
// 线程休眠1秒模拟每秒更新一次进度
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
@ -375,4 +461,4 @@ public class MainActivity extends BaseActivity {
}
}
}
}
}

@ -18,46 +18,61 @@ import com.example.musicplayer.R;
import com.example.musicplayer.adapter.SongAdapter;
import com.example.musicplayer.util.CommonUtil;
// WelcomeActivity类继承自AppCompatActivity是安卓应用中用于展示欢迎界面的Activity类
// 在这里主要负责处理欢迎界面相关的初始化操作、权限申请以及跳转到主界面的逻辑。
public class WelcomeActivity extends AppCompatActivity {
// 重写onCreate方法该方法在Activity创建时被调用用于进行各种初始化操作例如设置界面布局、申请权限等。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CommonUtil.hideStatusBar(this,true);
// 调用工具类CommonUtil中的方法隐藏状态栏传入true表示隐藏状态栏使欢迎界面能够全屏展示具体效果取决于CommonUtil中方法的实现逻辑
CommonUtil.hideStatusBar(this, true);
// 将窗口的背景Drawable设置为null可能是为了实现某种特定的背景效果比如透明背景等具体需结合应用整体的UI设计来看
getWindow().setBackgroundDrawable(null);
// 设置该Activity的界面布局为activity_welcome通过加载对应的布局资源文件来定义界面的外观和包含的控件等内容。
setContentView(R.layout.activity_welcome);
//申请权限
// 申请权限部分检查应用是否已经获取了写入外部存储的权限Manifest.permission.WRITE_EXTERNAL_STORAGE
// 如果没有获取到该权限ContextCompat.checkSelfPermission返回的结果不等于PackageManager.PERMISSION_GRANTED
// 则通过ActivityCompat.requestPermissions方法向用户请求该权限传入当前Activity实例、需要请求的权限数组这里只请求了写入外部存储权限以及请求码用于在权限申请结果回调中区分不同的权限申请
if (ContextCompat.checkSelfPermission(WelcomeActivity.this, Manifest.permission.
WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(WelcomeActivity.this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
// 如果已经获取了写入外部存储的权限通过Handler发送一个延迟消息延迟2000毫秒2秒后执行相应操作这里会调用getHome方法跳转到主界面。
mHandler.sendEmptyMessageDelayed(0, 2000);
}
}
// 创建一个Handler对象用于处理消息在这里主要用于在合适的时机延迟2秒后或者权限申请成功后执行跳转到主界面的操作。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 调用getHome方法该方法用于启动主界面Activity并关闭当前的欢迎界面Activity实现页面跳转。
getHome();
super.handleMessage(msg);
}
};
// 定义getHome方法用于启动主界面ActivityMainActivity并关闭当前的欢迎界面Activity实现从欢迎界面跳转到应用主界面的功能。
private void getHome() {
startActivity(new Intent(WelcomeActivity.this, MainActivity.class));
finish();
}
// 重写onRequestPermissionsResult方法该方法用于处理权限申请的结果回调根据用户是否授予权限来执行不同的逻辑。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
// 如果请求码为1对应之前请求写入外部存储权限时设置的请求码并且权限授予结果数组长度大于0且第一个元素对应写入外部存储权限的授予结果为PackageManager.PERMISSION_GRANTED表示权限已授予
// 则通过Handler发送一个延迟消息延迟2000毫秒2秒后执行相应操作这里同样会调用getHome方法跳转到主界面。
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mHandler.sendEmptyMessageDelayed(0, 2000);
} else {
// 如果权限被拒绝通过Toast显示一个提示信息告知用户拒绝该权限将无法使用程序然后调用finish方法关闭当前的欢迎界面Activity。
Toast.makeText(this, "拒绝该权限无法使用该程序", Toast.LENGTH_SHORT).show();
finish();
}
@ -66,4 +81,4 @@ public class WelcomeActivity extends AppCompatActivity {
break;
}
}
}
}

@ -1,6 +1,5 @@
package com.example.musicplayer.view.main;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
@ -39,139 +38,226 @@ import java.util.List;
/**
* A simple {@link Fragment} subclass.
* Fragment
*
*
*/
public class MainFragment extends Fragment {
private static final String TAG = "MainFragment";
// 自定义的可展开列表视图组件,用于展示如自建歌单、收藏歌单等相关歌单信息,支持分组展开和收缩查看详情
private MyListView myListView;
// 可展开列表视图的适配器负责将歌单数据分组及子项数据适配到MyListView上进行展示并处理相关点击等交互逻辑
private ExpandableListViewAdapter mAdapter;
private LinearLayout mLocalMusicLinear, mCollectionLinear, mHistoryMusicLinear,mDownloadLinear;
private TextView mLocalMusicNum, mLoveMusicNum, mHistoryMusicNum,mDownloadMusicNum;
// 用于展示本地音乐入口的线性布局,点击可进入本地音乐相关界面,方便用户操作
private LinearLayout mLocalMusicLinear,
// 用于展示收藏歌单入口的线性布局,点击可进入收藏歌单相关界面
mCollectionLinear,
// 用于展示历史播放音乐入口的线性布局,点击可进入历史播放音乐相关界面
mHistoryMusicLinear,
// 用于展示下载音乐入口的线性布局,点击可进入下载音乐相关界面
mDownloadLinear;
// 用于显示本地音乐数量的TextView组件实时展示本地音乐的数量情况
private TextView mLocalMusicNum,
// 用于显示收藏歌曲例如“我喜欢”歌单中歌曲数量等数量的TextView组件
mLoveMusicNum,
// 用于显示历史播放音乐数量的TextView组件
mHistoryMusicNum,
// 用于显示下载音乐数量的TextView组件
mDownloadMusicNum;
// 用于触发搜索操作的按钮可能是一个TextView样式上作为按钮使用点击可进入搜索相关界面
private TextView mSeekBtn;
// 存储歌单收藏信息的二维列表,外层列表按分组(如自建歌单、收藏歌单等)划分,内层列表存储每个分组下具体的歌单信息
private List<List<AlbumCollection>> mAlbumCollectionList;
// 存储“我喜欢”歌单信息的列表,属于一种特殊的收藏歌单,单独列出方便操作和展示
private List<AlbumCollection> mLoveAlbumList;
// 用于标记歌单展开和收缩状态的布尔变量,用于判断二级歌单展开或收缩的操作逻辑,辅助更新界面展示
private boolean twoExpand;
// 定义可展开列表视图中分组标题的字符串数组,这里包含了"自建歌单"和"收藏歌单"两个分组标题
private String[] mGroupStrings = {"自建歌单", "收藏歌单"};
/**
* Fragment
* EventBus
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 注册当前Fragment到EventBus用于接收事件例如歌单收藏变化、不同类型歌曲数量变化等事件用于更新界面等操作
EventBus.getDefault().register(this);
// 加载fragment_main布局文件并创建视图对象
View view = inflater.inflate(R.layout.fragment_main, container, false);
// 从加载的布局中找到对应的本地音乐入口线性布局组件
mLocalMusicLinear = view.findViewById(R.id.linear_local_music);
// 从加载的布局中找到对应的收藏歌单入口线性布局组件
mCollectionLinear = view.findViewById(R.id.linear_collection);
// 从加载的布局中找到对应的功能区线性布局组件(从代码中看可能用于整体获取焦点等操作,具体作用看业务逻辑)
LinearLayout mFunctionLinear = view.findViewById(R.id.linear_function);
// 从加载的布局中找到对应的历史播放音乐入口线性布局组件
mHistoryMusicLinear = view.findViewById(R.id.linear_history);
//获取焦点
// 设置功能区线性布局可在触摸模式下获取焦点,可能用于处理焦点相关交互逻辑(比如键盘操作等场景下焦点管理)
mFunctionLinear.setFocusableInTouchMode(true);
// 从加载的布局中找到对应的可展开列表视图组件
myListView = view.findViewById(R.id.expand_lv_song_list);
// 从加载的布局中找到对应的搜索按钮组件这里是一个TextView作为按钮使用
mSeekBtn = view.findViewById(R.id.tv_seek);
// 从加载的布局中找到对应的本地音乐数量显示TextView组件
mLocalMusicNum = view.findViewById(R.id.tv_local_music_num);
// 从加载的布局中找到对应的收藏歌曲数量显示TextView组件
mLoveMusicNum = view.findViewById(R.id.tv_love_num);
// 从加载的布局中找到对应的历史播放音乐数量显示TextView组件
mHistoryMusicNum = view.findViewById(R.id.tv_history_num);
// 从加载的布局中找到对应的下载音乐数量显示TextView组件
mDownloadMusicNum = view.findViewById(R.id.tv_download_num);
// 从加载的布局中找到对应的下载音乐入口线性布局组件
mDownloadLinear = view.findViewById(R.id.downloadLinear);
return view;
}
/**
* FragmentActivity
*
* @param savedInstanceState
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 调用方法展示歌单列表信息,包括创建数据集合、初始化数据、设置适配器等操作
showAlbumList();
// 调用方法设置各种点击事件的监听逻辑,如不同音乐分类入口点击、歌单展开收缩点击、歌单项点击等操作
onClick();
}
/**
*
*
*/
private void showAlbumList() {
// 创建用于存储“我喜欢”歌单信息的空列表
mLoveAlbumList = new ArrayList<>();
// 创建用于存储整体歌单收藏信息(按分组划分)的二维空列表
mAlbumCollectionList = new ArrayList<>();
// 创建一个AlbumCollection对象用于表示“我喜欢”歌单的基本信息这里设置了名称和歌手名称示例信息
AlbumCollection albumCollection = new AlbumCollection();
albumCollection.setAlbumName("我喜欢");
albumCollection.setSingerName("残渊");
// 将“我喜欢”歌单信息对象添加到对应的列表中
mLoveAlbumList.add(albumCollection);
mAlbumCollectionList.add(mLoveAlbumList);
// 从数据库中查询获取所有的AlbumCollection歌单收藏数据调用orderCollection方法对数据进行逆序排列整理
// 然后添加到整体歌单收藏列表中作为“收藏歌单”分组下的数据
mAlbumCollectionList.add(orderCollection(LitePal.findAll(AlbumCollection.class)));
// 创建可展开列表视图的适配器传入当前Activity上下文、分组标题数组和整理好的歌单收藏列表数据
// 用于将数据适配并展示到可展开列表视图上
mAdapter = new ExpandableListViewAdapter(getActivity(), mGroupStrings, mAlbumCollectionList);
// 将创建好的适配器设置给可展开列表视图组件,使其能够展示歌单列表信息
myListView.setAdapter(mAdapter);
}
/**
* FragmentEventBus
*/
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
/**
* Fragment
*/
@Override
public void onStart() {
super.onStart();
showMusicNum();
}
/**
* EventBusAlbumCollectionEvent
*
*
* @param event AlbumCollectionEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(AlbumCollectionEvent event) {
mAlbumCollectionList.clear();
mAlbumCollectionList.add(mLoveAlbumList);
mAlbumCollectionList.add(orderCollection(LitePal.findAll(AlbumCollection.class)));
//根据之前的状态,进行展开和收缩,从而达到更新列表的功能
if (twoExpand) {
myListView.collapseGroup(1);
myListView.expandGroup(1);
} else {
myListView.expandGroup(1);
myListView.collapseGroup(1);
}
mAlbumCollectionList.clear();
mAlbumCollectionList.add(mLoveAlbumList);
mAlbumCollectionList.add(orderCollection(LitePal.findAll(AlbumCollection.class)));
// 根据之前的状态,进行展开和收缩,从而达到更新列表的功能
if (twoExpand) {
myListView.collapseGroup(1);
myListView.expandGroup(1);
} else {
myListView.expandGroup(1);
myListView.collapseGroup(1);
}
}
/**
* EventBusSongListNumEvent
*
* @param event SongListNumEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onSongListEvent(SongListNumEvent event){
public void onSongListEvent(SongListNumEvent event) {
int type = event.getType();
if(type == Constant.LIST_TYPE_HISTORY){
if (type == Constant.LIST_TYPE_HISTORY) {
mHistoryMusicNum.setText(String.valueOf(LitePal.findAll(HistorySong.class).size()));
}else if(type == Constant.LIST_TYPE_LOCAL){
} else if (type == Constant.LIST_TYPE_LOCAL) {
mLocalMusicNum.setText(String.valueOf(LitePal.findAll(LocalSong.class).size()));
}else if(type == Constant.LIST_TYPE_DOWNLOAD){
} else if (type == Constant.LIST_TYPE_DOWNLOAD) {
mDownloadMusicNum.setText(String.valueOf(DownloadUtil.getSongFromFile(Api.STORAGE_SONG_FILE).size()));
}
}
/**
*
*
*/
private void onClick() {
//本地音乐
//本地音乐入口线性布局设置点击事件监听点击时跳转到本地音乐相关的FragmentLocalFragment
mLocalMusicLinear.setOnClickListener(v -> replaceFragment(new LocalFragment()));
//搜索
//搜索按钮设置点击事件监听点击时跳转到搜索相关的FragmentAlbumContentFragment.SearchFragment
mSeekBtn.setOnClickListener(v -> replaceFragment(new AlbumContentFragment.SearchFragment()));
//我的收藏
// 为收藏歌单入口线性布局设置点击事件监听点击时跳转到收藏歌单相关的FragmentCollectionFragment
mCollectionLinear.setOnClickListener(v -> replaceFragment(new CollectionFragment()));
//下载
//下载音乐入口线性布局设置点击事件监听点击时跳转到下载音乐相关的FragmentDownloadFragment
mDownloadLinear.setOnClickListener(view -> replaceFragment(new DownloadFragment()));
//最近播放
// 为历史播放音乐入口线性布局设置点击事件监听点击时跳转到历史播放音乐相关的FragmentHistoryFragment
mHistoryMusicLinear.setOnClickListener(v -> replaceFragment(new HistoryFragment()));
//歌单点击展开
// 为可展开列表视图设置分组展开监听事件当某个分组展开时如果是二级分组索引为1对应“收藏歌单”分组展开
// 则将twoExpand标记设置为true表示二级歌单处于展开状态
myListView.setOnGroupExpandListener(groupPosition -> {
if (groupPosition == 1) {
twoExpand = true;
}
});
//歌单点击收缩
// 为可展开列表视图设置分组收缩监听事件当某个分组收缩时如果是二级分组索引为1对应“收藏歌单”分组收缩
// 则将twoExpand标记设置为false表示二级歌单处于收缩状态
myListView.setOnGroupCollapseListener(groupPosition -> {
if (groupPosition == 1) {
twoExpand = false;
}
});
// 为可展开列表视图设置分组点击事件监听这里直接返回false表示点击分组标题时不进行默认的展开收缩切换操作
// 可能是为了自定义展开收缩逻辑或者避免与其他操作冲突,具体看业务需求
myListView.setOnGroupClickListener((parent, v, groupPosition, id) -> false);
//二级列表的item点击效果
// 为可展开列表视图的适配器设置子项(二级列表项,即具体歌单项)点击事件监听,点击时根据点击的位置进行不同的界面跳转操作
mAdapter.setOnChildItemClickListener((groupPosition, childPosition) -> {
//一级列表的第一个默认为我喜欢的歌单,故点击后跳转到我的收藏界面
// 如果点击的是一级列表的第一个子项通常对应“我喜欢”歌单索引为0则跳转到收藏歌单相关的FragmentCollectionFragment
if (groupPosition == 0 && childPosition == 0) {
replaceFragment(new CollectionFragment());
} else if (groupPosition == 1) {
//其他的列表都是我的收藏的列表故跳转到专辑详细fragment
// 如果点击的是二级列表中的其他项(对应“收藏歌单”分组下的具体歌单),则获取对应的歌单信息,
// 跳转到专辑详细信息展示的FragmentAlbumContentFragment并传递歌单相关参数如ID、名称、封面等用于展示详细内容
AlbumCollection albumCollection = mAlbumCollectionList.get(groupPosition).get(childPosition);
replaceFragment(AlbumContentFragment.newInstance(
albumCollection.getAlbumId(),
@ -184,34 +270,60 @@ public class MainFragment extends Fragment {
});
}
/**
* FragmentActivityFragmentFragment
* FragmentFragmentR.id.fragment_container
* Fragment便Fragment
* @param fragment Fragment
*/
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
//进入和退出动画
// 设置Fragment进入和退出动画效果分别对应进入、退出、从右侧进入、从右侧退出的动画资源ID
transaction.setCustomAnimations(R.anim.fragment_in, R.anim.fragment_out, R.anim.slide_in_right, R.anim.slide_out_right);
transaction.add(R.id.fragment_container, fragment);
transaction.hide(this);
//将事务提交到返回栈
// 将事务添加到返回栈按下返回键时可以按照添加顺序反向执行事务实现返回上一个Fragment的效果
transaction.addToBackStack(null);
transaction.commit();
}
//显示数目
/**
* LitePal
* TextView
*
*/
private void showMusicNum() {
// 获取本地歌曲数量通过LitePal框架查询本地歌曲LocalSong类对应的数据库表的所有记录数量
// 将数量转换为字符串并设置到对应的TextView组件上进行显示
mLocalMusicNum.setText(String.valueOf(LitePal.findAll(LocalSong.class).size()));
// 获取收藏歌曲这里可能是“我喜欢”等收藏相关歌曲Love类对应的数据库表的数量
// 通过LitePal框架查询所有记录数量将数量转换为字符串并设置到对应的TextView组件上进行显示
mLoveMusicNum.setText(String.valueOf(LitePal.findAll(Love.class).size()));
// 获取历史播放歌曲数量通过LitePal框架查询历史播放歌曲HistorySong类对应的数据库表的所有记录数量
// 将数量转换为字符串并设置到对应的TextView组件上进行显示
mHistoryMusicNum.setText(String.valueOf(LitePal.findAll(HistorySong.class).size()));
// 获取下载歌曲数量通过DownloadUtil工具类的特定方法getSongFromFile方法可能是从特定文件或存储位置获取下载歌曲信息
// 获取下载歌曲列表然后获取其数量将数量转换为字符串并设置到对应的TextView组件上进行显示
mDownloadMusicNum.setText(String.valueOf(DownloadUtil.getSongFromFile(Api.STORAGE_SONG_FILE).size()));
}
//使数据库中的列表逆序排列
/**
* 使AlbumCollection
*
*
* @param tempList AlbumCollection
* @return AlbumCollection
*/
private List<AlbumCollection> orderCollection(List<AlbumCollection> tempList) {
// 创建用于存储逆序排列后数据的空列表
List<AlbumCollection> mAlbumCollectionList = new ArrayList<>();
// 从传入列表的最后一个元素开始,逆序循环遍历每个元素
for (int i = tempList.size() - 1; i >= 0; i--) {
// 将当前元素添加到新创建的列表中
mAlbumCollectionList.add(tempList.get(i));
}
return mAlbumCollectionList;
}
}
}

@ -1,6 +1,5 @@
package com.example.musicplayer.view.main.collection;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@ -36,108 +35,249 @@ import java.util.Objects;
/**
* Created by on 2018/11/30.
* Fragment
*
* RecyclerView
*
*/
public class CollectionFragment extends Fragment {
private static final String TAG="CollectionFragment";
private static final String TAG = "CollectionFragment";
// 用于展示收藏歌曲列表的RecyclerView组件通过设置适配器等展示收藏歌曲的相关信息
// 如歌曲名称、歌手等,同时支持列表项的点击等交互操作(如点击播放歌曲等),方便用户操作收藏的歌曲。
private RecyclerView mRecycler;
// 用于返回上一界面的ImageView组件通常点击该图标可实现返回功能遵循常见的界面导航逻辑
// 使得用户可以方便地回到之前浏览的页面,增强应用的操作便利性。
private ImageView mBackIv;
// 用于管理RecyclerView布局的线性布局管理器设置收藏歌曲列表的布局方式如垂直排列等
// 决定了列表项在RecyclerView中的排列顺序和滚动方式等展示效果方便用户上下滑动查看不同的收藏歌曲信息。
private LinearLayoutManager mManager;
// 收藏歌曲列表的适配器负责将收藏歌曲的数据绑定到RecyclerView的每个Item视图上进行展示
// 并处理相关点击等交互逻辑,例如设置每首歌曲在列表中的显示样式,以及响应点击播放等操作。
private LoveSongAdapter mAdapter;
// 存储收藏歌曲信息Love类型的列表用于展示以及后续的操作如根据收藏相关事件更新列表内容、获取歌曲信息用于播放等
// 该列表的数据会从数据库或者其他存储方式获取并填充,然后传递给适配器用于展示在界面上。
private List<Love> mLoveList;
// 用于与播放服务进行交互的Binder对象通过它可以调用播放服务中的方法如播放收藏歌曲等操作
// 在与服务成功连接后获取其实例,以便后续在需要播放歌曲时使用。
private PlayerService.PlayStatusBinder mPlayStatusBinder;
// 定义与播放服务的连接对象用于监听服务的连接与断开状态并获取服务提供的Binder对象进行交互
// 是实现Fragment与播放服务通信的关键部分保障了相关播放服务功能如播放收藏歌曲等的正常调用
// 确保Fragment能和播放服务协同工作实现完整的收藏歌曲播放相关功能逻辑。
private ServiceConnection connection = new ServiceConnection() {
/**
* IBinderPlayStatusBinder
// 这一步是为了能通过强转后的对象调用播放服务中特定的、针对PlayStatusBinder类型定义的方法
// 例如播放歌曲等操作对应的方法,从而实现对收藏歌曲的播放控制。
* @param name
// 可以通过这个名称来区分不同的服务,确保连接到正确的目标服务。
* @param service IBinder
// 是实现跨组件Fragment与Service交互的重要媒介通过它可以调用服务端暴露的方法。
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayStatusBinder = (PlayerService.PlayStatusBinder) service;
}
/**
*
// 比如重新尝试连接、提示用户等操作例如可以弹出一个Toast提示用户播放服务异常断开
// 或者自动重新发起连接请求,以增强应用在面对服务异常情况时的稳定性和用户友好性。
* @param name
// 方便根据不同服务进行针对性的异常处理(如果有多个服务连接的情况)。
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* Fragment
// 同时注册到EventBus用于接收相关事件如歌曲收藏状态变化等事件为后续操作做准备
// 并创建一个空的收藏歌曲列表,用于后续填充收藏歌曲数据。
* @param inflater
// 它依据给定的布局资源文件解析并创建对应的视图层次结构是Android中构建界面视图的重要工具。
* @param container ActivityFragment
// Fragment的视图会被添加到这个容器中以正确显示在Activity的界面上。
* @param savedInstanceState
// 可以用于在Fragment重建比如屏幕旋转等情况时恢复之前的界面状态、数据等内容保持用户体验的连贯性。
* @return Fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 加载fragment_love布局文件并创建视图对象这一步根据指定的布局资源IDR.layout.fragment_love
// 使用布局填充器将布局文件解析并实例化为一个视图层次结构对象。
View mView = inflater.inflate(R.layout.fragment_love, container, false);
// 注册当前Fragment到EventBus用于接收事件例如歌曲新增收藏、取消收藏等相关事件
// 这样当其他地方发布了与歌曲收藏相关的事件时该Fragment能够接收到通知并做出相应的界面更新操作。
EventBus.getDefault().register(this); //注册事件订阅者
// 从加载的布局中找到对应的用于展示收藏歌曲列表的RecyclerView组件以便后续对其进行操作如设置属性、添加适配器等
mRecycler = mView.findViewById(R.id.recycler_love_songs);
// 从加载的布局中找到对应的返回上一界面的ImageView组件方便后续设置点击事件等操作。
mBackIv = mView.findViewById(R.id.iv_back);
// 创建一个空的收藏歌曲列表,后续将从数据库等途径获取收藏歌曲的详细信息并填充到该列表中。
mLoveList = new ArrayList<>();
return mView;
}
/**
* FragmentActivity
// 调用展示收藏歌曲列表的方法以及设置返回按钮点击事件监听的方法,完成界面的初始化及与播放服务建立通信连接,
// 为后续展示收藏歌曲列表以及播放歌曲操作做准备。
* @param savedInstanceState
// 不过在当前代码中未体现具体的恢复逻辑,一般可以用于一些诸如之前已经获取到的部分收藏歌曲相关数据等情况的恢复。
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//启动服务
// 创建启动播放服务的Intent对象指定要启动的服务类为PlayerService用于后续与播放服务的交互操作
// Intent是Android中用于启动组件如Activity、Service等或者传递消息的重要机制这里用于指定要启动的播放服务。
Intent playIntent = new Intent(getActivity(), PlayerService.class);
// 绑定播放服务通过传入的ServiceConnection对象connection监听服务连接状态
// 并设置绑定模式为自动创建服务如果服务未启动则自动启动确保播放服务能正常运行并与Fragment建立通信连接
// 这一步操作使得Fragment能够与播放服务进行双向通信获取服务提供的功能并向服务传递相关指令如播放歌曲等
Objects.requireNonNull(getActivity()).bindService(playIntent, connection, Context.BIND_AUTO_CREATE);
// 调用方法展示收藏歌曲列表,包括从数据库获取数据、设置布局管理器、创建适配器以及设置列表项点击事件监听等操作,
// 使得RecyclerView能够正确展示收藏歌曲的相关信息并能响应用户的点击播放等操作。
showSongList();
// 调用方法设置返回按钮的点击事件监听,使得点击返回按钮时能够执行相应的返回上一界面的操作,遵循常见的界面导航逻辑。
onClick();
}
/**
* FragmentEventBus
// 避免内存泄漏等问题释放相关资源确保应用的稳定运行和资源合理利用同时也符合Android组件生命周期管理的规范要求。
*/
@Override
public void onDestroy(){
public void onDestroy() {
super.onDestroy();
// 注销当前Fragment在EventBus的注册防止接收不必要的事件造成潜在的异常或者资源浪费等情况
// 确保Fragment在不再需要接收事件时停止相关监听操作。
EventBus.getDefault().unregister(this);
// 解除与播放服务的绑定,释放相关资源,避免因未正确关闭连接导致的资源泄漏问题。
Objects.requireNonNull(getActivity()).unbindService(connection);
}
/**
* EventBusSongCollectionEvent
// 先清空收藏歌曲列表再重新从数据库获取收藏歌曲数据填充列表通知RecyclerView的适配器数据集已改变触发界面刷新
// 如果事件表示是新增收藏歌曲且当前有正在播放歌曲通过FileUtil.getSong()判断则将RecyclerView滚动到对应歌曲位置进行展示
// 确保界面展示与实际收藏歌曲情况保持一致,能及时反映收藏歌曲的变化情况,并在合适情况下定位到相关歌曲位置。
* @param songCollectionEvent SongCollectionEvent
// 如是否是新增收藏通过isLove()方法判断)等情况,根据这些信息来决定如何更新界面展示内容。
*/
@Subscribe(threadMode = ThreadMode.MAIN )
public void onMessageEvent(SongCollectionEvent songCollectionEvent){
public void onMessageEvent(SongCollectionEvent songCollectionEvent) {
mLoveList.clear();
mLoveList.addAll(orderList(LitePal.findAll(Love.class)));
mAdapter.notifyDataSetChanged();
if(songCollectionEvent.isLove()){//定位歌曲
if (FileUtil.getSong() != null) {
if (songCollectionEvent.isLove()) {//定位歌曲
if (FileUtil.getSong()!= null) {
// 将RecyclerView滚动到指定位置通过获取当前播放歌曲的位置信息FileUtil.getSong().getPosition()
// 并加上一定偏移量这里是4以合适的视觉效果展示歌曲所在位置滚动高度依据RecyclerView自身高度进行调整
// 实现当新增收藏歌曲且该歌曲正在播放时,在界面上定位到该歌曲的功能,方便用户查看。
mManager.scrollToPositionWithOffset(FileUtil.getSong().getPosition() + 4, mRecycler.getHeight());
}
}
}
/**
* RecyclerView
* 线RecyclerViewRecyclerView
*
*
*/
private void showSongList() {
// 设置RecyclerView的尺寸固定属性为true这样可以提高RecyclerView的性能
// 避免在数据变化时重复计算尺寸等操作,适用于列表项尺寸相对固定的情况(如这里的歌曲列表)。
mRecycler.setHasFixedSize(true);
// 调用orderList方法对从数据库获取的所有收藏歌曲数据通过LitePal框架查询Love类对应的表数据进行逆序排列整理
// 逆序排列可能是为了按照某种特定顺序(如时间倒序等)展示收藏歌曲,更符合用户查看习惯。
mLoveList.addAll(orderList(LitePal.findAll(Love.class)));
// 创建收藏歌曲列表的适配器传入当前Activity上下文和整理后的收藏歌曲列表数据用于将数据绑定到视图上展示
// 适配器负责将数据与RecyclerView的视图项进行绑定设置每个视图项的展示样式并处理相关交互逻辑如点击事件
mAdapter = new LoveSongAdapter(getActivity(), mLoveList);
// 创建线性布局管理器设置为垂直方向排列默认情况用于管理RecyclerView中收藏歌曲列表的布局展示
// 确定列表项是垂直排列的,方便用户上下滑动查看不同的收藏歌曲信息,符合常见的列表展示习惯。
mManager = new LinearLayoutManager(getActivity());
// 将线性布局管理器设置给RecyclerView确定其布局方式使得音乐列表能按照设定的方式展示
// 确保RecyclerView的布局效果符合预期方便用户查看和操作列表中的歌曲信息。
mRecycler.setLayoutManager(mManager);
// 将创建好的适配器设置给RecyclerView使其能够展示收藏歌曲记录列表
// 这样RecyclerView就能依据适配器提供的数据和展示逻辑将收藏歌曲的信息展示给用户了。
mRecycler.setAdapter(mAdapter);
// 为收藏歌曲列表的适配器设置列表项点击事件监听,点击时进行以下操作,用于实现点击播放收藏歌曲的功能。
mAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onClick(int position) {
// 获取点击位置对应的收藏歌曲信息对象,以便后续提取该歌曲的各项详细信息进行相关操作。
Love love = mLoveList.get(position);
// 创建一个Song对象用于设置当前点击歌曲的相关信息方便后续保存和播放操作
// 这个Song对象可能是应用中统一用于表示歌曲信息的实体类整合了歌曲各方面的关键属性。
Song song = new Song();
// 设置歌曲的唯一标识,通常在数据库或者整个应用体系中有唯一性,用于准确区分不同歌曲。
song.setSongId(love.getSongId());
// 设置歌曲的QQ标识可能用于和QQ音乐等相关平台交互或者区分来源等情况具体依据应用逻辑
song.setQqId(love.getQqId());
// 设置歌曲的名称,用于在界面展示以及后续可能的查找、排序等操作中使用,方便用户识别歌曲。
song.setSongName(love.getName());
// 设置歌曲的演唱者信息,同样有助于用户明确歌曲来源以及在界面展示等场景中提供更详细的歌曲描述。
song.setSinger(love.getSinger());
// 设置歌曲是否为在线歌曲的标识,根据收藏歌曲记录中的对应属性进行赋值,
// 后续可能根据这个标识来决定播放时的获取方式(如在线播放还是本地播放等)。
song.setOnline(love.isOnline());
// 设置歌曲的播放地址,这是实际播放歌曲时需要的关键信息,播放服务会依据这个地址来获取音频资源进行播放。
song.setUrl(love.getUrl());
// 设置歌曲的封面图片地址(如果有的话),可用于在界面展示歌曲时显示对应的封面图片,提升视觉效果和用户体验。
song.setImgUrl(love.getPic());
// 设置歌曲在列表中的位置索引,方便记录当前点击歌曲在列表中的顺序等相关操作,比如定位播放位置等。
song.setPosition(position);
// 设置歌曲的时长信息,用于在界面展示歌曲时长或者进行一些与时长相关的操作(如播放进度计算等)。
song.setDuration(love.getDuration());
// 设置歌曲的媒体资源唯一标识(可能用于更细致地区分不同版本或者来源的媒体资源等情况)。
song.setMediaId(love.getMediaId());
// 设置歌曲所属的列表类型为收藏类型,用于标识歌曲来源或者在应用中进行分类管理等操作,
// 例如在不同的播放逻辑或者统计场景下可以依据这个类型进行区分处理。
song.setListType(Constant.LIST_TYPE_LOVE);
// 通过FileUtil工具类将歌曲相关信息保存到本地具体保存的方式和位置由FileUtil的实现决定
// 可能是保存到本地文件或者数据库中,方便后续操作(如记录播放历史、恢复播放状态等),
// 确保应用能持久化用户的操作记录以及播放相关信息,提升用户体验的连贯性。
FileUtil.saveSong(song);
// 通过获取到的与播放服务交互的Binder对象mPlayStatusBinder调用播放服务中的播放方法
// 传入Constant.LIST_TYPE_LOVE表示播放的是收藏类型的歌曲从而实现点击收藏歌曲列表中的歌曲后进行播放的功能。
mPlayStatusBinder.play(Constant.LIST_TYPE_LOVE);
}
});
}
/**
* ImageView
* FragmentFragment
* 便使便退
*/
private void onClick() {
mBackIv.setOnClickListener(v -> getActivity().getSupportFragmentManager().popBackStack());
}
private List<Love> orderList(List<Love> tempList){
List<Love> loveList=new ArrayList<>();
/**
*
*
* 使
* @param tempList
*
* @return
* 便
*/
private List<Love> orderList(List<Love> tempList) {
List<Love> loveList = new ArrayList<>();
loveList.clear();
for(int i=tempList.size()-1;i>=0;i--){
for (int i = tempList.size() - 1; i >= 0; i--) {
loveList.add(tempList.get(i));
}
return loveList;
}
}
}

@ -29,49 +29,118 @@ import butterknife.Unbinder;
* time : 2019/09/16
* desc :
* </pre>
* Fragment
* FragmentTabLayoutViewPager
* 便
*
*/
public class DownloadFragment extends Fragment {
// 通过ButterKnife框架绑定的返回上一界面的ImageView组件点击该图标可实现返回功能
// 遵循常见的界面导航逻辑,让用户可以方便地回到之前浏览的页面,增强应用的操作便利性。
@BindView(R.id.backIv)
ImageView backIv;
// 通过ButterKnife框架绑定的TabLayout组件用于展示选项卡在这里它将提供不同下载状态如已下载、正在下载的切换标签
// 用户可以通过点击不同标签切换到对应的下载歌曲列表展示页面,实现多页面切换展示的交互功能。
@BindView(R.id.tabLayout)
TabLayout tabLayout;
// 通过ButterKnife框架绑定的ViewPager组件它与TabLayout配合使用用于承载和切换不同的Fragment页面
// 根据TabLayout中选中的标签来展示对应的Fragment如展示已下载歌曲列表的Fragment或者正在下载歌曲列表的Fragment
// 提供了流畅的页面切换视觉效果和交互体验。
@BindView(R.id.page)
ViewPager page;
// 用于解除ButterKnife框架绑定的对象在合适的时机如Fragment销毁时需要调用其unbind方法来释放相关资源避免内存泄漏。
private Unbinder unbinder;
// 存储选项卡标题文本的列表用于设置TabLayout中每个选项卡显示的标题内容
// 这里的标题对应不同的下载状态,比如“已下载”“正在下载”等,方便用户直观了解每个标签对应的功能和展示内容。
private List<String> mTitleList;
// 存储要在ViewPager中展示的Fragment的列表每个Fragment代表了不同下载状态歌曲列表的展示页面
// 通过将对应的Fragment添加到该列表中ViewPager就能根据用户在TabLayout中的选择切换展示不同的Fragment页面。
private List<Fragment> mFragments;
// 用于ViewPager的适配器负责将Fragment列表与对应的标题列表关联起来并管理ViewPager中Fragment页面的展示与切换逻辑
// 它继承自合适的Adapter类此处是自定义的TabAdapter实现了将数据Fragment和标题适配到ViewPager组件上的功能。
private TabAdapter mAdapter;
// 定义了选项卡标题的字符串数组包含了要在TabLayout中显示的各个选项卡的标题文本内容
// 这里明确了有两个选项卡分别对应“已下载”和“正在下载”两种下载歌曲的状态分类方便后续初始化TabLayout时使用。
private String[] mTitles = {"已下载", "正在下载"};
/**
* FragmentButterKnife
*
* Fragment
*
* @param inflater
* Android
* @param container ActivityFragment
* FragmentActivity
* @param savedInstanceState
* Fragment
* @return Fragment
*/
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 加载fragment_download布局文件并创建视图对象这一步根据指定的布局资源IDR.layout.fragment_download
// 使用布局填充器将布局文件解析并实例化为一个视图层次结构对象。
View view = inflater.inflate(R.layout.fragment_download, container, false);
// 通过ButterKnife框架将视图与代码中的变量进行绑定使得可以方便地操作布局中的视图组件
// 例如通过绑定后的变量来设置ImageView、TabLayout、ViewPager等组件的属性、添加点击事件监听等操作。
unbinder = ButterKnife.bind(this, view);
// 调用方法初始化TabLayout和ViewPager相关设置包括创建标题列表、添加要展示的Fragment、创建适配器以及关联TabLayout与ViewPager等操作
// 完成下载模块界面中选项卡切换展示不同下载状态歌曲列表的初始化配置工作。
initTab();
// 调用方法设置返回按钮的点击事件监听,使得点击返回按钮时能够执行相应的返回上一界面的操作,遵循常见的界面导航逻辑。
onClick();
return view;
}
/**
* TabLayoutViewPagerFragment
* FragmentFragment
* ViewPagerFragmentFragment
* ViewPagerTabLayoutViewPager使ViewPagerFragment
* Fragment
*/
private void initTab() {
// 创建一个空的用于存储选项卡标题文本的列表,后续将添加具体的标题内容(如“已下载”“正在下载”等)到该列表中。
mTitleList = new ArrayList<>();
// 创建一个空的用于存储要在ViewPager中展示的Fragment的列表后续将添加对应的Fragment实例到该列表中。
mFragments = new ArrayList<>();
// 将预先定义好的选项卡标题字符串数组转换为列表形式并添加到标题列表中这样标题列表就包含了所有要在TabLayout中显示的标题内容
// 用于后续设置TabLayout的各个选项卡的标题文本。
mTitleList.addAll(Arrays.asList(mTitles));
// 创建一个代表已下载歌曲列表展示的Fragment实例并添加到Fragment列表中该Fragment负责展示已下载完成的歌曲相关信息及操作界面。
mFragments.add(new DownloadMusicFragment());
// 创建一个代表正在下载歌曲列表展示的Fragment实例并添加到Fragment列表中该Fragment负责展示正在下载的歌曲相关信息及操作界面。
mFragments.add(new DownloadingFragment());
// 创建用于ViewPager的适配器传入当前Fragment的子Fragment管理器通过getChildFragmentManager方法获取
// Fragment列表以及标题列表用于将这些数据关联起来实现管理ViewPager中Fragment页面展示与切换的功能。
mAdapter = new TabAdapter(getChildFragmentManager(), mFragments, mTitleList);
// 将创建好的适配器设置给ViewPager使得ViewPager能够依据适配器提供的信息来展示和切换不同的Fragment页面
// 确定了ViewPager展示内容的来源和切换逻辑。
page.setAdapter(mAdapter);
// 将TabLayout与ViewPager进行关联使得TabLayout中的选项卡切换操作能够同步控制ViewPager中Fragment页面的切换展示
// 实现用户点击不同选项卡就能看到对应下载状态歌曲列表的交互功能,确保两者协同工作,提供流畅的界面切换体验。
tabLayout.setupWithViewPager(page);
}
private void onClick(){
/**
* ImageView
* FragmentFragment
* 便使便退
*/
private void onClick() {
backIv.setOnClickListener(view -> Objects.requireNonNull(getActivity()).getSupportFragmentManager().popBackStack());
}
/**
* FragmentButterKnife
*
* Android
*/
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}
}

@ -47,105 +47,240 @@ import static com.example.musicplayer.app.Api.STORAGE_SONG_FILE;
* time : 2019/09/16
* desc :
* </pre>
* Fragment
*
* RecyclerView
*
*/
public class DownloadMusicFragment extends Fragment {
private static final String TAG = "DownloadMusicFragment";
// 通过ButterKnife框架绑定的RecyclerView视图组件用于展示已下载歌曲的列表
// 可以展示每首已下载歌曲的相关信息(如歌曲名称、歌手等),并支持列表项的点击等交互操作(如点击播放歌曲等)。
@BindView(R.id.songRecycle)
RecyclerView songRecycle;
// 用于解除ButterKnife框架绑定的对象在合适的时机如Fragment销毁时需要调用其unbind方法来释放相关资源避免内存泄漏。
Unbinder unbinder;
// 用于管理RecyclerView布局的线性布局管理器设置已下载歌曲列表的布局方式如垂直排列等
// 决定了列表项在RecyclerView中的排列顺序和滚动方式等展示效果方便用户上下滑动查看不同的已下载歌曲信息。
private LinearLayoutManager mManager;
// 下载歌曲列表的适配器负责将已下载歌曲的数据绑定到RecyclerView的每个Item视图上进行展示
// 并处理相关点击等交互逻辑,例如设置每首歌曲在列表中的显示样式,以及响应点击播放等操作。
private DownloadSongAdapter mAdapter;
// 存储已下载歌曲信息DownloadSong类型的列表用于展示以及后续的操作如根据下载相关事件更新列表内容、获取歌曲信息用于播放等
// 该列表的数据会从本地文件或者通过相关工具类获取并填充,然后传递给适配器用于展示在界面上。
private List<DownloadSong> mDownloadSongList; //已下载歌曲列表
// 用于与播放服务进行交互的Binder对象通过它可以调用播放服务中的方法如播放已下载歌曲等操作
// 在与服务成功连接后获取其实例,以便后续在需要播放歌曲时使用。
private PlayerService.PlayStatusBinder mPlayStatusBinder;
// 定义与播放服务的连接对象用于监听服务的连接与断开状态并获取服务提供的Binder对象进行交互
// 是实现Fragment与播放服务通信的关键部分保障了相关播放服务功能如播放已下载歌曲等的正常调用
// 确保Fragment能和播放服务协同工作实现完整的已下载歌曲播放相关功能逻辑。
private ServiceConnection connection = new ServiceConnection() {
/**
* IBinderPlayStatusBinder
// 这一步是为了能通过强转后的对象调用播放服务中特定的、针对PlayStatusBinder类型定义的方法
// 例如播放歌曲等操作对应的方法,从而实现对已下载歌曲的播放控制。
* @param name
// 可以通过这个名称来区分不同的服务,确保连接到正确的目标服务。
* @param service IBinder
// 是实现跨组件Fragment与Service交互的重要媒介通过它可以调用服务端暴露的方法。
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayStatusBinder = (PlayerService.PlayStatusBinder) service;
}
/**
*
// 比如重新尝试连接、提示用户等操作例如可以弹出一个Toast提示用户播放服务异常断开
// 或者自动重新发起连接请求,以增强应用在面对服务异常情况时的稳定性和用户友好性。
* @param name
// 方便根据不同服务进行针对性的异常处理(如果有多个服务连接的情况)。
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* Fragment
// 同时注册到EventBus用于接收相关事件如歌曲下载成功、已下载歌曲有更新等事件
// 并通过ButterKnife框架将视图与代码中的变量进行绑定还创建了一个空的已下载歌曲列表为后续操作做准备。
* @param inflater
// 它依据给定的布局资源文件解析并创建对应的视图层次结构是Android中构建界面视图的重要工具。
* @param container ActivityFragment
// Fragment的视图会被添加到这个容器中以正确显示在Activity的界面上。
* @param savedInstanceState
// 可以用于在Fragment重建比如屏幕旋转等情况时恢复之前的界面状态、数据等内容保持用户体验的连贯性。
* @return Fragment
*/
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 加载fragment_download_music布局文件并创建视图对象这一步根据指定的布局资源IDR.layout.fragment_download_music
// 使用布局填充器将布局文件解析并实例化为一个视图层次结构对象。
View view = inflater.inflate(R.layout.fragment_download_music, container, false);
// 注册当前Fragment到EventBus用于接收事件例如歌曲下载成功、已下载歌曲列表有更新等相关事件
// 这样当其他地方发布了与下载相关的事件时该Fragment能够接收到通知并做出相应的界面更新操作。
EventBus.getDefault().register(this);
// 通过ButterKnife框架将视图与代码中的变量进行绑定使得可以方便地操作布局中的视图组件
// 例如通过绑定后的变量来设置RecyclerView的属性、添加点击事件监听等操作。
unbinder = ButterKnife.bind(this, view);
// 创建一个空的已下载歌曲列表,后续将从本地文件等途径获取已下载歌曲的详细信息并填充到该列表中。
mDownloadSongList = new ArrayList<>();
return view;
}
/**
* FragmentActivity
// 调用展示已下载歌曲列表的方法,完成界面的初始化及与播放服务建立通信连接,为后续展示已下载歌曲列表以及播放歌曲操作做准备。
* @param savedInstanceState
// 不过在当前代码中未体现具体的恢复逻辑,一般可以用于一些诸如之前已经获取到的部分已下载歌曲相关数据等情况的恢复。
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//启动服务
// 创建启动播放服务的Intent对象指定要启动的服务类为PlayerService用于后续与播放服务的交互操作
// Intent是Android中用于启动组件如Activity、Service等或者传递消息的重要机制这里用于指定要启动的播放服务。
Intent playIntent = new Intent(getActivity(), PlayerService.class);
// 绑定播放服务通过传入的ServiceConnection对象connection监听服务连接状态
// 并设置绑定模式为自动创建服务如果服务未启动则自动启动确保播放服务能正常运行并与Fragment建立通信连接
// 这一步操作使得Fragment能够与播放服务进行双向通信获取服务提供的功能并向服务传递相关指令如播放歌曲等
Objects.requireNonNull(getActivity()).bindService(playIntent, connection, Context.BIND_AUTO_CREATE);
// 调用方法展示已下载歌曲列表,包括从本地文件获取数据、设置布局管理器、创建适配器以及设置列表项点击事件监听等操作,
// 使得RecyclerView能够正确展示已下载歌曲的相关信息并能响应用户的点击播放等操作。
showSongList();
}
private void showSongList(){
/**
*
// 创建已下载歌曲列表的适配器创建线性布局管理器并设置给RecyclerView最后将适配器设置给RecyclerView用于展示已下载歌曲记录列表
// 同时为已下载歌曲列表的适配器设置列表项点击事件监听,点击时进行一系列歌曲相关信息的设置(如歌曲名称、歌手、播放地址等),
// 保存歌曲信息到本地,并调用播放服务的方法开始播放对应已下载歌曲,完成列表展示及点击播放功能的配置工作。
*/
private void showSongList() {
// 调用orderList方法对从本地文件通过DownloadUtil.getSongFromFile方法依据指定的文件路径STORAGE_SONG_FILE获取
// 获取到的已下载歌曲数据进行逆序排列整理,逆序排列可能是为了按照某种特定顺序(如时间倒序等)展示已下载歌曲,更符合用户查看习惯。
mDownloadSongList = orderList(DownloadUtil.getSongFromFile(STORAGE_SONG_FILE));
// 创建已下载歌曲列表的适配器传入当前Activity上下文和整理后的已下载歌曲列表数据用于将数据绑定到视图上展示
// 适配器负责将数据与RecyclerView的视图项进行绑定设置每个视图项的展示样式并处理相关交互逻辑如点击事件
mAdapter = new DownloadSongAdapter(getActivity(), mDownloadSongList);
// 创建线性布局管理器设置为垂直方向排列默认情况用于管理RecyclerView中已下载歌曲列表的布局展示
// 确定列表项是垂直排列的,方便用户上下滑动查看不同的已下载歌曲信息,符合常见的列表展示习惯。
mManager = new LinearLayoutManager(getActivity());
// 将线性布局管理器设置给RecyclerView确定其布局方式使得音乐列表能按照设定的方式展示
// 确保RecyclerView的布局效果符合预期方便用户查看和操作列表中的歌曲信息。
songRecycle.setLayoutManager(mManager);
// 将创建好的适配器设置给RecyclerView使其能够展示已下载歌曲记录列表
// 这样RecyclerView就能依据适配器提供的数据和展示逻辑将已下载歌曲的信息展示给用户了。
songRecycle.setAdapter(mAdapter);
// 为已下载歌曲列表的适配器设置列表项点击事件监听,点击时进行以下操作,用于实现点击播放已下载歌曲的功能。
mAdapter.setOnItemClickListener(position -> {
// 获取点击位置对应的已下载歌曲信息对象,以便后续提取该歌曲的各项详细信息进行相关操作。
DownloadSong downloadSong = mDownloadSongList.get(position);
// 创建一个Song对象用于设置当前点击歌曲的相关信息方便后续保存和播放操作
// 这个Song对象可能是应用中统一用于表示歌曲信息的实体类整合了歌曲各方面的关键属性。
Song song = new Song();
// 设置歌曲的唯一标识,通常在数据库或者整个应用体系中有唯一性,用于准确区分不同歌曲。
song.setSongId(downloadSong.getSongId());
// 设置歌曲的名称,用于在界面展示以及后续可能的查找、排序等操作中使用,方便用户识别歌曲。
song.setSongName(downloadSong.getName());
// 设置歌曲的演唱者信息,同样有助于用户明确歌曲来源以及在界面展示等场景中提供更详细的歌曲描述。
song.setSinger(downloadSong.getSinger());
// 设置歌曲是否为在线歌曲的标识为false表示是已下载的本地歌曲用于后续播放等操作中区分歌曲来源类型。
song.setOnline(false);
// 设置歌曲的播放地址,这是实际播放歌曲时需要的关键信息,播放服务会依据这个地址来获取音频资源进行播放。
song.setUrl(downloadSong.getUrl());
Log.d(TAG, "showSongList: "+song.getUrl());
Log.d(TAG, "showSongList: " + song.getUrl());
// 设置歌曲的封面图片地址(如果有的话),可用于在界面展示歌曲时显示对应的封面图片,提升视觉效果和用户体验。
song.setImgUrl(downloadSong.getPic());
// 设置歌曲在列表中的位置索引,方便记录当前点击歌曲在列表中的顺序等相关操作,比如定位播放位置等。
song.setPosition(position);
// 设置歌曲的时长信息,用于在界面展示歌曲时长或者进行一些与时长相关的操作(如播放进度计算等)。
song.setDuration(downloadSong.getDuration());
// 设置歌曲所属的列表类型为下载类型,用于标识歌曲来源或者在应用中进行分类管理等操作,
// 例如在不同的播放逻辑或者统计场景下可以依据这个类型进行区分处理。
song.setListType(Constant.LIST_TYPE_DOWNLOAD);
// 设置歌曲的媒体资源唯一标识(可能用于更细致地区分不同版本或者来源的媒体资源等情况)。
song.setMediaId(downloadSong.getMediaId());
// 设置歌曲的下载状态为已下载,用于记录歌曲的下载情况等相关属性设置。
song.setDownload(true);
// 通过FileUtil工具类将歌曲相关信息保存到本地具体保存的方式和位置由FileUtil的实现决定
// 可能是保存到本地文件或者数据库中,方便后续操作(如记录播放历史、恢复播放状态等),
// 确保应用能持久化用户的操作记录以及播放相关信息,提升用户体验的连贯性。
FileUtil.saveSong(song);
// 通过获取到的与播放服务交互的Binder对象mPlayStatusBinder调用播放服务中的播放方法
// 传入Constant.LIST_TYPE_DOWNLOAD表示播放的是已下载类型的歌曲从而实现点击已下载歌曲列表中的歌曲后进行播放的功能。
mPlayStatusBinder.play(Constant.LIST_TYPE_DOWNLOAD);
});
}
/**
* EventBusDownloadEvent
// 进行相应的界面更新操作,先清空已下载歌曲列表,再重新从本地文件获取已下载歌曲数据填充列表,
// 最后通知RecyclerView的适配器数据集已改变触发界面刷新展示最新的已下载歌曲列表情况
// 确保界面展示与实际已下载歌曲情况保持一致,能及时反映新下载歌曲的添加情况。
* @param event DownloadEvent
// 根据事件携带的具体信息,来判断是否是歌曲下载成功的情况,进而决定如何更新界面展示内容。
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onDownloadSuccessEvent(DownloadEvent event){
if(event.getDownloadStatus() == Constant.TYPE_DOWNLOAD_SUCCESS){
public void onDownloadSuccessEvent(DownloadEvent event) {
if (event.getDownloadStatus() == Constant.TYPE_DOWNLOAD_SUCCESS) {
mDownloadSongList.clear();
mDownloadSongList.addAll(orderList(DownloadUtil.getSongFromFile(STORAGE_SONG_FILE)));
mAdapter.notifyDataSetChanged();
}
}
/**
* EventBusSongDownloadedEvent
* RecyclerViewSongDownloadedEvent
* 使
* @param event SongDownloadedEvent
*
*
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onSongDownloadedEvent(SongDownloadedEvent event){
public void onSongDownloadedEvent(SongDownloadedEvent event) {
mAdapter.notifyDataSetChanged();
}
/**
* FragmentButterKnifeEventBus
* Android
*/
@Override
public void onDestroyView() {
super.onDestroyView();
// 调用ButterKnife框架的unbind方法解除视图与代码中变量的绑定关系释放相关资源
// 避免因绑定关系未正确解除而导致的内存泄漏问题。
unbinder.unbind();
// 注销当前Fragment在EventBus的注册防止接收不必要的事件造成潜在的异常或者资源浪费等情况
// 确保Fragment在不再需要接收事件时停止相关监听操作。
EventBus.getDefault().unregister(this);
}
private List<DownloadSong> orderList(List<DownloadSong> tempList){
List<DownloadSong> loveList=new ArrayList<>();
/**
*
*
* 使
* @param tempList
*
* @return
* 便
*/
private List<DownloadSong> orderList(List<DownloadSong> tempList) {
List<DownloadSong> loveList = new ArrayList<>();
loveList.clear();
for(int i=tempList.size()-1;i>=0;i--){
for (int i = tempList.size() - 1; i >= 0; i--) {
loveList.add(tempList.get(i));
}
return loveList;
}
}
}

@ -47,123 +47,241 @@ import butterknife.Unbinder;
* time : 2019/09/16
* desc :
* </pre>
* Fragment
*
* RecyclerView
*
*/
public class DownloadingFragment extends Fragment {
private static final String TAG = "DownloadingFragment";
// 通过ButterKnife框架绑定的RecyclerView视图组件用于展示正在下载歌曲的列表
// 可以展示每首正在下载歌曲的相关信息(如歌曲名称、下载进度等),并支持列表项的交互操作(如点击暂停、取消下载等)。
@BindView(R.id.songDowningRecycle)
RecyclerView songDowningRecycle;
// 用于解除ButterKnife框架绑定的对象在合适的时机如Fragment销毁时需要调用其unbind方法来释放相关资源避免内存泄漏。
Unbinder unbinder;
// 正在下载歌曲列表的适配器负责将正在下载歌曲的数据绑定到RecyclerView的每个Item视图上进行展示
// 并处理相关点击等交互逻辑,例如设置每首歌曲在列表中的显示样式,以及响应点击暂停、取消下载等操作。
private DownloadingAdapter mAdapter;
// 用于管理RecyclerView布局的线性布局管理器设置正在下载歌曲列表的布局方式如垂直排列等
// 决定了列表项在RecyclerView中的排列顺序和滚动方式等展示效果方便用户上下滑动查看不同的正在下载歌曲信息。
private LinearLayoutManager mLinearLayoutManager;
// 存储正在下载歌曲信息DownloadInfo类型的列表用于展示以及后续的操作如根据下载状态更新列表项等
// 该列表的数据会从数据库或者通过与下载服务交互等方式获取并填充,然后传递给适配器用于展示在界面上。
private List<DownloadInfo> mDownloadInfoList; //下载队列
// 用于与下载服务进行交互的Binder对象通过它可以调用下载服务中的方法如开始下载、暂停下载、取消下载等操作
// 在与服务成功连接后获取其实例,以便后续在需要控制歌曲下载时使用。
private DownloadService.DownloadBinder mDownloadBinder;
//绑定下载服务
// 定义与下载服务的连接对象用于监听服务的连接与断开状态并获取服务提供的Binder对象进行交互
// 是实现Fragment与下载服务通信的关键部分保障了相关下载服务功能如控制下载操作等的正常调用
// 确保Fragment能和下载服务协同工作实现完整的正在下载歌曲管理相关功能逻辑。
// 绑定下载服务
private ServiceConnection mDownloadConnection = new ServiceConnection() {
/**
* IBinderDownloadBinder
// 这一步是为了能通过强转后的对象调用下载服务中特定的、针对DownloadBinder类型定义的方法
// 例如开始下载、暂停下载等操作对应的方法,从而实现对下载过程的控制。
* @param componentName
// 可以通过这个名称来区分不同的服务,确保连接到正确的目标服务。
* @param iBinder IBinder
// 是实现跨组件Fragment与Service交互的重要媒介通过它可以调用服务端暴露的方法。
*/
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mDownloadBinder = (DownloadService.DownloadBinder) iBinder;
}
/**
*
// 比如重新尝试连接、提示用户等操作例如可以弹出一个Toast提示用户下载服务异常断开
// 或者自动重新发起连接请求,以增强应用在面对服务异常情况时的稳定性和用户友好性。
* @param componentName
// 方便根据不同服务进行针对性的异常处理(如果有多个服务连接的情况)。
*/
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
/**
* Fragment
// 同时注册到EventBus用于接收相关事件如下载进度更新、下载完成等事件
// 并通过ButterKnife框架将视图与代码中的变量进行绑定为后续操作做准备。
* @param inflater
// 它依据给定的布局资源文件解析并创建对应的视图层次结构是Android中构建界面视图的重要工具。
* @param container ActivityFragment
// Fragment的视图会被添加到这个容器中以正确显示在Activity的界面上。
* @param savedInstanceState
// 可以用于在Fragment重建比如屏幕旋转等情况时恢复之前的界面状态、数据等内容保持用户体验的连贯性。
* @return Fragment
*/
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 加载fragment_download_ing布局文件并创建视图对象这一步根据指定的布局资源IDR.layout.fragment_download_ing
// 使用布局填充器将布局文件解析并实例化为一个视图层次结构对象。
View view = inflater.inflate(R.layout.fragment_download_ing, container, false);
// 注册当前Fragment到EventBus用于接收事件例如下载进度更新、下载完成、下载取消等相关事件
// 这样当其他地方发布了与下载相关的事件时该Fragment能够接收到通知并做出相应的界面更新操作。
EventBus.getDefault().register(this);
// 通过ButterKnife框架将视图与代码中的变量进行绑定使得可以方便地操作布局中的视图组件
// 例如通过绑定后的变量来设置RecyclerView的属性、添加点击事件监听等操作。
unbinder = ButterKnife.bind(this, view);
return view;
}
/**
* FragmentActivityRecyclerView
// 完成界面的初始化及与下载服务建立通信连接,为后续展示正在下载歌曲列表以及控制下载操作做准备。
* @param savedInstanceState
// 不过在当前代码中未体现具体的恢复逻辑,一般可以用于一些诸如之前已经获取到的部分下载相关数据等情况的恢复。
*/
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
// 调用方法初始化RecyclerView相关设置包括创建数据列表、设置布局管理器、创建适配器以及设置列表项点击事件监听等操作
// 使得RecyclerView能够正确展示正在下载歌曲的相关信息并能响应用户的操作如暂停、取消下载等
initRecycler();
//绑定服务
// 创建启动下载服务的Intent对象指定要启动的服务类为DownloadService用于后续与下载服务的交互操作
// Intent是Android中用于启动组件如Activity、Service等或者传递消息的重要机制这里用于指定要启动的下载服务。
Intent downIntent = new Intent(getActivity(), DownloadService.class);
// 绑定下载服务通过传入的ServiceConnection对象mDownloadConnection监听服务连接状态
// 并设置绑定模式为自动创建服务如果服务未启动则自动启动确保下载服务能正常运行并与Fragment建立通信连接
// 这一步操作使得Fragment能够与下载服务进行双向通信获取服务提供的功能并向服务传递相关指令如开始下载、暂停下载等
getActivity().bindService(downIntent, mDownloadConnection, Context.BIND_AUTO_CREATE);
super.onActivityCreated(savedInstanceState);
}
/**
* RecyclerView
// 设置RecyclerView的相关属性如解决进度刷新闪屏问题、创建线性布局管理器、创建适配器以及设置列表项的点击事件监听暂停和取消下载
// 全面完成RecyclerView展示正在下载歌曲列表的初始化配置工作使得界面能够正确展示正在下载歌曲的信息并对用户的操作做出响应。
*/
private void initRecycler() {
// 创建用于存储正在下载歌曲信息的空列表,后续将从数据库或者其他途径获取正在下载歌曲的详细信息并填充到该列表中。
mDownloadInfoList = new LinkedList<>();
mDownloadInfoList.addAll(LitePal.findAll(DownloadInfo.class,true));
// 使用LitePal框架查询获取数据库中所有的正在下载歌曲信息DownloadInfo类对应的表数据并添加到正在下载歌曲信息列表中
// LitePal是一个方便的Android数据库操作框架简化了数据库查询等操作这里用于获取存储在数据库中的正在下载歌曲记录信息。
mDownloadInfoList.addAll(LitePal.findAll(DownloadInfo.class, true));
// 设置RecyclerView的ItemAnimator为null用于解决进度刷新时可能出现的闪屏问题
// 这样在更新下载进度等操作导致界面刷新时,能有更平滑的视觉效果,提升用户体验。
songDowningRecycle.setItemAnimator(null); //解决进度刷新闪屏问题
// 创建线性布局管理器设置为垂直方向排列默认情况用于管理RecyclerView中正在下载歌曲列表的布局展示
// 确定列表项是垂直排列的,方便用户上下滑动查看不同的正在下载歌曲信息,符合常见的列表展示习惯。
mLinearLayoutManager = new LinearLayoutManager(getActivity());
// 创建正在下载歌曲列表的适配器,传入正在下载歌曲信息列表,用于将数据绑定到视图上展示,
// 适配器负责将数据与RecyclerView的视图项进行绑定设置每个视图项的展示样式并处理相关交互逻辑如点击事件
mAdapter = new DownloadingAdapter(mDownloadInfoList);
// 将线性布局管理器设置给RecyclerView确定其布局方式使得音乐列表能按照设定的方式展示
// 确保RecyclerView的布局效果符合预期方便用户查看和操作列表中的歌曲信息。
songDowningRecycle.setLayoutManager(mLinearLayoutManager);
// 将创建好的适配器设置给RecyclerView使其能够展示正在下载歌曲记录列表
// 这样RecyclerView就能依据适配器提供的数据和展示逻辑将正在下载歌曲的信息展示给用户了。
songDowningRecycle.setAdapter(mAdapter);
//暂停
// 为正在下载歌曲列表的适配器设置列表项点击事件监听,用于处理暂停下载操作,点击时进行以下操作。
mAdapter.setOnItemClickListener(position -> {
// 获取点击位置对应的正在下载歌曲信息对象,以便后续根据其当前下载状态来决定执行开始下载还是暂停下载操作。
DownloadInfo downloadInfo = mDownloadInfoList.get(position);
Log.d(TAG, "initRecycler: "+mDownloadInfoList.get(position).getStatus());
//判断是否为正在播放的歌曲
if(downloadInfo.getStatus() == Constant.DOWNLOAD_PAUSED){
Log.d(TAG, "initRecycler: " + mDownloadInfoList.get(position).getStatus());
// 判断是否为正在播放的歌曲这里可能通过歌曲的下载状态来判断具体依据Constant.DOWNLOAD_PAUSED常量的定义
// 如果当前歌曲的下载状态是暂停状态,则调用下载服务的方法开始下载该歌曲。
if (downloadInfo.getStatus() == Constant.DOWNLOAD_PAUSED) {
mDownloadBinder.startDownload(downloadInfo);
}else {
//传入要暂停的音乐id
} else {
// 如果歌曲不是暂停状态(可能是正在下载等其他状态),则传入要暂停的音乐id,调用下载服务的方法暂停该歌曲的下载。
mDownloadBinder.pauseDownload(downloadInfo.getSongId());
}
});
//取消下载
// 为正在下载歌曲列表的适配器设置列表项点击删除(取消下载)事件监听,点击时进行以下操作,
// 通过弹出一个确认对话框来提示用户是否确认取消下载操作,用户确认后调用下载服务的方法取消对应歌曲的下载。
mAdapter.setOnDeleteClickListener(position -> {
SpeedDialog speedDialog = new SpeedDialog(getActivity(),SpeedDialog.SELECT_TYPE);
// 创建一个SpeedDialog对话框实例用于显示确认取消下载的提示信息设置对话框的类型为选择类型可能包含确定、取消等按钮
SpeedDialog speedDialog = new SpeedDialog(getActivity(), SpeedDialog.SELECT_TYPE);
// 设置对话框的标题,通过获取字符串资源来显示友好的提示标题,告知用户当前操作是取消下载。
speedDialog.setTitle(getString(R.string.download_cancel))
.setMessage(getString(R.string.download_cancel_message))
.setSureText(getString(R.string.download_sure))
.setSureClickListener(dialog -> {
// 设置对话框的详细提示信息,同样通过获取字符串资源来显示更详细的提示内容,让用户明确取消下载的后果等信息。
.setMessage(getString(R.string.download_cancel_message))
// 设置对话框中确认按钮的文本内容,使用字符串资源来设置一个友好的确认按钮文本,如“确定”等。
.setSureText(getString(R.string.download_sure))
// 设置对话框中确认按钮的点击事件监听,当用户点击确认按钮时,调用下载服务的方法取消对应位置歌曲的下载,
// 传入要取消下载的歌曲信息对象(从正在下载歌曲信息列表中获取对应位置的歌曲信息)。
.setSureClickListener(dialog -> {
mDownloadBinder.cancelDownload(mDownloadInfoList.get(position));
})
.show();
// 显示对话框,将其展示给用户,等待用户进行操作(确认或取消),以决定是否取消对应歌曲的下载。
.show();
});
}
/**
* EventBusDownloadEvent
*
*
* @param event DownloadEvent
*
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onDownloadingMessage(DownloadEvent event) {
int status = event.getDownloadStatus();
if (status == Constant.TYPE_DOWNLOADING || status == Constant.TYPE_DOWNLOAD_PAUSED) {//进度条更新
DownloadInfo downloadInfo = event.getDownloadInfo();
// 先从正在下载歌曲信息列表中移除对应位置的歌曲信息,
mDownloadInfoList.remove(downloadInfo.getPosition());
mDownloadInfoList.add(downloadInfo.getPosition(),downloadInfo);
// 再将更新后的歌曲信息添加回原来的位置,这样可以确保列表中对应歌曲的信息是最新的,
mDownloadInfoList.add(downloadInfo.getPosition(), downloadInfo);
// 通知RecyclerView的适配器对应位置的列表项数据已改变触发界面更新使得下载进度条等信息能及时刷新显示。
mAdapter.notifyItemChanged(downloadInfo.getPosition());
}else if(status == Constant.TYPE_DOWNLOAD_SUCCESS){//歌曲下载成功
} else if (status == Constant.TYPE_DOWNLOAD_SUCCESS) {//歌曲下载成功
// 调用方法重新从数据库中读取需要下载的歌曲信息,更新正在下载歌曲信息列表,
// 因为下载成功后可能列表中的数据状态发生了变化(比如该歌曲不再处于正在下载状态了),需要重新获取最新数据。
resetDownloadInfoList();
// 通知RecyclerView的适配器数据集已改变触发整个RecyclerView界面刷新展示最新的正在下载歌曲列表情况
// 例如将已下载成功的歌曲从正在下载列表中移除等,保持界面展示与实际数据的一致性。
mAdapter.notifyDataSetChanged();
}else if(status == Constant.TYPE_DOWNLOAD_CANCELED){//下载取消
} else if (status == Constant.TYPE_DOWNLOAD_CANCELED) {//下载取消
// 同样调用方法重新从数据库中读取需要下载的歌曲信息,更新正在下载歌曲信息列表,
// 下载取消后列表中的数据也需要更新,去除已取消下载的歌曲相关信息,获取最新的正在下载歌曲情况。
resetDownloadInfoList();
// 通知RecyclerView的适配器数据集已改变触发界面刷新使得界面展示能及时反映下载取消后的列表变化情况
// 让用户看到准确的正在下载歌曲状态。
mAdapter.notifyDataSetChanged();
}else if(status == Constant.TYPE_DOWNLOAD_ADD){//添加了正在下载歌曲
} else if (status == Constant.TYPE_DOWNLOAD_ADD) {//添加了正在下载歌曲
// 当有新的歌曲添加到正在下载队列时,调用此方法重新从数据库中读取正在下载歌曲信息,
// 这样可以获取到包含新添加歌曲信息的最新列表数据,用于更新界面展示。
resetDownloadInfoList();
}
}
//重新从数据库中读取需要下载的歌曲
private void resetDownloadInfoList(){
// 重新从数据库中读取需要下载的歌曲
private void resetDownloadInfoList() {
// 先清空正在下载歌曲信息列表,准备重新填充最新的数据。
mDownloadInfoList.clear();
List<DownloadInfo> temp = LitePal.findAll(DownloadInfo.class,true);
if(temp.size()!=0){
// 使用LitePal框架查询获取数据库中所有的正在下载歌曲信息DownloadInfo类对应的表数据
// 如果查询到有相关数据,就将其添加到正在下载歌曲信息列表中,以更新列表内容。
List<DownloadInfo> temp = LitePal.findAll(DownloadInfo.class, true);
if (temp.size()!= 0) {
mDownloadInfoList.addAll(temp);
}
}
/**
* FragmentEventBusButterKnife
* Android
*/
@Override
public void onDestroyView() {
super.onDestroyView();
// 注销当前Fragment在EventBus的注册防止接收不必要的事件造成潜在的异常或者资源浪费等情况
// 确保Fragment在不再需要接收事件时停止相关监听操作。
EventBus.getDefault().unregister(this);
// 调用ButterKnife框架的unbind方法解除视图与代码中变量的绑定关系释放相关资源
// 避免因绑定关系未正确解除而导致的内存泄漏问题。
unbinder.unbind();
}
}
}

@ -36,103 +36,259 @@ import java.util.List;
/**
* Created by on 2018/12/2.
* Fragment
*
* RecyclerView
*
*/
public class HistoryFragment extends Fragment {
private static final String TAG="HistoryFragment";
private static final String TAG = "HistoryFragment";
// 用于展示历史播放音乐列表的RecyclerView组件通过设置适配器等展示历史播放歌曲的相关信息
// 如歌曲名称、歌手等,同时支持列表项的点击等交互操作,方便用户操作历史播放记录中的歌曲。
private RecyclerView mRecycler;
// 用于返回上一界面的ImageView组件通常点击该图标可实现返回功能遵循常见的界面导航逻辑
// 使得用户可以方便地回到之前浏览的页面,增强应用的操作便利性。
private ImageView mBackIv;
// 用于管理RecyclerView布局的线性布局管理器设置历史播放音乐列表的布局方式如垂直排列等
// 决定了列表项在RecyclerView中的排列顺序和滚动方式等展示效果方便用户上下滑动查看不同的历史播放歌曲记录。
private LinearLayoutManager mManager;
// 历史播放音乐列表的适配器负责将历史播放歌曲数据绑定到RecyclerView的每个Item视图上进行展示
// 例如设置每首歌曲在列表中的显示样式,同时处理相关点击等交互逻辑,如响应点击某首歌曲时的播放等操作。
private HistoryAdapter mAdapter;
// 用于展示包含历史播放音乐列表的线性布局,可能用于整体布局调整或者和其他相关组件组合展示等用途,
// 比如可以在该线性布局内添加一些装饰性元素或者其他与列表相关的附属信息展示等。
private LinearLayout mSongListLinear;
// 用于在历史播放音乐列表为空时展示提示信息等相关布局的RelativeLayout组件提示用户当前无历史播放记录
// 例如可以在该布局中显示一段友好的提示文字,告知用户暂无历史播放歌曲可供查看,提升用户体验。
private RelativeLayout mEmptyRelative;
// 存储历史播放歌曲数据HistorySong类型的列表用于展示以及后续的操作如点击播放等
// 该列表会从数据库或者其他存储方式获取历史播放歌曲的详细信息并填充,然后传递给适配器用于展示在界面上。
private List<HistorySong> mHistoryList;
// 用于显示标题的TextView组件从代码中看设置为“最近播放”用于标识该界面展示的是历史播放相关内容
// 让用户一眼能明确当前界面的功能和所展示数据的性质,符合界面设计的友好性原则。
private TextView mTitleTv;
// 用于与播放服务进行交互的Binder对象通过它可以调用播放服务中的方法如获取历史播放列表、控制播放等操作
// 在与服务成功连接后获取其实例,以便后续在需要与播放服务交互时(比如播放历史歌曲、获取更多播放历史相关信息等操作)使用。
private PlayerService.PlayStatusBinder mPlayStatusBinder;
// 定义与播放服务的连接对象用于监听服务的连接与断开状态并获取服务提供的Binder对象进行交互
// 是实现Fragment与播放服务通信的关键部分保障了相关播放服务功能如获取历史播放列表、执行播放操作等的正常调用
// 确保Fragment能和播放服务协同工作实现完整的历史播放相关功能逻辑。
private ServiceConnection connection = new ServiceConnection() {
/**
* IBinderPlayStatusBinder
// 这一步是为了能通过强转后的对象调用播放服务中特定的、针对PlayStatusBinder类型定义的方法
// 同时调用播放服务中的获取历史播放列表方法mPlayStatusBinder.getHistoryList()
// 用于获取用户之前的播放记录信息,以便后续展示在界面上,实现数据的获取和准备展示的操作流程。
* @param name
// 可以通过这个名称来区分不同的服务,确保连接到正确的目标服务。
* @param service IBinder
// 是实现跨组件Fragment与Service交互的重要媒介通过它可以调用服务端暴露的方法。
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayStatusBinder = (PlayerService.PlayStatusBinder) service;
mPlayStatusBinder.getHistoryList();
}
/**
*
// 比如重新尝试连接、提示用户等操作例如可以弹出一个Toast提示用户播放服务异常断开
// 或者自动重新发起连接请求,以增强应用在面对服务异常情况时的稳定性和用户友好性。
* @param name
// 方便根据不同服务进行针对性的异常处理(如果有多个服务连接的情况)。
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* Fragment
// 这是Fragment展示界面的基础操作通过加载布局文件将定义好的界面结构实例化为视图对象
// 同时注册到EventBus用于接收相关事件如历史播放列表更新等事件为后续操作做准备
// 使得Fragment能够响应外部发布的相关事件及时更新界面展示内容。
* @param inflater
// 它依据给定的布局资源文件解析并创建对应的视图层次结构是Android中构建界面视图的重要工具。
* @param container ActivityFragment
// Fragment的视图会被添加到这个容器中以正确显示在Activity的界面上。
* @param savedInstanceState
// 可以用于在Fragment重建比如屏幕旋转等情况时恢复之前的界面状态、数据等内容保持用户体验的连贯性。
* @return Fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 加载fragment_love布局文件并创建视图对象这里布局文件名可能不太准确对应功能也许是复用的布局或者后续需要修改为更合适的名字
// 这一步根据指定的布局资源IDR.layout.fragment_love使用布局填充器将布局文件解析并实例化为一个视图层次结构对象。
View view = inflater.inflate(R.layout.fragment_love, container, false);
// 注册当前Fragment到EventBus用于接收事件例如历史播放相关的更新事件等用于更新界面等操作
// 这样当其他地方发布了与历史播放相关的事件时该Fragment能够接收到通知并做出相应的界面更新操作。
EventBus.getDefault().register(this);
// 从加载的布局中找到对应的用于展示历史播放音乐列表的RecyclerView组件
// 通过视图对象的findViewById方法依据布局文件中定义的组件ID获取对应的视图实例以便后续对其进行操作。
mRecycler = view.findViewById(R.id.recycler_love_songs);
// 从加载的布局中找到对应的返回上一界面的ImageView组件同样是通过ID查找视图实例方便后续设置点击事件等操作。
mBackIv = view.findViewById(R.id.iv_back);
// 从加载的布局中找到对应的在列表为空时展示提示信息的RelativeLayout组件获取该布局实例用于后续控制其显示隐藏等操作。
mEmptyRelative = view.findViewById(R.id.relative_empty);
// 从加载的布局中找到对应的包含历史播放音乐列表的线性布局组件,以备后续对整个列表区域进行布局相关操作(如添加子视图、设置背景等)。
mSongListLinear = view.findViewById(R.id.linear_song_list);
// 从加载的布局中找到对应的用于显示标题的TextView组件获取该实例用于设置标题文本等操作。
mTitleTv = view.findViewById(R.id.tv_title);
// 设置标题TextView的文本内容为“最近播放”明确界面展示的是历史播放记录相关内容
// 让用户能直观地了解当前界面所呈现信息的性质,符合界面设计的清晰易懂原则。
mTitleTv.setText("最近播放");
return view;
}
/**
* FragmentActivity
// 通过绑定服务建立与播放服务的通信连接,以便后续调用服务中的方法获取历史播放数据等操作,
// 调用展示历史播放歌曲列表的方法以及设置各种点击事件的监听逻辑,完成界面的初始化及交互功能配置,
// 使得界面能够正确展示历史播放歌曲列表,并对用户的操作(如点击歌曲、点击返回按钮等)做出响应。
* @param savedInstanceState
// 例如之前已经获取到的部分历史播放数据等情况,不过在当前代码中未体现具体的恢复逻辑。
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//启动服务
// 创建启动播放服务的Intent对象指定要启动的服务类为PlayerService用于后续与播放服务的交互操作
// Intent是Android中用于启动组件如Activity、Service等或者传递消息的重要机制这里用于指定要启动的播放服务。
Intent playIntent = new Intent(getActivity(), PlayerService.class);
// 绑定播放服务通过传入的ServiceConnection对象监听服务连接状态
// 并设置绑定模式为自动创建服务如果服务未启动则自动启动确保播放服务能正常运行并与Fragment建立通信连接
// 这一步操作使得Fragment能够与播放服务进行双向通信获取服务提供的功能并向服务传递相关指令如获取历史播放列表、执行播放操作等
getActivity().bindService(playIntent, connection, Context.BIND_AUTO_CREATE);
// 调用方法展示历史播放歌曲列表,包括从数据库获取数据、设置适配器、布局管理器等操作,使得列表能正确展示,
// 完成RecyclerView展示历史播放歌曲的相关配置工作让用户能够看到历史播放记录的具体内容。
showSongList();
// 调用方法设置各种点击事件的监听逻辑,如历史播放歌曲列表项点击、返回上一界面点击等操作,实现交互功能,
// 通过设置点击事件监听,使得用户在界面上的操作能够触发相应的业务逻辑,如播放歌曲、返回上一界面等。
onClick();
}
/**
* FragmentEventBus
// 解除服务绑定可以避免因未正确关闭连接导致的资源泄漏问题注销EventBus注册防止接收不必要的事件造成的潜在异常
// 避免内存泄漏等问题,释放相关资源,确保应用的稳定运行和资源合理利用,提高应用的性能和稳定性。
*/
@Override
public void onDestroy(){
public void onDestroy() {
super.onDestroy();
getActivity().unbindService(connection);
EventBus.getDefault().unregister(this);
}
@Subscribe(threadMode = ThreadMode.MAIN )
public void onMessageEvent(SongHistoryEvent event){
/**
* EventBusSongHistoryEvent
// SongHistoryEvent可能是一个自定义的事件类用于传递与历史播放歌曲相关的变化信息如新增、删除记录等情况
// 通知历史播放音乐列表适配器数据集已改变触发RecyclerView更新界面展示内容
// 通过调用适配器的notifyDataSetChanged方法告知RecyclerView数据发生了变化需要重新绘制界面来展示最新的数据状态
// 实现历史播放列表相关数据变化时界面能及时更新展示的功能,保证用户看到的界面内容始终与实际数据一致。
* @param event SongHistoryEvent
// 根据事件携带的具体信息,来决定如何更新界面展示内容,例如如果是新增记录,就需要在列表中添加对应的展示项等。
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(SongHistoryEvent event) {
mAdapter.notifyDataSetChanged();
}
/**
* orderList
// 逆序排列可能是为了按照时间倒序等需求展示历史播放记录,使得最新播放的歌曲排在列表前面等展示效果,更符合用户查看习惯,
// 创建历史播放音乐列表的适配器创建线性布局管理器并设置给RecyclerView最后将适配器设置给RecyclerView用于展示历史播放记录列表
// 完成列表展示的相关配置工作使得RecyclerView能够正确展示历史播放歌曲的详细信息方便用户查看和操作。
*/
private void showSongList() {
// 调用orderList方法对从数据库获取的所有历史播放歌曲数据通过LitePal框架查询HistorySong类对应的表数据进行逆序排列整理
// LitePal是一个方便的Android数据库操作框架简化了数据库查询等操作这里用于获取存储在数据库中的历史播放歌曲记录信息。
mHistoryList = orderList(LitePal.findAll(HistorySong.class));
// 创建历史播放音乐列表的适配器传入当前Activity上下文和整理后的历史播放歌曲列表数据用于将数据绑定到视图上展示
// 适配器负责将数据与RecyclerView的视图项进行绑定设置每个视图项的展示样式并处理相关交互逻辑如点击事件
mAdapter = new HistoryAdapter(getActivity(), mHistoryList);
// 创建线性布局管理器设置为垂直方向排列默认情况用于管理RecyclerView中历史播放音乐列表的布局展示
// 确定列表项是垂直排列的,方便用户上下滑动查看不同的历史播放歌曲记录,符合常见的列表展示习惯。
mManager = new LinearLayoutManager(getActivity());
// 将线性布局管理器设置给RecyclerView确定其布局方式使得音乐列表能按照设定的方式展示
// 确保RecyclerView的布局效果符合预期方便用户查看和操作列表中的歌曲信息。
mRecycler.setLayoutManager(mManager);
// 将创建好的适配器设置给RecyclerView使其能够展示历史播放记录列表
// 这样RecyclerView就能依据适配器提供的数据和展示逻辑将历史播放歌曲信息展示给用户了。
mRecycler.setAdapter(mAdapter);
}
/**
*
* 使
*
*
* Fragment便
*/
private void onClick() {
// 为历史播放音乐列表的适配器设置列表项点击事件监听,点击时进行以下操作,
// 这里定义了用户点击历史播放歌曲列表项时的具体响应逻辑,实现了播放历史歌曲的核心功能。
mAdapter.setOnItemClickListener(position -> {
// 获取点击位置对应的历史播放歌曲数据对象,以便后续提取该歌曲的各项详细信息进行相关操作。
HistorySong history = mHistoryList.get(position);
// 创建一个Song对象用于设置当前点击歌曲的相关信息方便后续保存和播放操作
// 这个Song对象可能是应用中统一用于表示歌曲信息的实体类整合了歌曲各方面的关键属性。
Song song = new Song();
// 设置歌曲的唯一标识,通常在数据库或者整个应用体系中有唯一性,用于准确区分不同歌曲。
song.setSongId(history.getSongId());
// 设置歌曲的名称,用于在界面展示以及后续可能的查找、排序等操作中使用,方便用户识别歌曲。
song.setSongName(history.getName());
// 设置歌曲的演唱者信息,同样有助于用户明确歌曲来源以及在界面展示等场景中提供更详细的歌曲描述。
song.setSinger(history.getSinger());
// 设置歌曲是否为在线歌曲的标识,根据历史播放记录中对应歌曲的该属性进行赋值,
// 后续可能根据这个标识来决定播放时的获取方式(如在线播放还是本地播放等)。
song.setOnline(history.isOnline());
// 设置歌曲的播放地址,这是实际播放歌曲时需要的关键信息,播放服务会依据这个地址来获取音频资源进行播放。
song.setUrl(history.getUrl());
// 设置歌曲的封面图片地址(如果有的话),可用于在界面展示歌曲时显示对应的封面图片,提升视觉效果和用户体验。
song.setImgUrl(history.getPic());
// 设置歌曲在列表中的位置索引,方便记录当前点击歌曲在列表中的顺序等相关操作,比如定位播放位置等。
song.setPosition(position);
// 设置歌曲的时长信息,用于在界面展示歌曲时长或者进行一些与时长相关的操作(如播放进度计算等)。
song.setDuration(history.getDuration());
// 设置歌曲的媒体资源唯一标识(可能用于更细致地区分不同版本或者来源的媒体资源等情况)。
song.setMediaId(history.getMediaId());
// 设置歌曲所属的列表类型为历史播放类型,用于标识歌曲来源或者在应用中进行分类管理等操作,
// 例如在不同的播放逻辑或者统计场景下可以依据这个类型进行区分处理。
song.setListType(Constant.LIST_TYPE_HISTORY);
// 通过FileUtil工具类将歌曲相关信息保存到本地具体保存的方式和位置由FileUtil的实现决定
// 可能是保存到本地文件或者数据库中,方便后续操作(如记录播放历史、恢复播放状态等),
// 确保应用能持久化用户的操作记录以及播放相关信息,提升用户体验的连贯性。
FileUtil.saveSong(song);
// 通过获取到的与播放服务交互的Binder对象mPlayStatusBinder调用播放服务中的播放方法
// 传入Constant.LIST_TYPE_HISTORY表示播放的是历史播放类型的歌曲从而实现点击历史播放列表中的歌曲后进行播放的功能
// 这一步是触发实际播放操作的关键,通过与播放服务的交互,让用户能够再次收听之前播放过的歌曲。
mPlayStatusBinder.play(Constant.LIST_TYPE_HISTORY);
});
// 为返回上一界面的ImageView设置点击事件监听点击时通过Fragment管理器弹出当前Fragment所在的栈返回上一界面
// 遵循常见的界面导航逻辑,方便用户在界面间进行切换操作,使得用户可以按照自己的浏览路径方便地回退,提升应用的易用性。
mBackIv.setOnClickListener(v -> getActivity().getSupportFragmentManager().popBackStack());
}
private List<HistorySong> orderList(List<HistorySong> tempList){
List<HistorySong> historyList=new ArrayList<>();
/**
*
*
* 使
* @param tempList
*
* @return
* 便
*/
private List<HistorySong> orderList(List<HistorySong> tempList) {
List<HistorySong> historyList = new ArrayList<>();
historyList.clear();
for(int i=tempList.size()-1;i>=0;i--){
for (int i = tempList.size() - 1; i >= 0; i--) {
historyList.add(tempList.get(i));
}
return historyList;
}
}
}

@ -36,41 +36,77 @@ import java.util.Objects;
import butterknife.BindView;
/**
* Fragment
* MVPILocalContract.View
* BaseMvpFragmentPresenter
*
*/
public class LocalFragment extends BaseMvpFragment<LocalPresenter> implements ILocalContract.View {
private static final String TAG = "LocalFragment";
// 通过ButterKnife框架绑定的RecyclerView视图组件用于展示本地音乐列表支持列表项的展示和交互操作
// 例如通过设置适配器展示每首本地音乐的相关信息,并响应列表项的点击等事件。
@BindView(R.id.normalView)
RecyclerView mRecycler;
// 通过ButterKnife框架绑定的ImageView视图组件可能用于触发查找本地音乐等相关操作从代码逻辑推测
// 比如点击后可以重新扫描本地存储设备以获取最新的本地音乐文件信息等。
@BindView(R.id.iv_find_local_song)
ImageView mFindLocalMusicIv;
// 通过ButterKnife框架绑定的ImageView视图组件通常用于点击返回上一界面等操作提供返回功能
// 方便用户在该界面导航回之前浏览的页面,遵循常见的界面交互逻辑。
@BindView(R.id.iv_back)
ImageView mBackIv;
// 通过ButterKnife框架绑定的RelativeLayout视图组件可能用于在本地音乐列表为空时展示提示信息等相关布局展示
// 例如显示一个友好的提示文本告知用户当前没有本地音乐可供播放等情况。
@BindView(R.id.linear_empty)
RelativeLayout mEmptyViewLinear;
// 存储本地音乐数据LocalSong类型的列表用于展示以及后续的操作如点击播放等
// 该列表会从数据库或者本地文件扫描等方式获取本地音乐信息并填充,然后传递给适配器用于展示在界面上。
private List<LocalSong> mLocalSongsList;
// 本地音乐的Presenter对象用于处理业务逻辑如获取本地音乐数据、与数据层交互等遵循MVP模式
// 它负责处理和协调视图层本Fragment与数据层之间的通信和操作使得界面展示与数据获取等逻辑分离。
private LocalPresenter mPresenter;
// 本地音乐列表的适配器负责将本地音乐数据绑定到RecyclerView的每个Item视图上进行展示
// 并处理相关点击等交互逻辑,例如设置每首音乐在列表中的显示样式,以及响应点击某首音乐时的播放等操作。
private SongAdapter songAdapter;
// 用于管理RecyclerView布局的线性布局管理器设置列表的布局方式如垂直排列等
// 决定了本地音乐列表在RecyclerView中的排列顺序和滚动方式等展示效果。
private LinearLayoutManager layoutManager;
//在onServiceConnected中获取PlayStatusBinder的实例从而调用服务里面的方法
// 用于与播放服务进行交互的Binder对象通过它可以调用播放服务中的方法如控制本地音乐播放、暂停、切换等操作
// 在onServiceConnected方法中获取其实例以便后续在用户操作音乐播放相关功能时使用。
private PlayerService.PlayStatusBinder mPlayStatusBinder;
// 定义与播放服务的连接对象用于监听服务的连接与断开状态并获取服务提供的Binder对象进行交互
// 是实现Fragment与播放服务通信的关键部分确保播放相关操作能正确执行。
private ServiceConnection connection = new ServiceConnection() {
/**
* IBinderPlayStatusBinder
* 便
* @param name
* @param service IBinder
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayStatusBinder = (PlayerService.PlayStatusBinder) service;
}
/**
*
* Toast
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* FragmentEventBus
*
*/
@Override
public void onDestroy() {
super.onDestroy();
@ -78,47 +114,92 @@ public class LocalFragment extends BaseMvpFragment<LocalPresenter> implements IL
EventBus.getDefault().unregister(this);
}
/**
* FragmentPresenterLocalPresenter
* MVPPresenterPresenter
* 使FragmentPresenter
* @return PresenterLocalPresenter
*/
@Override
protected LocalPresenter getPresenter() {
mPresenter = new LocalPresenter();
return mPresenter;
}
/**
* initView
* EventBusRecyclerView
* Fragment
*/
@Override
public void initView() {
super.initView();
// 注册当前Fragment到EventBus用于接收事件例如本地音乐相关的更新事件等用于更新界面等操作
// 这样当有相关事件发布时该Fragment可以及时响应并更新界面显示内容。
EventBus.getDefault().register(this);
// 调用方法注册和绑定播放服务,用于后续与播放服务的交互操作,确保能正确控制音乐播放。
registerAndBindReceive();
// 调用方法初始化本地音乐RecyclerView的相关设置如设置布局管理器、加载本地音乐数据、设置适配器等操作
// 使得RecyclerView能正确展示本地音乐列表信息。
initLocalRecycler();
}
/**
* EventBusSongLocalEvent
* RecyclerView
* FileUtil.getSongRecyclerView
*
* @param event SongLocalEvent
*
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(SongLocalEvent event){
public void onMessageEvent(SongLocalEvent event) {
songAdapter.notifyDataSetChanged();
if (FileUtil.getSong() != null) {
if (FileUtil.getSong()!= null) {
layoutManager.scrollToPositionWithOffset(FileUtil.getSong().getPosition() + 4, mRecycler.getHeight());
}
}
/**
* FragmentActivity
* 使
* @param savedInstanceState
*/
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
onClick();
}
/**
* Fragment
*
*
*/
@Override
protected void loadData() {
}
/**
* FragmentIDfragment_localID
* Fragment
* @return IDR.layout.fragment_local
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_local;
}
/**
*
* RecyclerView
*
*
* @param mp3InfoList
*
*/
@Override
public void showMusicList(final List<LocalSong> mp3InfoList) {
mLocalSongsList.clear();
@ -126,11 +207,11 @@ public class LocalFragment extends BaseMvpFragment<LocalPresenter> implements IL
mRecycler.setVisibility(View.VISIBLE);
mEmptyViewLinear.setVisibility(View.GONE);
mRecycler.setLayoutManager(layoutManager);
//令recyclerView定位到当前播放的位置
// 令recyclerView定位到当前播放的位置,方便用户在播放音乐时能快速定位到正在播放的歌曲所在位置。
songAdapter = new SongAdapter(mActivity, mLocalSongsList);
mRecycler.setAdapter(songAdapter);
songAdapter.setOnItemClickListener(position -> {
//将点击的序列化到本地
// 将点击的序列化到本地,可能是保存当前点击歌曲的相关信息,方便后续记录用户操作等用途。
LocalSong mp3Info = mLocalSongsList.get(position);
Song song = new Song();
song.setSongName(mp3Info.getName());
@ -148,41 +229,76 @@ public class LocalFragment extends BaseMvpFragment<LocalPresenter> implements IL
}
/**
*
* ToastRecyclerView
*
*/
@Override
public void showErrorView() {
showToast("本地音乐为空");
mRecycler.setVisibility(View.GONE);
mEmptyViewLinear.setVisibility(View.VISIBLE);
}
/**
*
* IntentPlayerService
* ServiceConnection
*
* Fragment
*/
private void registerAndBindReceive() {
//启动服务
// 创建启动播放服务的Intent对象指定要启动的服务类为PlayerService
// Intent是Android中用于启动组件如Activity、Service等的消息传递对象。
Intent playIntent = new Intent(getActivity(), PlayerService.class);
// 绑定播放服务通过传入的ServiceConnection对象监听服务连接状态
// 并设置绑定模式为自动创建服务(如果服务未启动则自动启动),
// 这样可以保证在需要与播放服务交互时服务已处于运行状态。
mActivity.bindService(playIntent, connection, Context.BIND_AUTO_CREATE);
}
/**
* RecyclerView线
* LitePal
* RecyclerView
* showMusicList
*
* RecyclerView
*/
private void initLocalRecycler() {
// 创建用于存储本地音乐数据的空集合,后续将从数据库或本地文件扫描获取的音乐信息填充到该集合中。
mLocalSongsList = new ArrayList<>();
// 创建线性布局管理器设置为垂直方向排列默认情况用于管理RecyclerView中本地音乐列表的布局展示
// 确定列表项是垂直排列的,方便用户上下滑动查看不同的音乐。
layoutManager = new LinearLayoutManager(getActivity());
mLocalSongsList.clear();
// 使用LitePal框架查询获取数据库中所有的本地音乐数据LocalSong类对应的表数据添加到本地音乐列表集合中
// LitePal是一个方便的Android数据库操作框架简化了数据库查询等操作。
mLocalSongsList.addAll(LitePal.findAll(LocalSong.class));
// 如果本地音乐列表集合中没有数据(即没有本地音乐文件),进行相应的界面显示设置。
if (mLocalSongsList.size() == 0) {
// 将RecyclerView设置为不可见因为没有音乐可展示避免显示一个空白的列表给用户造成困惑。
mRecycler.setVisibility(View.GONE);
// 将空视图布局设置为可见,用于提示用户当前无本地音乐,例如可以在该布局中显示一段友好的提示文字。
mEmptyViewLinear.setVisibility(View.VISIBLE);
} else {
// 如果有本地音乐数据,则将空视图布局设置为不可见,准备展示音乐列表。
mEmptyViewLinear.setVisibility(View.GONE);
// 将RecyclerView设置为可见准备展示本地音乐列表让用户可以看到并操作音乐列表。
mRecycler.setVisibility(View.VISIBLE);
// 将线性布局管理器设置给RecyclerView确定其布局方式使得音乐列表能按照设定的方式展示。
mRecycler.setLayoutManager(layoutManager);
//令recyclerView定位到当前播放的位置
// 令recyclerView定位到当前播放的位置,方便用户快速找到正在播放的歌曲。
songAdapter = new SongAdapter(mActivity, mLocalSongsList);
mRecycler.setAdapter(songAdapter);
if (FileUtil.getSong() != null) {
if (FileUtil.getSong()!= null) {
// 滚动RecyclerView到指定位置这里是根据正在播放歌曲的位置进行滚动
// 并设置一定的偏移量FileUtil.getSong().getPosition() - 4目的是让正在播放的歌曲在RecyclerView中处于合适的展示位置
// mRecycler.getHeight()可能用于根据RecyclerView的高度来进一步调整滚动的偏移量以达到更好的视觉效果使得正在播放的歌曲能更突出显示。
layoutManager.scrollToPositionWithOffset(FileUtil.getSong().getPosition() - 4, mRecycler.getHeight());
}
songAdapter.setOnItemClickListener(position -> {
//将点击的序列化到本地
// 将点击的序列化到本地,可能是保存当前点击歌曲的相关信息,方便后续记录用户操作等用途。
LocalSong mp3Info = mLocalSongsList.get(position);
Song song = new Song();
song.setSongName(mp3Info.getName());
@ -193,18 +309,31 @@ public class LocalFragment extends BaseMvpFragment<LocalPresenter> implements IL
song.setOnline(false);
song.setSongId(mp3Info.getSongId());
song.setListType(Constant.LIST_TYPE_LOCAL);
// 通过FileUtil工具类将歌曲相关信息保存到本地具体保存的方式和位置由FileUtil的实现决定
// 可能是保存到本地文件或者数据库中,方便下次启动应用时能恢复之前的播放状态等操作。
FileUtil.saveSong(song);
// 通过获取到的与播放服务交互的Binder对象mPlayStatusBinder调用播放服务中的播放方法
// 传入Constant.LIST_TYPE_LOCAL表示播放的是本地音乐类型从而实现点击列表中的本地歌曲后进行播放的功能。
mPlayStatusBinder.play(Constant.LIST_TYPE_LOCAL);
});
}
}
//按钮事件
/**
*
* ImageViewPresenter
* ImageViewFragmentFragment
* 便
*/
// 按钮事件
private void onClick() {
mFindLocalMusicIv.setOnClickListener(v -> mPresenter.getLocalMp3Info()); //得到本地列表
mBackIv.setOnClickListener(v -> Objects.requireNonNull(getActivity()).getSupportFragmentManager().popBackStack()); //返回
// 为查找本地音乐的ImageView设置点击事件监听点击时调用Presenter的方法获取本地音乐列表信息
// 比如可能触发重新扫描本地存储设备查找新添加的音乐文件等操作,以更新本地音乐列表展示。
mFindLocalMusicIv.setOnClickListener(v -> mPresenter.getLocalMp3Info());
// 为返回的ImageView设置点击事件监听点击时通过Fragment管理器弹出栈返回上一界面
// FragmentManager负责管理Fragment的添加、替换、移除以及返回栈等操作popBackStack方法会将当前Fragment从栈顶弹出显示上一个Fragment的界面。
mBackIv.setOnClickListener(v -> Objects.requireNonNull(getActivity()).getSupportFragmentManager().popBackStack());
}
}
}

@ -52,49 +52,94 @@ import static com.example.musicplayer.app.Constant.SINGER_NAME_KEY;
/**
* Created by on 2018/11/25.
* Fragment
*
*/
public class AlbumContentFragment extends Fragment {
private static final String TAG = "AlbumContentFragment";
private String mAlbumName, mSingerName, mAlbumPic, mPublicTime, mId;
// 专辑名称
private String mAlbumName,
// 歌手名称
mSingerName,
// 专辑封面图片的路径或资源标识等
mAlbumPic,
// 专辑发行时间
mPublicTime,
// 专辑的唯一标识例如ID
mId;
// 用于实现页面滑动切换效果的ViewPager来自MaterialViewPager库
private MaterialViewPager mViewPager;
// 顶部的Toolbar用于显示标题、操作按钮等
private Toolbar toolbar;
// 专辑背景布局,用于设置专辑封面等作为背景展示效果
private RelativeLayout mAlbumBackground;
// 用于显示歌手名称的TextView
private TextView mSingerNameTv;
// 用于显示发行时间的TextView
private TextView mPublicTimeTv;
// 用于显示专辑封面图片的ImageView
private ImageView mAlbumPicIv;
// 表示收藏按钮的菜单项
private MenuItem mLoveBtn;
// 用于记录专辑是否已被收藏的状态
private boolean mLove;
/**
* FragmentFragment
* @param savedInstanceState
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);//加上这句话menu才会显示出来
}
/**
* Fragment
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
getBundle();
// 加载fragment_album_content布局文件并创建视图对象
View view = inflater.inflate(R.layout.fragment_album_content, container, false);
// 从布局中找到对应的MaterialViewPager组件
mViewPager = view.findViewById(R.id.materialViewPager);
// 获取MaterialViewPager中的Toolbar组件
toolbar = mViewPager.getToolbar();
// 从MaterialViewPager的头部背景容器中找到对应的RelativeLayout组件用于设置专辑背景
mAlbumBackground = mViewPager.getHeaderBackgroundContainer().findViewById(R.id.relative_album);
// 从MaterialViewPager的头部背景容器中找到对应的ImageView组件用于显示专辑封面图片
mAlbumPicIv = mViewPager.getHeaderBackgroundContainer().findViewById(R.id.iv_album);
// 从MaterialViewPager的头部背景容器中找到对应的TextView组件用于显示歌手名称
mSingerNameTv = mViewPager.getHeaderBackgroundContainer().findViewById(R.id.tv_singer_name);
// 从MaterialViewPager的头部背景容器中找到对应的TextView组件用于显示发行时间
mPublicTimeTv = mViewPager.getHeaderBackgroundContainer().findViewById(R.id.tv_public_time);
return view;
}
/**
* FragmentActivity
* @param savedInstanceState
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
initView();
}
/**
* inflate
*
* @param menu
* @param inflater
*/
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.love, menu);
@ -103,43 +148,69 @@ public class AlbumContentFragment extends Fragment {
super.onCreateOptionsMenu(menu, inflater);
}
/**
* Toolbar
* ViewPager
*/
@SuppressLint("SetTextI18n")
private void initView() {
// 设置Toolbar的标题为专辑名称
toolbar.setTitle(mAlbumName);
// 创建一个SimpleTarget对象用于接收Glide加载图片后的结果在图片加载完成后进行相关处理
SimpleTarget target = new SimpleTarget<Drawable>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
@Override
public void onResourceReady(@Nullable Drawable resource, Transition<? super Drawable> transition) {
// 将加载后的Drawable资源转换为Bitmap以便后续设置背景等操作
Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
// 使用工具方法将Bitmap转换为可设置为背景的Drawable并设置为专辑背景
mAlbumBackground.setBackground(CommonUtil.getForegroundDrawable(bitmap));
// 将Bitmap设置为ImageView的显示图片用于显示专辑封面
mAlbumPicIv.setImageBitmap(bitmap);
}
};
// 使用Glide库加载专辑封面图片设置占位图、加载出错时的图片并将加载结果传递给SimpleTarget对象处理
Glide.with(getActivity())
.load(mAlbumPic)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(target);
.load(mAlbumPic)
.apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome))
.into(target);
// 设置歌手名称的TextView显示内容添加前缀 "歌手 "
mSingerNameTv.setText("歌手 " + mSingerName);
// 设置发行时间的TextView显示内容添加前缀 "发行时间 "
mPublicTimeTv.setText("发行时间 " + mPublicTime);
// 设置Toolbar标题文字颜色为白色
toolbar.setTitleTextColor(getActivity().getResources().getColor(R.color.white));
if (toolbar != null) {
if (toolbar!= null) {
// 将Toolbar设置为Activity的支持ActionBar用于显示标题、导航按钮等功能
((AppCompatActivity) getActivity()).setSupportActionBar(toolbar);
final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
if (actionBar != null) {
if (actionBar!= null) {
// 设置ActionBar显示返回按钮用于导航返回上一个页面
actionBar.setDisplayHomeAsUpEnabled(true);
// 设置ActionBar显示返回按钮确保图标显示等
actionBar.setDisplayShowHomeEnabled(true);
// 设置ActionBar显示标题已设置过标题内容
actionBar.setDisplayShowTitleEnabled(true);
// 不使用logo作为显示元素这里可能是按照需求禁用
actionBar.setDisplayUseLogoEnabled(false);
// 设置Home按钮可用点击返回按钮等操作生效
actionBar.setHomeButtonEnabled(true);
}
}
//返回键的监听
// 为Toolbar的返回按钮设置点击监听事件点击时弹出Fragment栈返回上一个Fragment页面
toolbar.setNavigationOnClickListener(v -> getActivity().getSupportFragmentManager().popBackStack());
// 为MaterialViewPager的ViewPager设置页面切换的适配器用于展示不同的Fragment页面如歌曲列表、专辑信息页面
mViewPager.getViewPager().setAdapter(new FragmentStatePagerAdapter(getActivity().getSupportFragmentManager()) {
/**
* FragmentViewPager
* @param position 01
* @return Fragment
*/
@Override
public Fragment getItem(int position) {
switch (position) {
@ -152,11 +223,20 @@ public class AlbumContentFragment extends Fragment {
}
}
/**
* ViewPager2
* @return
*/
@Override
public int getCount() {
return 2;
}
/**
* ViewPager
* @param position 01
* @return
*/
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
@ -169,43 +249,60 @@ public class AlbumContentFragment extends Fragment {
}
});
// 将ViewPager与标题栏关联起来使标题栏能根据ViewPager的页面切换显示对应的标题和指示器效果
mViewPager.getPagerTitleStrip().setViewPager(mViewPager.getViewPager());
// 设置标题栏指示器的颜色(例如切换页面时的滑动条颜色)
mViewPager.getPagerTitleStrip().setIndicatorColorResource(R.color.yellow);
// 设置标题栏每个标题标签的背景颜色
mViewPager.getPagerTitleStrip().setTabBackground(R.color.tab);
// 设置标题栏文字的颜色状态列表(例如不同状态下的文字颜色,按下、正常等状态)
mViewPager.getPagerTitleStrip().setTextColorStateListResource(R.color.white);
}
/**
*
*
* @param item
* @return true
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.btn_love:
if (mLove) {
// 如果专辑已收藏,执行取消收藏操作,从数据库中删除对应的收藏记录
LitePal.deleteAllAsync(AlbumCollection.class, "albumId=?", mId).listen(new UpdateOrDeleteCallback() {
@Override
public void onFinish(int rowsAffected) {
// 更新收藏按钮图标为未收藏状态图标
mLoveBtn.setIcon(R.drawable.favorites);
// 显示提示Toast消息告知用户已取消收藏
CommonUtil.showToast(getActivity(), "你已取消收藏该专辑");
}
});
} else {
// 如果专辑未收藏创建一个AlbumCollection对象设置专辑相关信息准备保存到数据库作为收藏记录
AlbumCollection albumCollection = new AlbumCollection();
albumCollection.setAlbumId(mId);
albumCollection.setAlbumName(mAlbumName);
albumCollection.setAlbumPic(mAlbumPic);
albumCollection.setPublicTime(mPublicTime);
albumCollection.setSingerName(mSingerName);
// 异步保存收藏记录到数据库
albumCollection.saveAsync().listen(new SaveCallback() {
@Override
public void onFinish(boolean success) {
// 更新收藏按钮图标为已收藏状态图标
mLoveBtn.setIcon(R.drawable.favorites_selected);
// 显示提示Toast消息告知用户收藏专辑成功
CommonUtil.showToast(getActivity(), "收藏专辑成功");
}
});
}
mLove = !mLove;
//发送收藏改变的事件
mLove =!mLove;
// 发送专辑收藏状态改变的事件通过EventBus通知其他可能关注该事件的地方进行相应处理
EventBus.getDefault().post(new AlbumCollectionEvent());
break;
}
@ -213,8 +310,11 @@ public class AlbumContentFragment extends Fragment {
}
/**
*
*/
private void showLove() {
if (LitePal.where("albumId=?", mId).find(AlbumCollection.class).size() != 0) {
if (LitePal.where("albumId=?", mId).find(AlbumCollection.class).size()!= 0) {
mLove = true;
mLoveBtn.setIcon(R.drawable.favorites_selected);
} else {
@ -224,6 +324,16 @@ public class AlbumContentFragment extends Fragment {
}
/**
* AlbumContentFragment
* BundleFragment
* @param id ID
* @param albumName
* @param albumPic
* @param singerName
* @param publicTime
* @return AlbumContentFragment
*/
public static Fragment newInstance(String id, String albumName, String albumPic, String
singerName, String publicTime) {
AlbumContentFragment albumContentFragment = new AlbumContentFragment();
@ -237,9 +347,12 @@ public class AlbumContentFragment extends Fragment {
return albumContentFragment;
}
/**
* Bundle
*/
private void getBundle() {
Bundle bundle = getArguments();
if (bundle != null) {
if (bundle!= null) {
mId = bundle.getString(ALBUM_ID_KEY);
mAlbumName = bundle.getString(ALBUM_NAME_KEY);
mAlbumPic = bundle.getString(ALBUM_PIC_KEY);
@ -249,67 +362,132 @@ public class AlbumContentFragment extends Fragment {
}
/**
* Fragment
* Fragment使
* Created by on 2018/11/20.
*/
public static class SearchFragment extends Fragment {
private static final String TAG = "SearchFragment";
// 用于输入搜索内容的编辑文本框
private EditText mSeekEdit;
// 用于触发搜索操作的按钮(这里是一个具有点击涟漪效果的视图)
private RippleView mSeekTv;
// 用于返回上一个页面的按钮(这里是一个具有点击涟漪效果的视图)
private RippleView mBackIv;
/**
* SearchFragment
*
/**
* Fragment
* Fragment使
* Created by on 2018/11/20.
*/
public static class SearchFragment extends Fragment {
private static final String TAG = "SearchFragment";
// 用于输入搜索内容的编辑文本框
private EditText mSeekEdit;
// 用于触发搜索操作的按钮(这里是一个具有点击涟漪效果的视图)
private RippleView mSeekTv;
// 用于返回上一个页面的按钮(这里是一个具有点击涟漪效果的视图)
private RippleView mBackIv;
/**
* SearchFragment
* Fragment
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 加载fragment_search布局文件并创建视图对象
View view = inflater.inflate(R.layout.fragment_search, container, false);
// 从布局中找到对应的EditText组件用于输入搜索内容
mSeekEdit = view.findViewById(R.id.edit_seek);
// 从布局中找到对应的RippleView组件作为搜索按钮
mSeekTv = view.findViewById(R.id.tv_search);
// 从布局中找到对应的RippleView组件作为返回按钮
mBackIv = view.findViewById(R.id.iv_back);
// 初始时显示搜索历史的Fragment页面
replaceFragment(new SearchHistoryFragment());
return view;
}
/**
* FragmentActivity
*
* @param saveInstanceState
*/
@SuppressLint("ClickableViewAccessibility")
@Override
public void onActivityCreated(Bundle saveInstanceState) {
super.onActivityCreated(saveInstanceState);
// 弹出软键盘,聚焦到搜索编辑文本框上,方便用户直接输入搜索内容
CommonUtil.showKeyboard(mSeekEdit, getActivity());
// 为搜索按钮设置点击监听事件
mSeekTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 关闭软键盘,避免遮挡界面等情况
CommonUtil.closeKeybord(mSeekEdit, getActivity());
mSeekEdit.setCursorVisible(false);//隐藏光标
if(mSeekEdit.getText().toString().trim().length()==0){
// 隐藏编辑文本框的光标,可能是搜索后改变显示样式需求
mSeekEdit.setCursorVisible(false);
// 如果用户输入的内容为空(仅包含空格等也视为空),则将编辑文本框的内容设置为提示文本内容
if (mSeekEdit.getText().toString().trim().length() == 0) {
mSeekEdit.setText(mSeekEdit.getHint().toString().trim());
}
// 将此次搜索内容保存到数据库中,作为搜索历史记录
saveDatabase(mSeekEdit.getText().toString());
// 根据输入的搜索内容切换显示对应的搜索结果Fragment页面
replaceFragment(ContentFragment.newInstance(mSeekEdit.getText().toString()));
}
});
// 为编辑文本框设置触摸事件监听,当用户触摸文本框时,显示光标,方便用户输入编辑
mSeekEdit.setOnTouchListener((v, event) -> {
if (MotionEvent.ACTION_DOWN == event.getAction()) {
mSeekEdit.setCursorVisible(true);
}
return false;
});
// 为返回按钮设置点击监听事件点击时关闭软键盘并弹出Fragment栈返回上一个Fragment页面
mBackIv.setOnClickListener(v -> {
CommonUtil.closeKeybord(mSeekEdit,getActivity());
CommonUtil.closeKeybord(mSeekEdit, getActivity());
getActivity().getSupportFragmentManager().popBackStack();
});
}
/**
*
*
* @param seekHistory
*/
private void saveDatabase(String seekHistory) {
// 从数据库中查询是否已存在相同的搜索历史记录
List<SearchHistory> searchHistoryList = LitePal.where("history=?", seekHistory).find(SearchHistory.class);
// 如果查询到有一条相同的搜索历史记录
if (searchHistoryList.size() == 1) {
// 删除这条已存在的旧搜索历史记录
LitePal.delete(SearchHistory.class, searchHistoryList.get(0).getId());
}
// 创建一个新的SearchHistory对象用于保存此次搜索历史记录
SearchHistory searchHistory = new SearchHistory();
searchHistory.setHistory(seekHistory);
// 将新的搜索历史记录保存到数据库中
searchHistory.save();
}
/**
* Fragment
*
* @param seek
*/
public void setSeekEdit(String seek) {
mSeekEdit.setText(seek);
mSeekEdit.setCursorVisible(false);//隐藏光标
@ -319,6 +497,12 @@ public class AlbumContentFragment extends Fragment {
replaceFragment(ContentFragment.newInstance(mSeekEdit.getText().toString()));
}
/**
* FragmentFragmentTransactionFragment
* FragmentR.id.container
* Fragment
* @param fragment Fragment
*/
//搜索后的页面
private void replaceFragment(Fragment fragment) {
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
@ -327,3 +511,4 @@ public class AlbumContentFragment extends Fragment {
}
}
}

@ -47,71 +47,130 @@ import static com.example.musicplayer.app.Constant.PUBLIC_TIME_KEY;
/**
* Created by on 2018/11/25.
* Fragment
*
* BaseMvpFragmentIAlbumSongContract.View
*/
public class AlbumSongFragment extends BaseMvpFragment<AlbumSongPresenter> implements IAlbumSongContract.View{
public class AlbumSongFragment extends BaseMvpFragment<AlbumSongPresenter> implements IAlbumSongContract.View {
// 用于区分Fragment展示类型的键值用于在传递参数和获取参数时作为标识
private static final String TYPE_KEY = "type_key";
// 表示展示专辑歌曲列表的类型常量用于判断当前Fragment应展示歌曲列表相关界面
public static final int ALBUM_SONG = 0;
// 表示展示专辑信息的类型常量用于判断当前Fragment应展示专辑详细信息相关界面
public static final int ALBUM_INFORMATION = 1;
// 专辑歌曲的Presenter对象用于处理业务逻辑如获取专辑相关数据等遵循MVP模式
private AlbumSongPresenter mPresenter;
// 专辑的唯一标识例如ID用于后续获取对应专辑的详细信息等操作
private String mId;
// 用于展示专辑信息页面中可滚动的视图容器,方便显示较多内容时滚动查看
private NestedScrollView mScrollView;
private TextView mNameTv, mLanguageTv,mDescTv,mCompany,mPublicTimeTv,mTypeTv;
// 用于显示专辑名称的TextView
private TextView mNameTv,
// 用于显示语言相关信息的TextView
mLanguageTv,
// 用于显示专辑描述信息的TextView
mDescTv,
// 用于显示发行公司信息的TextView
mCompany,
// 用于显示发行时间的TextView此处与成员变量mPublicTime可能有重复赋值情况具体看使用场景
mPublicTimeTv,
// 用于显示专辑类型信息的TextView
mTypeTv;
// 当前Fragment的展示类型通过参数传入判断是展示歌曲列表还是专辑信息
private int mType;
// 专辑的发行时间,从传入参数获取,用于展示等操作
private String mPublicTime;
// 专辑的描述信息,后续从获取的数据中赋值,用于展示
private String mDesc;
//用来判断网络问题及加载问题
// 用于显示加载动画的视图组件,提示用户正在加载数据,例如网络请求加载专辑相关信息时展示
private AVLoadingIndicatorView mLoading;
// 用于显示加载相关提示文本的TextView例如在加载出现问题等情况下展示提示信息
private TextView mLoadingTv;
// 用于显示网络错误相关提示图标的ImageView当出现网络问题时展示给用户提示
private ImageView mNetworkErrorIv;
// 存储专辑歌曲列表数据的集合,从获取的数据中赋值,用于展示歌曲列表等操作
private List<AlbumSong.DataBean.ListBean> mSongsList;
// 用于展示专辑歌曲列表的RecyclerView组件通过设置适配器等展示歌曲信息列表
private RecyclerView mRecycle;
// 用于管理RecyclerView布局的线性布局管理器设置列表的布局方式如垂直排列等
private LinearLayoutManager mLinearManager;
// 专辑歌曲列表的适配器负责将歌曲数据绑定到RecyclerView的每个Item视图上进行展示并处理相关点击等交互逻辑
private AlbumSongAdapter mAdapter;
// 用于启动播放服务的Intent对象通过它可以启动与播放相关的后台服务
private Intent playIntent;
// 用于与播放服务进行交互的Binder对象通过它可以调用播放服务中的方法如控制播放等操作
private PlayerService.PlayStatusBinder mPlayStatusBinder;
// 定义与播放服务的连接对象用于监听服务的连接与断开状态并获取服务提供的Binder对象进行交互
private ServiceConnection connection = new ServiceConnection() {
/**
* IBinderPlayStatusBinder
* 便
* @param name
* @param service IBinder
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayStatusBinder = (PlayerService.PlayStatusBinder) service;
}
/**
*
*
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* FragmentPresenterthis
* PresenterID
*/
@Override
protected void loadData() {
mPresenter =new AlbumSongPresenter();
mPresenter = new AlbumSongPresenter();
mPresenter.attachView(this);
mPresenter.getAlbumDetail(mId,mType);
mPresenter.getAlbumDetail(mId, mType);
}
/**
* FragmentID0
* onCreateView
* @return ID0
*/
@Override
protected int getLayoutId() {
return 0;
}
/**
* Fragment
* EventBus
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
getBundle();
// 注册当前Fragment到EventBus用于接收事件例如歌曲相关事件可能用于更新界面等操作
EventBus.getDefault().register(this);
View view = null;
// 根据类型判断加载不同的布局文件
if (mType == ALBUM_SONG) {
view = inflater.inflate(R.layout.fragment_album_recycler, container, false);
mRecycle = view.findViewById(R.id.normalView);
// 获取数据库实例LitePal框架相关操作可能用于后续数据查询等
LitePal.getDatabase();
} else {
view = inflater.inflate(R.layout.fragment_album_song, container, false);
@ -130,90 +189,156 @@ public class AlbumSongFragment extends BaseMvpFragment<AlbumSongPresenter> imple
return view;
}
/**
* EventBusSongAlbumEvent
*
* @param event SongAlbumEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onSongAlbumEvent(SongAlbumEvent event){
public void onSongAlbumEvent(SongAlbumEvent event) {
mAdapter.notifyDataSetChanged();
}
/**
* FragmentActivity
* MaterialViewPager
* @param save
*/
@Override
public void onActivityCreated(Bundle save) {
super.onActivityCreated(save);
if(mType==ALBUM_SONG){
//启动服务
if (mType == ALBUM_SONG) {
// 创建启动播放服务的Intent对象指定要启动的服务类为PlayerService
playIntent = new Intent(getActivity(), PlayerService.class);
// 绑定播放服务通过传入的ServiceConnection对象监听服务连接状态
// 并设置绑定模式为自动创建服务(如果服务未启动则自动启动)
mActivity.bindService(playIntent, connection, Context.BIND_AUTO_CREATE);
}else{
} else {
// 将可滚动视图NestedScrollView注册到MaterialViewPager帮助类
// 可能用于实现一些与滚动效果相关的交互功能(例如标题栏随滚动变化等效果)
MaterialViewPagerHelper.registerScrollView(getActivity(), mScrollView);
}
}
/**
* FragmentEventBus
*/
@Override
public void onDestroyView(){
public void onDestroyView() {
// 注销当前Fragment在EventBus的注册避免内存泄漏等问题以及不再接收事件
EventBus.getDefault().unregister(this);
if(playIntent!=null){
if (playIntent!= null) {
// 解除与播放服务的绑定,释放相关资源,避免内存泄漏等问题
Objects.requireNonNull(getActivity()).unbindService(connection);
}
super.onDestroyView();
}
private void getBundle(){
/**
* BundleFragmentID
*
*/
private void getBundle() {
Bundle bundle = getArguments();
if (bundle != null) {
if (bundle!= null) {
mType = bundle.getInt(TYPE_KEY);
mId =bundle.getString(ALBUM_ID_KEY);
mId = bundle.getString(ALBUM_ID_KEY);
mPublicTime = bundle.getString(PUBLIC_TIME_KEY);
}
}
public static Fragment newInstance(int type, String id,String publicTime) {
/**
* AlbumSongFragmentID
* BundleFragment便Fragment
* @param type ALBUM_SONGALBUM_INFORMATION
* @param id ID
* @param publicTime
* @return AlbumSongFragment
*/
public static Fragment newInstance(int type, String id, String publicTime) {
AlbumSongFragment fragment = new AlbumSongFragment();
Bundle bundle = new Bundle();
bundle.putInt(TYPE_KEY, type);
bundle.putString(ALBUM_ID_KEY,id);
bundle.putString(ALBUM_ID_KEY, id);
bundle.putString(PUBLIC_TIME_KEY, publicTime);
fragment.setArguments(bundle);
return fragment;
}
/**
* RecyclerView
* Item
* @param songList
*/
@Override
public void setAlbumSongList(final List<AlbumSong.DataBean.ListBean> songList) {
mLinearManager =new LinearLayoutManager(getActivity());
// 创建线性布局管理器设置为垂直方向排列默认情况用于管理RecyclerView中歌曲列表的布局展示
mLinearManager = new LinearLayoutManager(getActivity());
// 将线性布局管理器设置给RecyclerView确定其布局方式
mRecycle.setLayoutManager(mLinearManager);
mAdapter =new AlbumSongAdapter(songList);
// 创建专辑歌曲列表的适配器,传入歌曲列表数据,用于将数据绑定到视图上展示
mAdapter = new AlbumSongAdapter(songList);
// 为RecyclerView添加头部装饰器可能用于实现一些特殊的头部效果具体看MaterialViewPagerHeaderDecorator的实现
mRecycle.addItemDecoration(new MaterialViewPagerHeaderDecorator());
// 将适配器设置给RecyclerView使其能够展示歌曲列表数据
mRecycle.setAdapter(mAdapter);
// 为歌曲列表的适配器设置点击事件监听点击某个歌曲Item时执行以下逻辑
mAdapter.setSongClick(position -> {
AlbumSong.DataBean.ListBean dataBean= songList.get(position);
// 获取点击位置对应的歌曲数据对象
AlbumSong.DataBean.ListBean dataBean = songList.get(position);
Song song = new Song();
// 设置歌曲的唯一标识例如歌曲ID
song.setSongId(dataBean.getSongmid());
// 设置歌曲的歌手信息通过调用getSinger方法拼接多个歌手名字如果有多个歌手
song.setSinger(getSinger(dataBean));
// 设置歌曲名称
song.setSongName(dataBean.getSongname());
// 设置歌曲在列表中的位置索引
song.setPosition(position);
// 设置歌曲时长
song.setDuration(dataBean.getInterval());
// 设置歌曲为在线状态(可根据实际情况判断是否准确,这里可能只是简单标记)
song.setOnline(true);
// 设置歌曲列表类型为在线类型(同样可根据实际业务情况有不同取值和含义)
song.setListType(Constant.LIST_TYPE_ONLINE);
song.setImgUrl(Api.ALBUM_PIC+dataBean.getAlbummid()+ Api.JPG);
// 设置歌曲封面图片的URL地址通过拼接相关路径和参数生成
song.setImgUrl(Api.ALBUM_PIC + dataBean.getAlbummid() + Api.JPG);
// 此处设置歌曲播放地址为null可能后续会有根据在线情况等获取真实地址的逻辑
song.setUrl(null);
// 设置歌曲的媒体ID具体用途看业务逻辑中如何使用这个字段
song.setMediaId(dataBean.getStrMediaMid());
//判断是否已经下载
song.setDownload(LitePal.where("songId=?", dataBean.getSongmid()).find(DownloadSong.class).size() != 0);
// 判断歌曲是否已经下载通过查询LitePal数据库中是否存在对应歌曲ID的下载记录来确定
song.setDownload(LitePal.where("songId=?", dataBean.getSongmid()).find(DownloadSong.class).size()!= 0);
// 保存歌曲相关信息可能是保存到本地数据库或者其他存储方式具体看FileUtil.saveSong的实现
FileUtil.saveSong(song);
// 通过与播放服务交互的Binder对象调用播放服务中的播放方法传入播放类型参数开始播放歌曲
mPlayStatusBinder.play(Constant.LIST_TYPE_ONLINE);
});
}
/**
* Toast
*/
@Override
public void showAlbumSongError() {
CommonUtil.showToast(getActivity(),"获取专辑信息失败");
CommonUtil.showToast(getActivity(), "获取专辑信息失败");
}
/**
* TextView
* @param name
* @param language
* @param company
* @param type
* @param desc
*/
@Override
public void showAlbumMessage(String name, String language, String company, String type,String desc) {
public void showAlbumMessage(String name, String language, String company, String type, String desc) {
mNameTv.setText(name);
mLanguageTv.setText(language);
mCompany.setText(company);
@ -222,23 +347,35 @@ public class AlbumSongFragment extends BaseMvpFragment<AlbumSongPresenter> imple
mTypeTv.setText(type);
}
*
*
*/
@Override
public void showLoading() {
mLoading.show();
}
/**
* Fragment
* RecyclerViewScrollView
*
*/
@Override
public void hideLoading() {
mLoading.hide();
mLoadingTv.setVisibility(View.GONE);
if(mType==ALBUM_SONG){
if (mType == ALBUM_SONG) {
mRecycle.setVisibility(View.VISIBLE);
}else {
} else {
mScrollView.setVisibility(View.VISIBLE);
}
mNetworkErrorIv.setVisibility(View.GONE);
}
/**
*
*
*/
@Override
public void showNetError() {
mLoadingTv.setVisibility(View.GONE);
@ -246,17 +383,29 @@ public class AlbumSongFragment extends BaseMvpFragment<AlbumSongPresenter> imple
mNetworkErrorIv.setVisibility(View.VISIBLE);
}
/**
* FragmentPresenternull
* 使Presenter
* MVPPresenter
* @return Presenternull
*/
@Override
protected AlbumSongPresenter getPresenter() {
return null;
}
/**
*
* 12便
* @param dataBean
* @return
*/
//获取歌手,因为歌手可能有很多个
private String getSinger(AlbumSong.DataBean.ListBean dataBean){
private String getSinger(AlbumSong.DataBean.ListBean dataBean) {
StringBuilder singer = new StringBuilder(dataBean.getSinger().get(0).getName());
for (int i = 1; i < dataBean.getSinger().size(); i++) {
singer.append("、").append(dataBean.getSinger().get(i).getName());
}
return singer.toString();
}
}
}

@ -16,51 +16,96 @@ import java.util.List;
/**
* Created by on 2018/11/25.
* Fragment
* TabLayoutViewPager
* Fragment
*/
public class ContentFragment extends Fragment {
// 用于存储选项卡标题的列表每个元素对应一个Tab的标题文本
private List<String> mTitleList;
// 用于存储各个选项卡对应的Fragment实例的列表每个元素对应一个Tab要展示的Fragment
private List<Fragment> mFragments;
// 用于实现页面切换功能的ViewPager组件可通过滑动或者点击Tab来切换不同的页面Fragment
private ViewPager mPager;
// 自定义的TabAdapter适配器用于将Fragment和标题等数据适配到ViewPager上实现页面切换和标题显示逻辑
private TabAdapter mAdapter;
// 用于展示选项卡的TabLayout组件提供可视化的选项卡切换交互界面
private TabLayout mTabLayout;
// 定义选项卡标题的字符串数组,这里包含了两个标题,分别是"歌曲"和"专辑",对应不同的展示内容
private String[] mTitles = {"歌曲", "专辑"};
// 定义与不同选项卡对应的类型标识字符串数组,用于区分不同类型的搜索内容或者页面展示逻辑,
// 这里分别对应 "song"(歌曲相关)和 "album"(专辑相关)
private String[] mTypes = {"song", "album"};
// 用于接收从外部传入的参数Bundle可能包含了搜索内容等相关数据用于在Fragment内部传递和使用
private Bundle mBundle;
// 用于存储搜索内容的字符串从传入的Bundle中获取可能用于后续根据搜索内容展示相应的结果等操作
private String mSeek;
/**
* FragmentViewPagerTabLayout
* BundleFragment
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 加载fragment_content布局文件并创建视图对象
View view = inflater.inflate(R.layout.fragment_content, container, false);
// 获取从外部传入的参数Bundle对象
mBundle = getArguments();
if (mBundle != null) {
if (mBundle!= null) {
// 从Bundle中获取搜索内容字符串其键值对应SearchContentFragment.SEEK_KEY具体定义在其他地方
mSeek = mBundle.getString(SearchContentFragment.SEEK_KEY);
}
// 从加载的布局中找到对应的ViewPager组件
mPager = view.findViewById(R.id.page);
// 从加载的布局中找到对应的TabLayout组件
mTabLayout = view.findViewById(R.id.tab_layout);
// 创建用于存储选项卡标题的空列表
mTitleList = new ArrayList<>();
// 创建用于存储选项卡对应Fragment实例的空列表
mFragments = new ArrayList<>();
// 调用方法初始化选项卡相关设置包括添加标题、创建对应的Fragment实例以及设置适配器等操作
initTab();
return view;
}
/**
*
* SearchContentFragmentFragment
* TabAdapterViewPagerTabLayoutViewPager
*/
private void initTab() {
for (int i = 0; i < mTitles.length; i++) {
// 将标题数组中的每个标题添加到标题列表中
mTitleList.add(mTitles[i]);
// 根据类型创建对应的SearchContentFragment实例并传入搜索内容和类型标识添加到Fragment列表中
mFragments.add(SearchContentFragment.newInstance(mSeek, mTypes[i]));
}
// 创建TabAdapter适配器传入子Fragment管理器、Fragment列表和标题列表用于适配ViewPager的页面切换逻辑
mAdapter = new TabAdapter(getChildFragmentManager(), mFragments, mTitleList);
// 将创建好的适配器设置给ViewPager使其能够根据适配器中的配置展示不同的Fragment页面
mPager.setAdapter(mAdapter);
// 将TabLayout与ViewPager关联起来使得TabLayout能够根据ViewPager的页面切换自动更新选中的选项卡
// 同时点击TabLayout的选项卡也能切换ViewPager的页面
mTabLayout.setupWithViewPager(mPager);
}
/**
* ContentFragment
* BundleFragment便Fragment
* @param seek Fragment
* @return ContentFragment
*/
public static Fragment newInstance(String seek) {
ContentFragment fragment = new ContentFragment();
Bundle bundle = new Bundle();
@ -68,4 +113,4 @@ public class ContentFragment extends Fragment {
fragment.setArguments(bundle);
return fragment;
}
}
}

@ -45,48 +45,81 @@ import butterknife.BindView;
/**
* Created by on 2018/11/21.
* Fragment
*
* MVPISearchContentContract.View
* BaseLoadingFragment
*/
public class SearchContentFragment extends BaseLoadingFragment<SearchContentPresenter> implements ISearchContentContract.View {
private static final String TAG = "SearchContentFragment";
// 用于区分搜索类型的键值,在传递和获取参数时作为标识,表明是歌曲还是专辑等类型的搜索
public static final String TYPE_KEY = "type";
// 用于传递搜索内容的键值,在传递和获取参数时作为标识,存储用户输入的搜索关键字等信息
public static final String SEEK_KEY = "seek";
// 用于标识是否在线的键值(从代码中看可能未完整体现其使用场景,也许用于区分在线资源相关逻辑)
public static final String IS_ONLINE = "online";
// 用于记录当前搜索结果的页码偏移量初始化为1用于实现翻页搜索功能每次加载更多数据时递增
private int mOffset = 1; //用于翻页搜索
// 搜索内容的Presenter对象用于处理业务逻辑如发起搜索请求、获取更多数据等遵循MVP模式
private SearchContentPresenter mPresenter;
// 用于管理RecyclerView布局的线性布局管理器设置列表的布局方式如垂直排列等
private LinearLayoutManager manager;
// 搜索内容的适配器负责将搜索结果数据歌曲或专辑列表数据绑定到RecyclerView的每个Item视图上进行展示并处理相关点击等交互逻辑
private SearchContentAdapter mAdapter;
// 存储搜索到的歌曲列表数据的集合初始化为空的ArrayList后续从获取的数据中添加元素并展示
private ArrayList<SearchSong.DataBean.SongBean.ListBean> mSongList = new ArrayList<>();
// 存储搜索到的专辑列表数据的集合初始化为null后续根据搜索结果赋值并用于展示相关专辑信息
private List<Album.DataBean.AlbumBean.ListBean> mAlbumList;
// 用于支持RecyclerView下拉刷新和上拉加载更多功能的适配器对原始的mAdapter进行包装添加相关功能支持
private LRecyclerViewAdapter mLRecyclerViewAdapter;//下拉刷新
// 通过ButterKnife框架绑定的RecyclerView视图组件用于展示搜索结果列表支持下拉刷新和上拉加载更多功能
@BindView(R.id.normalView)
LRecyclerView mRecycler;
// 通过ButterKnife框架绑定的ImageView视图组件可能用于显示背景图片等相关用途从代码中未明确体现具体使用场景
@BindView(R.id.iv_background)
ImageView mBackgroundIv;
// 用于接收从外部传入的参数Bundle可能包含了搜索类型、搜索内容等相关数据用于在Fragment内部传递和使用
private Bundle mBundle;
// 用于存储搜索内容的字符串从传入的Bundle中获取代表用户输入的搜索关键字等信息
private String mSeek;
// 用于存储搜索类型的字符串从传入的Bundle中获取表明是歌曲还是专辑等类型的搜索
private String mType;
// 用于与播放服务进行交互的Binder对象通过它可以调用播放服务中的方法如控制播放等操作
private PlayerService.PlayStatusBinder mPlayStatusBinder;
// 定义与播放服务的连接对象用于监听服务的连接与断开状态并获取服务提供的Binder对象进行交互
private ServiceConnection connection = new ServiceConnection() {
/**
* IBinderPlayStatusBinder
* 便
* @param name
* @param service IBinder
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mPlayStatusBinder = (PlayerService.PlayStatusBinder) service;
}
/**
*
*
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
/**
* FragmentPresenter
* searchMore
*/
@Override
protected void loadData() {
if (mType.equals("song")) {
@ -97,6 +130,11 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
searchMore();
}
/**
* reload
* Presenter
* 使
*/
@Override
public void reload() {
super.reload();
@ -107,43 +145,75 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
}
}
/**
* FragmentIDfragment_search_contentID
* Fragment
* @return IDR.layout.fragment_search_content
*/
@Override
protected int getLayoutId() {
return R.layout.fragment_search_content;
}
/**
* initView
* EventBusBundle
* 线
*/
@Override
protected void initView() {
super.initView();
// 注册当前Fragment到EventBus用于接收事件例如歌曲在线状态变化、歌曲版权相关事件等用于更新界面等操作
EventBus.getDefault().register(this);
mBundle = getArguments();
if (mBundle != null) {
if (mBundle!= null) {
mSeek = mBundle.getString(SEEK_KEY);
mType = mBundle.getString(TYPE_KEY);
}
manager = new LinearLayoutManager(mActivity);
//启动服务
// 创建启动播放服务的Intent对象指定要启动的服务类为PlayerService
Intent playIntent = new Intent(getActivity(), PlayerService.class);
// 绑定播放服务通过传入的ServiceConnection对象监听服务连接状态
// 并设置绑定模式为自动创建服务(如果服务未启动则自动启动)
mActivity.bindService(playIntent, connection, Context.BIND_AUTO_CREATE);
}
/**
* FragmentPresenterSearchContentPresenter
* MVPPresenterPresenter
* @return PresenterSearchContentPresenter
*/
@Override
protected SearchContentPresenter getPresenter() {
mPresenter = new SearchContentPresenter();
return mPresenter ;
return mPresenter;
}
/**
* EventBusOnlineSongChangeEvent
*
* 线
* @param event OnlineSongChangeEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onOnlineSongChangeEvent(OnlineSongChangeEvent event){
if(mAdapter!= null) mAdapter.notifyDataSetChanged();
public void onOnlineSongChangeEvent(OnlineSongChangeEvent event) {
if (mAdapter!= null) mAdapter.notifyDataSetChanged();
}
/**
* EventBusOnlineSongErrorEvent
* Toast
* @param event OnlineSongErrorEvent
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void onOnlineSongErrorEvent(OnlineSongErrorEvent event){
public void onOnlineSongErrorEvent(OnlineSongErrorEvent event) {
showToast("抱歉该歌曲暂没有版权,搜搜其他歌曲吧");
}
/**
* FragmentEventBus
*
*/
@Override
public void onDestroy() {
super.onDestroy();
@ -151,7 +221,12 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
EventBus.getDefault().unregister(this);
}
/**
*
* RecyclerView
* Item
* @param songListBeans
*/
@Override
public void setSongsList(final ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans) {
mSongList.addAll(songListBeans);
@ -160,23 +235,28 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
mRecycler.setLayoutManager(manager);
mRecycler.setAdapter(mLRecyclerViewAdapter);
//点击播放
// 为歌曲列表的适配器设置点击事件监听点击某个歌曲Item时执行以下逻辑
SearchContentAdapter.setItemClick(position -> {
SearchSong.DataBean.SongBean.ListBean dataBean = mSongList.get(position);
Song song = new Song();
song.setSongId(dataBean.getSongmid());
song.setSinger(getSinger(dataBean));
song.setSongName(dataBean.getSongname());
song.setImgUrl(Api.ALBUM_PIC+dataBean.getAlbummid()+Api.JPG);
song.setImgUrl(Api.ALBUM_PIC + dataBean.getAlbummid() + Api.JPG);
song.setDuration(dataBean.getInterval());
song.setOnline(true);
song.setMediaId(dataBean.getStrMediaMid());
song.setDownload(DownloadUtil.isExistOfDownloadSong(dataBean.getSongmid()));
//网络获取歌曲地址
// 调用Presenter的方法获取歌曲的播放地址用于后续播放操作
mPresenter.getSongUrl(song);
});
}
/**
*
* RecyclerView
* @param songListBeans
*/
@Override
public void searchMoreSuccess(ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans) {
mSongList.addAll(songListBeans);
@ -184,11 +264,20 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
mRecycler.refreshComplete(Constant.OFFSET);
}
/**
* RecyclerView
*
*/
@Override
public void searchMoreError() {
mRecycler.setNoMore(true);
}
/**
* RecyclerView
* RecyclerViewPresenter
*
*/
@Override
public void searchMore() {
@ -202,13 +291,17 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
mPresenter.searchAlbumMore(mSeek, mOffset);
}
});
//设置底部加载颜色
// 设置底部加载提示的背景颜色、进度条颜色和文字颜色(具体颜色资源通过对应的R.color资源引用获取
mRecycler.setFooterViewColor(R.color.colorAccent, R.color.musicStyle_low, R.color.transparent);
//设置底部加载文字提示
// 设置底部加载文字提示信息,分别对应正在加载、加载完成、加载出错时的提示文本内容
mRecycler.setFooterViewHint("拼命加载中", "已经全部为你呈现了", "网络不给力啊,点击再试一次吧");
}
/**
* RecyclerView
* Presenter
*/
@Override
public void showSearcherMoreNetworkError() {
mRecycler.setOnNetWorkErrorListener(() -> {
@ -217,17 +310,12 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
});
}
@Override
public void searchAlbumSuccess(final List<Album.DataBean.AlbumBean.ListBean> albumList) {
mAlbumList = new ArrayList<>();
mAlbumList.addAll(albumList);
mAdapter = new SearchContentAdapter(mAlbumList, mSeek, getActivity(), Constant.TYPE_ALBUM);
mLRecyclerViewAdapter = new LRecyclerViewAdapter(mAdapter);
mRecycler.setLayoutManager(manager);
mRecycler.setAdapter(mLRecyclerViewAdapter);
SearchContentAdapter.setAlbumClick(position -> toAlbumContentFragment(mAlbumList.get(position)));
}
/**
*
* RecyclerView
* ItemFragmenttoAlbumContentFragment
* @param albumList
*/
@Override
public void searchAlbumMoreSuccess(List<Album.DataBean.AlbumBean.ListBean> songListBeans) {
mAlbumList.addAll(songListBeans);
@ -235,23 +323,34 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
mRecycler.refreshComplete(Constant.OFFSET);
}
/**
* Toast
*/
@Override
public void searchAlbumError() {
CommonUtil.showToast(getActivity(), "获取专辑信息失败");
}
/**
*
* FileUtil.saveSong
* Binder线
* @param song ID
* @param url
*/
@Override
public void getSongUrlSuccess(Song song,String url) {
public void getSongUrlSuccess(Song song, String url) {
song.setUrl(url);
FileUtil.saveSong(song);
mPlayStatusBinder.playOnline();
}
/**
* fragment
*
* @param type
* fragmentSearchContentFragment
* BundleFragment便Fragment
* @param seek
* @param type
* @return SearchContentFragment
*/
public static Fragment newInstance(String seek, String type) {
SearchContentFragment fragment = new SearchContentFragment();
@ -262,25 +361,38 @@ public class SearchContentFragment extends BaseLoadingFragment<SearchContentPres
return fragment;
}
/**
* FragmentActivityFragmentFragment
* FragmentFragmentR.id.fragment_container
* Fragment便Fragment
* @param album ID
* Fragment
*/
public void toAlbumContentFragment(Album.DataBean.AlbumBean.ListBean album) {
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
// 设置Fragment进入和退出的动画效果分别对应进入、退出、从右侧进入、从右侧退出的动画资源ID
transaction.setCustomAnimations(R.anim.fragment_in, R.anim.fragment_out, R.anim.slide_in_right, R.anim.slide_out_right);
transaction.add(R.id.fragment_container, AlbumContentFragment.
newInstance(album.getAlbumMID(), album.getAlbumName(), album.getAlbumPic(), album.getSingerName(), album.getPublicTime()));
transaction.hide(this);
//将事务提交到返回栈
// 将事务添加到返回栈按下返回键时可以按照添加顺序反向执行事务实现返回上一个Fragment的效果
transaction.addToBackStack(null);
transaction.commit();
}
/**
*
* 12便
* @param dataBean
* @return
*/
//获取歌手,因为歌手可能有很多个
private String getSinger( SearchSong.DataBean.SongBean.ListBean dataBean){
private String getSinger(SearchSong.DataBean.SongBean.ListBean dataBean) {
StringBuilder singer = new StringBuilder(dataBean.getSinger().get(0).getName());
for (int i = 1; i < dataBean.getSinger().size(); i++) {
singer.append("、").append(dataBean.getSinger().get(i).getName());
}
return singer.toString();
}
}
}

@ -11,7 +11,9 @@ import android.view.ViewGroup;
import com.example.SpeedDialog.dialog.SpeedDialog;
import com.example.musicplayer.R;
import com.example.musicplayer.adapter.SearchHistoryAdapter;
import com.example.musicplayer.callback.*;
import com.example.musicplayer.callback.OnDeleteClickListener;
import com.example.musicplayer.callback.OnFooterClickListener;
import com.example.musicplayer.callback.OnItemClickListener;
import com.example.musicplayer.entiy.SearchHistory;
import org.litepal.LitePal;
@ -21,84 +23,154 @@ import java.util.List;
/**
* Created by on 2018/11/29.
* Fragment
* RecyclerView
*/
public class SearchHistoryFragment extends Fragment {
// 用于展示搜索历史记录的RecyclerView组件通过设置适配器等展示历史记录列表
private RecyclerView mRecycler;
// 搜索历史记录的适配器负责将搜索历史数据绑定到RecyclerView的每个Item视图上进行展示并处理相关点击等交互逻辑
private SearchHistoryAdapter mAdapter;
// 用于管理RecyclerView布局的线性布局管理器设置列表的布局方式如垂直排列等
private LinearLayoutManager mLayoutManager;
// 存储从数据库获取的所有搜索历史记录的集合,用于展示以及后续的操作(如删除等)
private List<SearchHistory> mSearchHistoryList;
// 临时存储搜索历史记录的集合,在一些操作过程中(如数据更新、筛选等)辅助使用,避免直接操作原始数据集合造成问题
private List<SearchHistory> mTempList;
/**
* FragmentRecyclerView
* @param inflater
* @param container
* @param savedInstanceState
* @return
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 加载fragment_search_history布局文件并创建视图对象
View view = inflater.inflate(R.layout.fragment_search_history, container, false);
// 从加载的布局中找到对应的RecyclerView组件
mRecycler = view.findViewById(R.id.recycler_seek_history);
return view;
}
/**
* FragmentActivity
* @param savedInstanceState
*/
@Override
public void onActivityCreated(Bundle savedInstanceState){
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 调用方法展示搜索历史记录,包括从数据库获取数据、设置适配器、布局管理器等操作
showHistory();
// 调用方法设置各种点击事件的监听逻辑,如清空历史、删除单条历史记录、点击历史记录重新进行搜索等操作
onClick();
}
private void showHistory(){
/**
* changeList
* 线RecyclerViewRecyclerView
*/
private void showHistory() {
// 创建用于存储搜索历史记录的空集合
mSearchHistoryList = new ArrayList<>();
// 创建用于临时存储搜索历史记录的空集合
mTempList = new ArrayList<>();
// 调用方法更新数据列表,从数据库获取数据并整理到相应集合中
changeList();
// 创建搜索历史记录适配器,传入搜索历史记录集合,用于将数据绑定到视图上展示
mAdapter = new SearchHistoryAdapter(mSearchHistoryList);
// 创建线性布局管理器设置为垂直方向排列默认情况用于管理RecyclerView中搜索历史记录列表的布局展示
mLayoutManager = new LinearLayoutManager(getActivity());
// 将线性布局管理器设置给RecyclerView确定其布局方式
mRecycler.setLayoutManager(mLayoutManager);
// 将适配器设置给RecyclerView使其能够展示搜索历史记录列表
mRecycler.setAdapter(mAdapter);
}
private void onClick(){
/**
*
*/
private void onClick() {
// 为搜索历史记录适配器设置底部(例如可能是一个清空历史的按钮所在位置)点击事件监听
mAdapter.setFooterClickListener(new OnFooterClickListener() {
@Override
public void onClick() {
// 创建一个SpeedDialog对话框实例用于提示用户确认操作这里是清空所有搜索历史记录的确认对话框
SpeedDialog deleteDialog = new SpeedDialog(getActivity());
// 设置对话框的标题为"删除"
deleteDialog.setTitle("删除")
.setMessage("确定清空所有搜索历史吗?")
.setSureClickListener(dialog -> {
//删除数据库中的历史记录
// 设置对话框的提示信息,询问用户是否确定清空所有搜索历史记录
.setMessage("确定清空所有搜索历史吗?")
// 设置对话框确认按钮的点击事件监听,点击确认时执行以下逻辑
.setSureClickListener(dialog -> {
// 使用LitePal框架删除数据库中所有的搜索历史记录SearchHistory表中的所有数据
LitePal.deleteAll(SearchHistory.class);
// 将RecyclerView设置为不可见因为历史记录已清空无需展示列表了
mRecycler.setVisibility(View.GONE);
}).show();
})
// 显示对话框,呈现给用户进行操作确认
.show();
}
});
// 为搜索历史记录适配器设置删除单条历史记录的点击事件监听
mAdapter.setOnDeleteClickListener(new OnDeleteClickListener() {
@Override
public void onClick(int position) {
// 获取点击位置对应的搜索历史记录对象
SearchHistory searchHistory = mSearchHistoryList.get(position);
if(searchHistory.isSaved()){
// 如果该搜索历史记录已保存这里可能根据业务逻辑有保存状态的判断比如是否已同步等情况具体看SearchHistory类的实现
if (searchHistory.isSaved()) {
// 调用该搜索历史记录对象的删除方法可能是LitePal框架提供的操作或者自定义的数据库删除逻辑删除这条记录
searchHistory.delete();
}
mTempList =LitePal.findAll(SearchHistory.class);
// 使用LitePal框架查询获取数据库中所有的搜索历史记录更新临时集合
mTempList = LitePal.findAll(SearchHistory.class);
// 调用方法更新数据列表,重新整理数据到展示集合中,并更新界面显示
changeList();
// 通知适配器数据集已改变触发RecyclerView更新界面展示内容刷新列表显示最新的历史记录情况
mAdapter.notifyDataSetChanged();
}
});
// 为搜索历史记录适配器设置点击单条历史记录重新进行搜索的点击事件监听
mAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onClick(int position) {
((AlbumContentFragment.SearchFragment)(getParentFragment())).setSeekEdit(mSearchHistoryList.get(position).getHistory());
// 获取点击位置对应的搜索历史记录对象,获取其历史记录内容(即之前的搜索关键字)
SearchHistory searchHistory = mSearchHistoryList.get(position);
// 通过获取父Fragment这里强制转换为AlbumContentFragment.SearchFragment类型前提是确保类型正确
// 调用其setSeekEdit方法将历史记录内容设置到搜索编辑框中实现点击历史记录重新进行搜索的功能
((AlbumContentFragment.SearchFragment) (getParentFragment())).setSeekEdit(searchHistory.getHistory());
}
});
}
private void changeList(){
/**
*
* RecyclerView
*
*/
private void changeList() {
// 清空用于展示的搜索历史记录集合
mSearchHistoryList.clear();
// 使用LitePal框架查询获取数据库中所有的搜索历史记录更新临时集合
mTempList = LitePal.findAll(SearchHistory.class);
if(mTempList.size()==0){
// 如果临时集合中没有搜索历史记录(即数据库中没有历史记录了)
if (mTempList.size() == 0) {
// 将RecyclerView设置为不可见因为没有历史记录可展示了
mRecycler.setVisibility(View.INVISIBLE);
}else{
} else {
// 如果有历史记录则将RecyclerView设置为可见准备展示历史记录列表
mRecycler.setVisibility(View.VISIBLE);
}
for(int i=mTempList.size()-1;i>=0;i--){
// 循环遍历临时集合中的搜索历史记录,从后往前(逆序)添加到展示集合中
for (int i = mTempList.size() - 1; i >= 0; i--) {
SearchHistory searchHistory = mTempList.get(i);
mSearchHistoryList.add(searchHistory);
}
}
}
}

@ -13,62 +13,100 @@ import android.widget.RelativeLayout;
import com.example.musicplayer.R;
/**
* RelativeLayout
* */
* RelativeLayout
*
*/
public class BackgroundAnimationRelativeLayout extends RelativeLayout {
// 动画持续的时间单位是毫秒这里设置为500毫秒表示动画持续半秒钟
private final int DURATION_ANIMATION = 500;
// 表示LayerDrawable中背景图层的索引值为0用于在LayerDrawable数组中定位背景图层
private final int INDEX_BACKGROUND = 0;
// 表示LayerDrawable中前景图层的索引值为1用于在LayerDrawable数组中定位前景图层
private final int INDEX_FOREGROUND = 1;
/**
* LayerDrawable[0]: background drawable
* LayerDrawable[1]: foreground drawable
* LayerDrawableDrawable
*/
private LayerDrawable layerDrawable;
// 用于控制动画的ObjectAnimator对象用来实现属性动画效果
private ObjectAnimator objectAnimator;
// 构造函数调用两个参数的构造函数并传入默认的属性集合null
public BackgroundAnimationRelativeLayout(Context context) {
this(context, null);
}
// 构造函数调用三个参数的构造函数并传入默认的样式属性0
public BackgroundAnimationRelativeLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public BackgroundAnimationRelativeLayout(Context context, AttributeSet attrs, int
defStyleAttr) {
/**
*
* LayerDrawableObjectAnimator
*
* @param context
* @param attrs
* @param defStyleAttr
*/
public BackgroundAnimationRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initLayerDrawable();
initObjectAnimator();
}
/**
* LayerDrawable
* DrawableLayerDrawable
*
*/
private void initLayerDrawable() {
// 获取名为ic_blackground的Drawable资源作为背景Drawable这里应该是用于设置初始的背景样式。
Drawable backgroundDrawable = getContext().getDrawable(R.drawable.ic_blackground);
// 创建一个长度为2的Drawable数组用于存放背景和前景的Drawable对象。
Drawable[] drawables = new Drawable[2];
/*初始化时先将前景与背景颜色设为一致*/
// 将获取到的背景Drawable设置为背景图层的Drawable
drawables[INDEX_BACKGROUND] = backgroundDrawable;
// 同样将背景Drawable设置为前景图层的Drawable此时前景和背景显示效果一样后续通过动画来改变前景显示效果。
drawables[INDEX_FOREGROUND] = backgroundDrawable;
// 使用包含背景和前景Drawable的数组创建LayerDrawable对象用于后续对图层的操作。
layerDrawable = new LayerDrawable(drawables);
}
/**
* ObjectAnimator
* ObjectAnimator
*
*/
private void initObjectAnimator() {
// 创建一个ObjectAnimator对象用于对当前控件this的名为"number"的属性(这里可能是自定义属性,实际是通过监听器来控制透明度)进行动画操作,
// 动画的值从0f变化到1.0f,通过这个变化来控制前景图的透明度变化。
objectAnimator = ObjectAnimator.ofFloat(this, "number", 0f, 1.0f);
// 设置动画的持续时间为之前定义的DURATION_ANIMATION500毫秒
objectAnimator.setDuration(DURATION_ANIMATION);
// 设置动画的插值器为加速插值器,使得动画在开始阶段加速进行,产生一种先慢后快的视觉效果。
objectAnimator.setInterpolator(new AccelerateInterpolator());
// 添加动画更新监听器,在动画每一帧更新时执行相应操作。
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 根据动画当前的值范围是0f到1.0f计算出前景图的透明度值范围是0到255用于设置前景图的透明度。
int foregroundAlpha = (int) ((float) animation.getAnimatedValue() * 255);
/*动态设置Drawable的透明度让前景图逐渐显示*/
// 设置前景图层的Drawable的透明度使得前景图随着动画的进行逐渐显示出来。
layerDrawable.getDrawable(INDEX_FOREGROUND).setAlpha(foregroundAlpha);
// 将设置好透明度的LayerDrawable设置为控件的背景从而实现前景图在背景上逐渐显示的效果。
BackgroundAnimationRelativeLayout.this.setBackground(layerDrawable);
}
});
// 添加动画结束监听器,在动画结束时执行相应操作。
objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
@ -77,6 +115,7 @@ public class BackgroundAnimationRelativeLayout extends RelativeLayout {
@Override
public void onAnimationEnd(Animator animation) {
/*动画结束后,记得将原来的背景图及时更新*/
// 动画结束后将前景图层的Drawable设置为背景图层的Drawable实现一种效果切换或者更新背景的操作。
layerDrawable.setDrawable(INDEX_BACKGROUND, layerDrawable.getDrawable(
INDEX_FOREGROUND));
}
@ -92,15 +131,20 @@ public class BackgroundAnimationRelativeLayout extends RelativeLayout {
}
});
}
/**
* DrawableAPI23TargetApi
* LayerDrawableDrawable
*
* @param drawable Drawable
*/
@TargetApi(23)
public void setForeground(Drawable drawable) {
layerDrawable.setDrawable(INDEX_FOREGROUND, drawable);
}
//对外提供方法,用于开始渐变动画
// 对外提供方法,用于开始渐变动画调用ObjectAnimator的start方法来启动之前设置好的动画。
public void beginAnimation() {
objectAnimator.start();
}
}
}

@ -24,30 +24,49 @@ import com.example.musicplayer.util.DisplayUtil;
/**
* Created by on 2018/10/27.
* RelativeLayout
*/
public class DiscView extends RelativeLayout {
// 用于显示唱针的ImageView代表界面上的唱针元素
private ImageView mIvNeedle;
// 用于控制唱针旋转动画的ObjectAnimator对象通过它来操作唱针的旋转动画效果
private ObjectAnimator mNeedleAnimator;
// 用于控制唱片旋转动画的ObjectAnimator对象用于实现唱片的旋转动画
private ObjectAnimator mObjectAnimator;
/*标记ViewPager是否处于偏移的状态*/
/*
ViewPager
ViewPager
*/
private boolean mViewPagerIsOffset = false;
/*标记唱针复位后,是否需要重新偏移到唱片处*/
/*
*/
private boolean mIsNeed2StartPlayAnimator = false;
// 表示音乐当前的状态初始化为MusicStatus.STOP有播放PLAY、暂停PAUSE、停止STOP三种可能状态。
private MusicStatus musicStatus = MusicStatus.STOP;
// 定义唱针动画的持续时间单位为毫秒这里设置为500毫秒表示唱针旋转动画完成一次的时间长度。
public static final int DURATION_NEEDLE_ANIAMTOR = 500;
// 唱针当前所处的状态初始化为NeedleAnimatorStatus.IN_FAR_END即初始状态下唱针处于远离唱片的位置。
private NeedleAnimatorStatus needleAnimatorStatus = NeedleAnimatorStatus.IN_FAR_END;
private int mScreenWidth, mScreenHeight;
/*唱针当前所处的状态*/
// 存储屏幕的宽度通过CommonUtil工具类获取用于后续根据屏幕尺寸来设置各种视图元素的大小和位置等。
private int mScreenWidth;
// 存储屏幕的高度同样通过CommonUtil工具类获取作用与屏幕宽度类似辅助布局相关的计算。
private int mScreenHeight;
/*
TO_FAR_END
TO_NEAR_END
IN_FAR_END
IN_NEAR_END
便
*/
private enum NeedleAnimatorStatus {
/*移动时:从唱盘往远处移动*/
TO_FAR_END,
@ -59,27 +78,44 @@ public class DiscView extends RelativeLayout {
IN_NEAR_END
}
/*音乐当前的状态:只有播放、暂停、停止三种*/
/*
PLAY
PAUSE
STOP
便
*/
public enum MusicStatus {
PLAY, PAUSE, STOP
}
// 构造函数调用带有两个参数的构造函数并传入null作为AttributeSet参数这是一种常见的构造函数重载调用方式。
public DiscView(Context context) {
this(context, null);
}
// 构造函数调用带有三个参数的构造函数并传入0作为defStyleAttr参数用于初始化视图同时传递上下文和属性集信息。
public DiscView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
*
*
* @param context
* @param attrs XMLnull
* @param defStyleAttr 0使
*/
public DiscView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mScreenWidth = CommonUtil.getScreenWidth(context);
mScreenHeight = CommonUtil.getScreenHeight(context);
}
/**
* XML
*
*/
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@ -89,64 +125,111 @@ public class DiscView extends RelativeLayout {
initObjectAnimator();
}
/**
*
* 1. ImageView
* 2. ObjectAnimator
* 3. DrawableDrawable
* 4.
*/
private void initDiscImg() {
// 通过findViewById方法在当前布局中查找id为iv_disc_background的ImageView它代表唱片的背景元素。
ImageView mDiscBackground = findViewById(R.id.iv_disc_background);
mObjectAnimator=getDiscObjectAnimator(mDiscBackground);
// 获取用于控制唱片旋转动画的ObjectAnimator对象传入唱片背景的ImageView作为参数以便后续操作该视图的旋转动画。
mObjectAnimator = getDiscObjectAnimator(mDiscBackground);
// 设置唱片背景的Drawable通过调用getDiscDrawable方法获取合成后的Drawable包含专辑图片和圆盘图片
mDiscBackground.setImageDrawable(getDiscDrawable(
BitmapFactory.decodeResource(getResources(),R.drawable.default_disc)
BitmapFactory.decodeResource(getResources(), R.drawable.default_disc)
));
// 根据屏幕高度以及定义好的比例DisplayUtil.SCALE_DISC_MARGIN_TOP计算唱片的上边距用于调整唱片在布局中的垂直位置。
int marginTop = (int) (DisplayUtil.SCALE_DISC_MARGIN_TOP * mScreenHeight);
LayoutParams layoutParams = (LayoutParams) mDiscBackground
.getLayoutParams();
.getLayoutParams();
// 设置唱片背景ImageView的外边距将上边距设置为计算得到的值左右和下边距设置为0从而定位唱片在布局中的位置。
layoutParams.setMargins(0, marginTop, 0, 0);
mDiscBackground.setLayoutParams(layoutParams);
}
/**
*
* 1. findViewByIdImageView
* 2. SCALE_*使
* 3.
* 4. Bitmap使
* 5.
* 6. BitmapImageView
*/
private void initNeedle() {
mIvNeedle = findViewById(R.id.iv_needle);
// 通过findViewById方法在当前布局中查找id为iv_needle的ImageView它代表唱针元素。
mIvNeedle = findViewById(R.id.iv_needle);
// 根据屏幕宽度以及定义好的比例DisplayUtil.SCALE_NEEDLE_WIDTH计算唱针的宽度使其在不同屏幕上显示合适大小。
int needleWidth = (int) (DisplayUtil.SCALE_NEEDLE_WIDTH * mScreenWidth);
// 根据屏幕高度以及定义好的比例DisplayUtil.SCALE_NEEDLE_HEIGHT计算唱针的高度同样用于适配屏幕尺寸。
int needleHeight = (int) (DisplayUtil.SCALE_NEEDLE_HEIGHT * mScreenHeight);
/*设置手柄的外边距为负数,让其隐藏一部分*/
/*
DisplayUtil.SCALE_NEEDLE_MARGIN_TOP
*/
int marginTop = (int) (DisplayUtil.SCALE_NEEDLE_MARGIN_TOP * mScreenHeight) * -1;
// 根据屏幕宽度以及定义好的比例DisplayUtil.SCALE_NEEDLE_MARGIN_LEFT计算唱针的左边距确定其水平位置。
int marginLeft = (int) (DisplayUtil.SCALE_NEEDLE_MARGIN_LEFT * mScreenWidth);
// 从资源文件R.drawable.ic_needle中获取唱针的原始Bitmap对象用于后续的处理和显示。
Bitmap originBitmap = BitmapFactory.decodeResource(getResources(), R.drawable
.ic_needle);
.ic_needle);
// 根据计算得到的宽度和高度对原始唱针Bitmap进行缩放处理创建一个新的缩放后的Bitmap用于设置给唱针的ImageView。
Bitmap bitmap = Bitmap.createScaledBitmap(originBitmap, needleWidth, needleHeight, false);
LayoutParams layoutParams = (LayoutParams) mIvNeedle.getLayoutParams();
// 设置唱针ImageView的外边距将左边距和上边距设置为计算得到的值右边和下边距设置为0确定唱针在布局中的位置。
layoutParams.setMargins(marginLeft, marginTop, 0, 0);
// 根据屏幕宽度以及定义好的比例DisplayUtil.SCALE_NEEDLE_PIVOT_X计算唱针旋转的中心点X坐标用于确定旋转中心位置。
int pivotX = (int) (DisplayUtil.SCALE_NEEDLE_PIVOT_X * mScreenWidth);
// 根据屏幕宽度以及定义好的比例DisplayUtil.SCALE_NEEDLE_PIVOT_Y计算唱针旋转的中心点Y坐标同样用于定位旋转中心。
int pivotY = (int) (DisplayUtil.SCALE_NEEDLE_PIVOT_Y * mScreenWidth);
// 设置唱针ImageView的旋转中心点的X坐标使其绕该点进行旋转动画操作。
mIvNeedle.setPivotX(pivotX);
// 设置唱针ImageView的旋转中心点的Y坐标与X坐标共同确定旋转中心位置。
mIvNeedle.setPivotY(pivotY);
// 设置唱针的初始旋转角度角度值由DisplayUtil.ROTATION_INIT_NEEDLE定义一般为远离唱片的初始角度。
mIvNeedle.setRotation(DisplayUtil.ROTATION_INIT_NEEDLE);
// 将缩放后的唱针Bitmap设置给唱针的ImageView使其显示在界面上。
mIvNeedle.setImageBitmap(bitmap);
// 更新唱针ImageView的布局参数使设置的外边距、旋转中心点等属性生效完成唱针的初始化布局设置。
mIvNeedle.setLayoutParams(layoutParams);
}
/**
*
* 1. ObjectAnimatorDisplayUtil.ROTATION_INIT_NEEDLE0
* 2. DURATION_NEEDLE_ANIAMTOR500
* 3. AccelerateInterpolator
* 4. AnimatorListener
*
*/
private void initObjectAnimator() {
// 创建一个ObjectAnimator对象用于控制唱针的旋转动画指定动画作用的视图mIvNeedle、动画属性View.ROTATION表示旋转角度以及起始和结束的角度值。
mNeedleAnimator = ObjectAnimator.ofFloat(mIvNeedle, View.ROTATION, DisplayUtil
.ROTATION_INIT_NEEDLE, 0);
.ROTATION_INIT_NEEDLE, 0);
// 设置唱针动画的持续时间这里使用之前定义好的常量DURATION_NEEDLE_ANIAMTOR500毫秒确定动画完成一次旋转的时间长度。
mNeedleAnimator.setDuration(DURATION_NEEDLE_ANIAMTOR);
// 设置唱针动画的插值器为AccelerateInterpolator使得唱针在旋转过程中呈现加速的动画效果增强视觉上的真实感。
mNeedleAnimator.setInterpolator(new AccelerateInterpolator());
mNeedleAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
/**
* NeedleAnimatorStatus
* NeedleAnimatorStatus
* */
* NeedleAnimatorStatus
* NeedleAnimatorStatus便
* IN_FAR_ENDTO_NEAR_END
* IN_NEAR_ENDTO_FAR_END
*/
if (needleAnimatorStatus == NeedleAnimatorStatus.IN_FAR_END) {
needleAnimatorStatus = NeedleAnimatorStatus.TO_NEAR_END;
} else if (needleAnimatorStatus == NeedleAnimatorStatus.IN_NEAR_END) {
@ -171,8 +254,10 @@ public class DiscView extends RelativeLayout {
if (mIsNeed2StartPlayAnimator) {
mIsNeed2StartPlayAnimator = false;
/**
* ViewPager
* */
* ViewPagermViewPagerIsOffsetfalse
* ViewPager50playAnimator
*
*/
if (!mViewPagerIsOffset) {
/*延时500ms*/
DiscView.this.postDelayed(new Runnable() {
@ -197,139 +282,7 @@ public class DiscView extends RelativeLayout {
});
}
/**
*
*
*/
public Drawable getDiscDrawable(Bitmap bitmap) {
int discSize = (int) (mScreenWidth * DisplayUtil.SCALE_DISC_SIZE);
int musicPicSize = (int) (mScreenWidth * DisplayUtil.SCALE_MUSIC_PIC_SIZE);
Bitmap bitmapDisc = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R
.drawable.ic_disc), discSize, discSize, false);
Bitmap bitmapMusicPic = Bitmap.createScaledBitmap(bitmap, musicPicSize, musicPicSize, true);
BitmapDrawable discDrawable = new BitmapDrawable(bitmapDisc);
RoundedBitmapDrawable roundMusicDrawable = RoundedBitmapDrawableFactory.create
(getResources(), bitmapMusicPic);
//抗锯齿
discDrawable.setAntiAlias(true);
roundMusicDrawable.setAntiAlias(true);
Drawable[] drawables = new Drawable[2];
drawables[0] = roundMusicDrawable;
drawables[1] = discDrawable;
LayerDrawable layerDrawable = new LayerDrawable(drawables);
int musicPicMargin = (int) ((DisplayUtil.SCALE_DISC_SIZE - DisplayUtil
.SCALE_MUSIC_PIC_SIZE) * mScreenWidth / 2);
//调整专辑图片的四周边距,让其显示在正中
layerDrawable.setLayerInset(0, musicPicMargin, musicPicMargin, musicPicMargin,
musicPicMargin);
return layerDrawable;
}
public ObjectAnimator getDiscObjectAnimator(ImageView disc) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(disc, View.ROTATION, 0, 360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.setDuration(30 * 1000);
objectAnimator.setInterpolator(new LinearInterpolator());
return objectAnimator;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
/*播放动画*/
private void playAnimator() {
/*唱针处于远端时,直接播放动画*/
if (needleAnimatorStatus == NeedleAnimatorStatus.IN_FAR_END) {
mNeedleAnimator.start();
}
/*唱针处于往远端移动时,设置标记,等动画结束后再播放动画*/
else if (needleAnimatorStatus == NeedleAnimatorStatus.TO_FAR_END) {
mIsNeed2StartPlayAnimator = true;
}
}
/*暂停动画*/
private void pauseAnimator() {
/*播放时暂停动画*/
if (needleAnimatorStatus == NeedleAnimatorStatus.IN_NEAR_END) {
pauseDiscAnimatior();
}
/*唱针往唱盘移动时暂停动画*/
else if (needleAnimatorStatus == NeedleAnimatorStatus.TO_NEAR_END) {
mNeedleAnimator.reverse();
/**
* reverseonStart
* */
needleAnimatorStatus = NeedleAnimatorStatus.TO_FAR_END;
}
}
/*播放唱盘动画*/
private void playDiscAnimator() {
if (mObjectAnimator.isPaused()) {
mObjectAnimator.resume();
} else {
mObjectAnimator.start();
}
}
/*暂停唱盘动画*/
private void pauseDiscAnimatior() {
mObjectAnimator.pause();
mNeedleAnimator.reverse();
}
public void play() {
playAnimator();
}
public void pause() {
musicStatus = MusicStatus.PAUSE;
pauseAnimator();
}
public void stop() {
musicStatus = MusicStatus.STOP;
pauseAnimator();
}
public void next() {
playAnimator();
selectMusicWithButton();
}
public void last() {
playAnimator();
selectMusicWithButton();
}
public boolean isPlaying() {
return musicStatus == MusicStatus.PLAY;
}
private void selectMusicWithButton() {
if (musicStatus == MusicStatus.PLAY) {
mIsNeed2StartPlayAnimator = true;
pauseAnimator();
} else if (musicStatus == MusicStatus.PAUSE) {
play();
}
}
}
* DrawableDrawable
* 1. DisplayUtil.SCALE_DISC_SIZEDisplayUtil.SCALE_MUSIC_PIC_SIZE
* 2. BitmapBitmap使

@ -6,16 +6,36 @@ import android.widget.ExpandableListView;
import android.widget.ListView;
/**
* Created by on 2018/9/25.
* MyListViewExpandableListView
* ExpandableListView
* 2018925
*/
public class MyListView extends ExpandableListView {
/**
* MyListView
* ContextAttributeSet
* ContextAttributeSetXML
* @param context 访
* @param attrs XML
*/
public MyListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* onMeasure
* heightMeasureSpec
* MeasureSpec.makeMeasureSpec
* widthMeasureSpec使
* heightMeasureSpec
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 将高度测量规格重新设置让高度可以根据内容尽量伸展采用了一种常见的处理技巧使用Integer.MAX_VALUE进行移位操作来构造合适的测量模式和大小值
// MeasureSpec.AT_MOST表示视图的大小最多为指定的值这里构造的新值使得列表高度可以在一定范围内自适应内容。
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Loading…
Cancel
Save