Compare commits

...

127 Commits

Author SHA1 Message Date
pqj95kzpi bded0a090f cw
1 year ago
pqj95kzpi b830285f55 Update CollectionFragment.java
1 year ago
pqj95kzpi b4ca192f5d Update DownloadFragment.java
1 year ago
pqj95kzpi 5dd6f3a135 Update DownloadMusicFragment.java
1 year ago
pqj95kzpi c6cd2f003b Update DownloadingFragment.java
1 year ago
pqj95kzpi 66305e8ff0 Update HistoryFragment.java
1 year ago
pqj95kzpi 3235e7e360 Update LocalFragment.java
1 year ago
pqj95kzpi 6f63cfa99e Update MainFragment.java
1 year ago
pqj95kzpi 6728a5f8d3 Update SearchHistoryFragment.java
1 year ago
pqj95kzpi eaca0e153b Update SearchContentFragment.java
1 year ago
pqj95kzpi 3940cf1fbb Update ContentFragment.java
1 year ago
pqj95kzpi 9b65f57563 Update AlbumSongFragment.java
1 year ago
pqj95kzpi 37c6929c4f Update AlbumContentFragment.java
1 year ago
pvcerb4ut 2121e2c868 Merge pull request 'fh' (#9) from fh into develop
1 year ago
pvcerb4ut 783261e3e0 Update AlbumSong.java
1 year ago
pvcerb4ut 36956b61f7 Update SongUrl.java
1 year ago
p8txnwle5 8ed412e27b Merge pull request 'wxj' (#8) from wxj into develop
1 year ago
p8txnwle5 49f7f6506c 将播放服务升级为前台服务
1 year ago
p8txnwle5 8bcb99178d 实现切换播放顺序的功能
1 year ago
p8txnwle5 cd61e2716a 实现同步下载歌曲功能,并实现下载模块的下载中的界面显示
1 year ago
p8txnwle5 a0d663ef44 将播放服务升级为前台服务
1 year ago
p8txnwle5 e4bc2c4f94 添加歌词显示功能
1 year ago
p8txnwle5 2c8562149d Fiddler爬取Api之本地歌词和歌手图片的获取以及保存
1 year ago
p8txnwle5 50068a47cd 优化了播放界面,界面主要仿网易云,并增加了播放歌碟旋转的动画效果
1 year ago
pvcerb4ut c8ec011df8 Update SongLrc.java
1 year ago
p8txnwle5 b131856140 完善了下载队列暂停的处理逻辑,增加了付费歌曲播放不了的吐司提示
1 year ago
p8txnwle5 6e5a6547d1 优化了播放界面,界面主要仿网易云,并增加了播放歌碟旋转的动画效果
1 year ago
p8txnwle5 be18457ef0 Fiddler爬取Api之播放和搜索功能
1 year ago
p8txnwle5 853106649f 启动优化,解决白屏问题
1 year ago
pvcerb4ut ecba5bb5f3 Update Song.java
1 year ago
pvcerb4ut 0e451267bf Update SingerImg.java
1 year ago
pvcerb4ut 2a6a999a57 Update SearchSong.java
1 year ago
pqj95kzpi 29791696f5 Update WelcomeActivity.java
1 year ago
pqj95kzpi a3636e481d Update PlayActivity.java
1 year ago
pvcerb4ut a2b66ec38c Update SearchHistory.java
1 year ago
pvcerb4ut 7d939b5649 Update OnlineSongLrc.java
1 year ago
p8txnwle5 58f7c40602 完善了下载队列暂停的处理逻辑,增加了付费歌曲播放不了的吐司提示
1 year ago
pvcerb4ut 4e9f651b23 Update OnlineSong.java
1 year ago
pqj95kzpi b04eb67c8a Update DiscView.java
1 year ago
pvcerb4ut a7d8ef2df5 Update LrcBean.java
1 year ago
pvcerb4ut d64ee036ba Update Love.java
1 year ago
pvcerb4ut 403a59cb7c 删除正在下载歌曲的关联表Song,更改保存歌曲的命名规则,已下载歌曲的显示更改为从本地扫描
1 year ago
pvcerb4ut f1176de1fe 删除正在下载歌曲的关联表Song,更改保存歌曲的命名规则,已下载歌曲的显示更改为从本地扫描
1 year ago
pvcerb4ut c63a108916 实现下载功能的暂停,取消以及断点续传功能
1 year ago
p8txnwle5 4c87dce109 修复访问播放地址API时Retrofit将%转义%25的问题
1 year ago
pvcerb4ut 99f26e1286 Fiddler爬取Api之增加本地,喜欢,历史表的一个字段qqId,修改本地和网络音乐的照片和歌词的处理逻辑
1 year ago
pvcerb4ut d5646c7996 Update HistorySong.java
1 year ago
pvcerb4ut e4878d94ea Update DownloadSong.java
1 year ago
p8txnwle5 b2c6aca43e 实现切换播放顺序的功能
1 year ago
pvcerb4ut 2ef89e3035 Update DownloadInfo.java
1 year ago
p8txnwle5 0c7e5c4b09 实现下载功能的暂停,取消以及断点续传功能
1 year ago
px4lqzo5h e083b8e12c Merge pull request 'pl' (#7) from pl into develop
1 year ago
px4lqzo5h eacc7f777e Update AlbumCollection.java
1 year ago
p8txnwle5 7a3a9fb683 Fiddler爬取Api之专辑歌曲模块
1 year ago
p8txnwle5 d9a13786c6 实现切换播放顺序的功能
1 year ago
px4lqzo5h 2be2f5ffc4 Update Album.java
1 year ago
pqj95kzpi b49c3fce38 Update MainActivity.java
1 year ago
px4lqzo5h 8526c747f1 Merge pull request 'pl' (#6) from pl into develop
1 year ago
px4lqzo5h 1bc1217bd6 Update DownloadTask.java
1 year ago
p8txnwle5 1ab31a6cef 实现切换播放顺序的功能
1 year ago
p8txnwle5 10c8712b63 实现切换播放顺序的功能
1 year ago
px4lqzo5h 80bb935f99 Update DownloadListener.java
1 year ago
px4lqzo5h 699d126fca Update ISearchContentContract.java
1 year ago
p8txnwle5 69b0ee17fd 修复访问播放地址API时Retrofit将%转义%25的问题
1 year ago
px4lqzo5h 2150764734 Update IPlayContract.java
1 year ago
p8txnwle5 3b1d0823b8 Fiddler爬取Api之网络歌曲歌词功能
1 year ago
px4lqzo5h 724c8d7c27 Update ILocalContract.java
1 year ago
pvcerb4ut 3fe326ab82 Fiddler爬取Api之专辑歌曲模块
1 year ago
px4lqzo5h a9162c44e5 Update IAlbumSongContract.java
1 year ago
px4lqzo5h e3c253dfa1 Update OnAlbumItemClickListener.java
1 year ago
px4lqzo5h 8274a4c749 Update OnItemClickListener.java
1 year ago
px4lqzo5h d76aee099e Update OnFooterClickListener.java
1 year ago
px4lqzo5h 77d357dcf7 Update OnDeleteClickListener.java
1 year ago
px4lqzo5h d87d518afd Update OnChildItemClickListener.java
1 year ago
px4lqzo5h 9c93671b4e Update OnAlbumItemClickListener.java
1 year ago
p8txnwle5 7e50966dc2 Fiddler爬取Api之网络歌曲歌词功能
1 year ago
px4lqzo5h bb5b96b485 Merge pull request 'pl' (#5) from pl into develop
1 year ago
px4lqzo5h 59bc5b2e66 Update BaseView.java
1 year ago
p8txnwle5 0ba81c6e5c 修复访问播放地址API时Retrofit将%转义%25的问题
1 year ago
px4lqzo5h 4cd81a0c5a Update IPresenter.java
1 year ago
p8txnwle5 f58039ac2b 本地音乐的导入新增下载的歌曲
1 year ago
px4lqzo5h 40f8653936 Update BasePresenter.java
1 year ago
p8txnwle5 8492f67c7e 实现切换播放顺序的功能
1 year ago
px4lqzo5h 3e0110c0d7 Update BaseObserver.java
1 year ago
p8txnwle5 1c5ed87c4d 重构之EventBus替换广播进行通信
1 year ago
px4lqzo5h e0f3d3d12a Update BaseMvpFragment.java
1 year ago
p8txnwle5 025cd958b8 重构之EventBus替换广播进行通信
1 year ago
p8txnwle5 4af3dda7b6 Update SongLocalEvent.java
1 year ago
px4lqzo5h 499c0a3d9f Update BaseLoadingFragment.java
1 year ago
p8txnwle5 282e785825 实现下载功能的暂停,取消以及断点续传功能
1 year ago
px4lqzo5h a31b670034 Update BaseLazyFragment.java
1 year ago
p8txnwle5 69fd6cd44f 重构之EventBus替换广播进行通信
1 year ago
px4lqzo5h 1d456e9524 Update BaseFragment.java
1 year ago
p8txnwle5 3bc9e4989d 初步实现已下载歌曲列表展示和播放
1 year ago
pqj95kzpi c822275fc5 Update DiscView.java
1 year ago
px4lqzo5h e0d78047b1 Update BaseMvpActivity.java
1 year ago
pqj95kzpi 1a4062732e Update MyListView.java
1 year ago
px4lqzo5h 33250edf2b Update BaseActivity.java
1 year ago
p8txnwle5 8801c03743 重构之EventBus替换广播进行通信
1 year ago
p8txnwle5 a304a3cacf Update SongAlbumEvent.java
1 year ago
pqj95kzpi 79f18cdba4 Update BackgroundAnimationRelativeLayout.java
1 year ago
p8txnwle5 29ef37b308 Update OnlineSongErrorEvent.java
1 year ago
px4lqzo5h f7cd60a538 Update Constant.java
1 year ago
p8txnwle5 fc7ca752c1 Update OnlineSongChangeEvent.java
1 year ago
px4lqzo5h 3f9d46f959 Update App.java
1 year ago
p8txnwle5 f53b2b89cf Update DownloadEvent.java
1 year ago
px4lqzo5h 360b162e12 Update Api.java
1 year ago
p8txnwle5 41ef767d0d Update AlbumCollectionEvent.java
1 year ago
px4lqzo5h 1c859d3c7d Update TabAdapter.java
1 year ago
px4lqzo5h 553b9e2a31 Update SongAdapter.java
1 year ago
px4lqzo5h 15e1422ddb Update SearchHistoryAdapter.java
1 year ago
px4lqzo5h 010e20197b Update SearchContentAdapter.java
1 year ago
px4lqzo5h 2b8deb5a5f Update LoveSongAdapter.java
1 year ago
px4lqzo5h 17c34ce361 Update Project_Default.xml
1 year ago
px4lqzo5h 83c3671cce Update litepal.xml
1 year ago
px4lqzo5h f05009d744 Update litepal.xml
1 year ago
px4lqzo5h 64d67980e4 Update Project_Default.xml
1 year ago
px4lqzo5h 4e29230c77 Update litepal.xml
1 year ago
px4lqzo5h 2ecc827d3f Merge pull request 'pl' (#4) from pl into develop
1 year ago
px4lqzo5h 1dc95f7c78 Update HistoryAdapter.java
1 year ago
px4lqzo5h 57a98e011c Merge pull request 'pl' (#2) from pl into develop
1 year ago
px4lqzo5h 8cb36b3256 Update ExpandableListViewAdapter.java
1 year ago
px4lqzo5h 5838e1dbfe Update DownloadingAdapter.java
1 year ago
px4lqzo5h f61e67b728 Update DownloadSongAdapter.java
1 year ago
px4lqzo5h 3ded1fe82f Update AlbumSongAdapter.java
1 year ago
px4lqzo5h 48515ed1d8 Update ExampleInstrumentedTest.java
1 year ago
px4lqzo5h 73c39b8b95 Update ExampleInstrumentedTest.java
1 year ago

@ -7,20 +7,28 @@ import android.support.test.runner.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/** /**
* Instrumented test, which will execute on an Android device. * ExampleInstrumentedTest AndroidJUnit4 Android
* * <p>
* useAppContext
* <p>
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a> * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
* Android访
*/ */
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest { public class ExampleInstrumentedTest {
/**
* useAppContext
*
* @throws Exception
*/
@Test @Test
public void useAppContext() throws Exception { public void useAppContext() throws Exception {
// Context of the app under test. // 获取当前测试的应用程序的上下文环境
Context appContext = InstrumentationRegistry.getTargetContext(); Context appContext = InstrumentationRegistry.getTargetContext();
// 断言测试的应用程序的包名是否为 "com.example.musicplayer"
// 如果包名不匹配,则测试失败,并抛出异常
assertEquals("com.example.musicplayer", appContext.getPackageName()); assertEquals("com.example.musicplayer", appContext.getPackageName());
} }
} }

@ -1,18 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- LitePal 配置文件 -->
<litepal> <litepal>
<!-- 数据库名称 -->
<dbname value="SongList" /> <dbname value="SongList" />
<!-- 数据库版本 -->
<version value="27" /> <version value="27" />
<!-- 实体类映射列表 -->
<list> <list>
<!-- 在线歌曲实体类 -->
<mapping class="com.example.musicplayer.entiy.OnlineSong" /> <mapping class="com.example.musicplayer.entiy.OnlineSong" />
<!-- 本地歌曲实体类 -->
<mapping class="com.example.musicplayer.entiy.LocalSong" /> <mapping class="com.example.musicplayer.entiy.LocalSong" />
<!-- 搜索历史实体类 -->
<mapping class="com.example.musicplayer.entiy.SearchHistory" /> <mapping class="com.example.musicplayer.entiy.SearchHistory" />
<!-- 喜欢的歌曲实体类 -->
<mapping class="com.example.musicplayer.entiy.Love" /> <mapping class="com.example.musicplayer.entiy.Love" />
<!-- 历史歌曲实体类 -->
<mapping class="com.example.musicplayer.entiy.HistorySong" /> <mapping class="com.example.musicplayer.entiy.HistorySong" />
<!-- 专辑收藏实体类 -->
<mapping class="com.example.musicplayer.entiy.AlbumCollection" /> <mapping class="com.example.musicplayer.entiy.AlbumCollection" />
<!-- 下载歌曲实体类 -->
<mapping class="com.example.musicplayer.entiy.DownloadSong" /> <mapping class="com.example.musicplayer.entiy.DownloadSong" />
<!-- 下载信息实体类 -->
<mapping class="com.example.musicplayer.entiy.DownloadInfo"/> <mapping class="com.example.musicplayer.entiy.DownloadInfo"/>
<!-- 歌曲实体类 -->
<mapping class="com.example.musicplayer.entiy.Song"/> <mapping class="com.example.musicplayer.entiy.Song"/>
</list> </list>
</litepal> </litepal>

@ -17,34 +17,53 @@ import com.example.musicplayer.util.FileUtil;
import java.util.List; import java.util.List;
/** /**
* Created by on 2018/11/27. * AlbumSongAdapter RecyclerView.Adapter
*/ */
public class AlbumSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class AlbumSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// 歌曲数据列表
private List<AlbumSong.DataBean.ListBean> mSongsBeanList; private List<AlbumSong.DataBean.ListBean> mSongsBeanList;
// 上一个被点击的位置
private int mLastPosition = -1; private int mLastPosition = -1;
// 点击事件的监听器
private OnItemClickListener mSongClick; private OnItemClickListener mSongClick;
// 歌曲项的视图类型
private final int songType = 1; private final int songType = 1;
// 底部视图的类型
private final int footerType = 2; private final int footerType = 2;
/**
*
* @param songsBeans
*/
public AlbumSongAdapter(List<AlbumSong.DataBean.ListBean> songsBeans) { public AlbumSongAdapter(List<AlbumSong.DataBean.ListBean> songsBeans) {
mSongsBeanList = songsBeans; mSongsBeanList = songsBeans;
} }
/**
*
* @param songClick
*/
public void setSongClick(OnItemClickListener songClick) { public void setSongClick(OnItemClickListener songClick) {
mSongClick = songClick; mSongClick = songClick;
} }
/**
* ViewHolder
* @param parent
* @param viewType
* @return ViewHolder
*/
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 根据视图类型创建不同的ViewHolder
if (viewType == songType) { if (viewType == songType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_song_search_item, parent, false); .inflate(R.layout.recycler_song_search_item, parent, false);
ViewHolder viewHolder = new ViewHolder(view); ViewHolder viewHolder = new ViewHolder(view);
return viewHolder; return viewHolder;
}else{ } else {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.footer_view_player_height, parent, false); .inflate(R.layout.footer_view_player_height, parent, false);
FooterHolder footerHolder = new FooterHolder(view); FooterHolder footerHolder = new FooterHolder(view);
@ -52,12 +71,18 @@ public class AlbumSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
} }
} }
/**
* ViewHolder
* @param viewHolder ViewHolder
* @param position
*/
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, final int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, final int position) {
// 如果是歌曲类型的ViewHolder
if (viewHolder instanceof ViewHolder) { if (viewHolder instanceof ViewHolder) {
ViewHolder holder = (ViewHolder) viewHolder; ViewHolder holder = (ViewHolder) viewHolder;
AlbumSong.DataBean.ListBean songsBean = mSongsBeanList.get(position); AlbumSong.DataBean.ListBean songsBean = mSongsBeanList.get(position);
//设置歌手,因为歌手可能有两个 // 设置歌手名称,可能包含多个歌手
StringBuilder singer = new StringBuilder(songsBean.getSinger().get(0).getName()); StringBuilder singer = new StringBuilder(songsBean.getSinger().get(0).getName());
for (int i = 1; i < songsBean.getSinger().size(); i++) { for (int i = 1; i < songsBean.getSinger().size(); i++) {
singer.append("、").append(songsBean.getSinger().get(i).getName()); singer.append("、").append(songsBean.getSinger().get(i).getName());
@ -65,7 +90,7 @@ public class AlbumSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
holder.artistTv.setText(singer.toString()); holder.artistTv.setText(singer.toString());
holder.titleTv.setText(songsBean.getSongname()); holder.titleTv.setText(songsBean.getSongname());
holder.mItemView.setBackgroundResource(R.color.translucent); holder.mItemView.setBackgroundResource(R.color.translucent);
//根据点击显示 // 根据当前播放的歌曲设置不同的显示效果
if(songsBean.getSongmid().equals(FileUtil.getSong().getSongId())){ if(songsBean.getSongmid().equals(FileUtil.getSong().getSongId())){
holder.playLine.setVisibility(View.VISIBLE); holder.playLine.setVisibility(View.VISIBLE);
holder.titleTv.setTextColor(App.getContext().getResources().getColor(R.color.yellow)); holder.titleTv.setTextColor(App.getContext().getResources().getColor(R.color.yellow));
@ -76,6 +101,7 @@ public class AlbumSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
holder.titleTv.setTextColor(App.getContext().getResources().getColor(R.color.white)); holder.titleTv.setTextColor(App.getContext().getResources().getColor(R.color.white));
holder.artistTv.setTextColor(App.getContext().getResources().getColor(R.color.white_blue)); holder.artistTv.setTextColor(App.getContext().getResources().getColor(R.color.white_blue));
} }
// 设置点击事件
holder.mItemView.setOnRippleCompleteListener(rippleView -> { holder.mItemView.setOnRippleCompleteListener(rippleView -> {
mSongClick.onClick(position); mSongClick.onClick(position);
equalPosition(position); equalPosition(position);
@ -83,18 +109,29 @@ public class AlbumSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
} }
} }
/**
*
* @return
*/
@Override @Override
public int getItemCount() { public int getItemCount() {
return mSongsBeanList.size() + 1; return mSongsBeanList.size() + 1;
} }
/**
*
* @param position
* @return
*/
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return position + 1 == getItemCount() ? footerType : songType; return position + 1 == getItemCount() ? footerType : songType;
} }
/**
* ViewHolder
*/
class ViewHolder extends RecyclerView.ViewHolder { class ViewHolder extends RecyclerView.ViewHolder {
TextView titleTv; TextView titleTv;
TextView artistTv; TextView artistTv;
RippleView mItemView; RippleView mItemView;
@ -109,13 +146,19 @@ public class AlbumSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
} }
} }
/**
* FooterHolder
*/
class FooterHolder extends RecyclerView.ViewHolder { class FooterHolder extends RecyclerView.ViewHolder {
FooterHolder(View itemView) { FooterHolder(View itemView) {
super(itemView); super(itemView);
} }
} }
//判断点击的是否为上一个点击的项目 /**
*
* @param position
*/
private void equalPosition(int position) { private void equalPosition(int position) {
if (position != mLastPosition) { if (position != mLastPosition) {
if (mLastPosition != -1) notifyItemChanged(mLastPosition); if (mLastPosition != -1) notifyItemChanged(mLastPosition);

@ -17,6 +17,7 @@ import com.example.musicplayer.util.FileUtil;
import java.util.List; import java.util.List;
/** /**
* DownloadSongAdapter RecyclerView.Adapter
* <pre> * <pre>
* author : * author :
* time : 2019/09/18 * time : 2019/09/18
@ -25,30 +26,52 @@ import java.util.List;
*/ */
public class DownloadSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class DownloadSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// 用于日志记录的标签
private static final String TAG = "LoveSongAdapter"; private static final String TAG = "LoveSongAdapter";
// 底部视图类型
private int footerViewType = 1; private int footerViewType = 1;
// 歌曲项视图类型
private int itemViewType = 0; private int itemViewType = 0;
// 已下载歌曲的数据列表
private List<DownloadSong> mDownloadSongList; private List<DownloadSong> mDownloadSongList;
// 上下文环境
private Context mContext; private Context mContext;
// 上一个被点击的位置
private int mLastPosition = -1; private int mLastPosition = -1;
// 点击事件的监听器
private OnItemClickListener onItemClickListener; private OnItemClickListener onItemClickListener;
/**
*
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) { public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener; this.onItemClickListener = onItemClickListener;
} }
/**
*
* @param context
* @param loveList
*/
public DownloadSongAdapter(Context context, List<DownloadSong> loveList) { public DownloadSongAdapter(Context context, List<DownloadSong> loveList) {
mContext = context; mContext = context;
mDownloadSongList = loveList; mDownloadSongList = loveList;
} }
/**
* ViewHolder
*/
class ViewHolder extends RecyclerView.ViewHolder { class ViewHolder extends RecyclerView.ViewHolder {
TextView songNameTv; TextView songNameTv;
TextView singerTv; TextView singerTv;
View playLine; View playLine;
RippleView item; RippleView item;
/**
*
* @param itemView
*/
public ViewHolder(View itemView) { public ViewHolder(View itemView) {
super(itemView); super(itemView);
songNameTv = itemView.findViewById(R.id.tv_title); songNameTv = itemView.findViewById(R.id.tv_title);
@ -59,21 +82,31 @@ public class DownloadSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
} }
/** /**
* holder * FooterHolder
*/ */
static class FooterHolder extends RecyclerView.ViewHolder { static class FooterHolder extends RecyclerView.ViewHolder {
TextView numTv; TextView numTv;
/**
*
* @param itemView
*/
public FooterHolder(View itemView) { public FooterHolder(View itemView) {
super(itemView); super(itemView);
numTv = itemView.findViewById(R.id.tv_song_num); numTv = itemView.findViewById(R.id.tv_song_num);
} }
} }
/**
* ViewHolder
* @param parent
* @param viewType
* @return ViewHolder
*/
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 根据视图类型创建不同的ViewHolder
if (viewType == itemViewType) { if (viewType == itemViewType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_song_search_item, parent, false); .inflate(R.layout.recycler_song_search_item, parent, false);
@ -87,15 +120,21 @@ public class DownloadSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
} }
} }
/**
* ViewHolder
* @param viewHolder ViewHolder
* @param position
*/
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
// 如果是歌曲类型的ViewHolder
if (viewHolder instanceof ViewHolder) { if (viewHolder instanceof ViewHolder) {
ViewHolder holder = (ViewHolder) viewHolder; ViewHolder holder = (ViewHolder) viewHolder;
final DownloadSong downloadSong = mDownloadSongList.get(position); final DownloadSong downloadSong = mDownloadSongList.get(position);
holder.songNameTv.setText(downloadSong.getName()); holder.songNameTv.setText(downloadSong.getName());
holder.singerTv.setText(downloadSong.getSinger()); holder.singerTv.setText(downloadSong.getSinger());
//根据点击显示 // 根据当前播放的歌曲设置不同的显示效果
if (downloadSong.getSongId().equals(FileUtil.getSong().getSongId())) { if (downloadSong.getSongId().equals(FileUtil.getSong().getSongId())) {
holder.playLine.setVisibility(View.VISIBLE); holder.playLine.setVisibility(View.VISIBLE);
mLastPosition = position; mLastPosition = position;
@ -117,12 +156,16 @@ public class DownloadSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
equalPosition(position); equalPosition(position);
}); });
} else { } else {
// 如果是底部视图的ViewHolder
FooterHolder footerHolder = (FooterHolder) viewHolder; FooterHolder footerHolder = (FooterHolder) viewHolder;
footerHolder.numTv.setText("共" + mDownloadSongList.size() + "首音乐"); footerHolder.numTv.setText("共" + mDownloadSongList.size() + "首音乐");
} }
} }
//判断点击的是否为上一个点击的项目 /**
*
* @param position
*/
public void equalPosition(int position) { public void equalPosition(int position) {
if (position != mLastPosition) { if (position != mLastPosition) {
notifyItemChanged(mLastPosition); notifyItemChanged(mLastPosition);
@ -131,11 +174,20 @@ public class DownloadSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
notifyItemChanged(position); notifyItemChanged(position);
} }
/**
*
* @return
*/
@Override @Override
public int getItemCount() { public int getItemCount() {
return mDownloadSongList.size() + 1; return mDownloadSongList.size() + 1;
} }
/**
*
* @param position
* @return
*/
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return position + 1 == getItemCount() ? footerViewType : itemViewType; return position + 1 == getItemCount() ? footerViewType : itemViewType;

@ -20,6 +20,7 @@ import com.example.musicplayer.util.MediaUtil;
import java.util.List; import java.util.List;
/** /**
* DownloadingAdapter RecyclerView.Adapter
* <pre> * <pre>
* author : * author :
* time : 2019/09/17 * time : 2019/09/17
@ -28,25 +29,40 @@ import java.util.List;
*/ */
public class DownloadingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class DownloadingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// 正在下载的歌曲信息列表
private List<DownloadInfo> downloadInfoList; private List<DownloadInfo> downloadInfoList;
// 项点击事件监听器
private OnItemClickListener onItemClickListener; private OnItemClickListener onItemClickListener;
// 删除点击事件监听器
private OnDeleteClickListener onDeleteClickListener; private OnDeleteClickListener onDeleteClickListener;
/**
*
* @param onDeleteClickListener
*/
public void setOnDeleteClickListener(OnDeleteClickListener onDeleteClickListener){ public void setOnDeleteClickListener(OnDeleteClickListener onDeleteClickListener){
this.onDeleteClickListener = onDeleteClickListener; this.onDeleteClickListener = onDeleteClickListener;
} }
/**
*
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) { public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener; this.onItemClickListener = onItemClickListener;
} }
/**
*
* @param downloadInfoList
*/
public DownloadingAdapter(List<DownloadInfo> downloadInfoList) { public DownloadingAdapter(List<DownloadInfo> downloadInfoList) {
this.downloadInfoList = downloadInfoList; this.downloadInfoList = downloadInfoList;
} }
/**
* ViewHolder
*/
class ViewHolder extends RecyclerView.ViewHolder { class ViewHolder extends RecyclerView.ViewHolder {
TextView songTv; TextView songTv;
TextView sizeTv; TextView sizeTv;
@ -54,6 +70,10 @@ public class DownloadingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
View itemView; View itemView;
ImageView cancelTv; ImageView cancelTv;
/**
*
* @param itemView
*/
public ViewHolder(@NonNull View itemView) { public ViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
this.itemView = itemView; this.itemView = itemView;
@ -64,23 +84,34 @@ public class DownloadingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
} }
} }
/**
* ViewHolder
* @param viewGroup
* @param viewType
* @return ViewHolder
*/
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()) View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.recycler_downing_item, viewGroup, false); .inflate(R.layout.recycler_downing_item, viewGroup, false);
ViewHolder viewHolder = new ViewHolder(view); ViewHolder viewHolder = new ViewHolder(view);
return viewHolder; return viewHolder;
} }
/**
* ViewHolder
* @param viewHolder ViewHolder
* @param position
*/
@SuppressLint({"SetTextI18n", "ClickableViewAccessibility"}) @SuppressLint({"SetTextI18n", "ClickableViewAccessibility"})
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {
ViewHolder holder = (ViewHolder) viewHolder; ViewHolder holder = (ViewHolder) viewHolder;
if (downloadInfoList.size() == 0) return; if (downloadInfoList.size() == 0) return;
DownloadInfo downloadInfo = downloadInfoList.get(i); DownloadInfo downloadInfo = downloadInfoList.get(position);
holder.songTv.setText(downloadInfo.getSongName()); holder.songTv.setText(downloadInfo.getSongName());
// 根据下载状态设置不同的显示内容
if(downloadInfo.getStatus() == Constant.DOWNLOAD_READY){//准备开始下载 if(downloadInfo.getStatus() == Constant.DOWNLOAD_READY){//准备开始下载
holder.sizeTv.setText("正在获取歌曲大小"); holder.sizeTv.setText("正在获取歌曲大小");
holder.seekBar.setVisibility(View.GONE); holder.seekBar.setVisibility(View.GONE);
@ -99,16 +130,18 @@ public class DownloadingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHo
} }
holder.seekBar.setOnTouchListener((view, motionEvent) -> true); //消费该事件让seekBar不能拖动和点击 holder.seekBar.setOnTouchListener((view, motionEvent) -> true); //消费该事件让seekBar不能拖动和点击
holder.seekBar.setProgress(downloadInfo.getProgress()); holder.seekBar.setProgress(downloadInfo.getProgress());
//点击事件 // 设置点击事件
holder.itemView.setOnClickListener(view -> { holder.itemView.setOnClickListener(view -> {
onItemClickListener.onClick(i); onItemClickListener.onClick(position);
}); });
//取消 // 设置取消按钮点击事件
holder.cancelTv.setOnClickListener(view -> onDeleteClickListener.onClick(i)); holder.cancelTv.setOnClickListener(view -> onDeleteClickListener.onClick(position));
} }
/**
*
* @return
*/
@Override @Override
public int getItemCount() { public int getItemCount() {
return downloadInfoList.size(); return downloadInfoList.size();

@ -17,64 +17,80 @@ import com.example.musicplayer.util.CommonUtil;
import java.util.List; import java.util.List;
/** /**
* *
* Created by on 2018/9/23. * Created by on 2018/9/23.
*/ */
public class ExpandableListViewAdapter extends BaseExpandableListAdapter { public class ExpandableListViewAdapter extends BaseExpandableListAdapter {
private static final String TAG = "ExpandableListViewAdapter"; private static final String TAG = "ExpandableListViewAdapter";
private String[] mGroupStrings; //一级标题 private String[] mGroupStrings; // 一级标题数组
private List<List<AlbumCollection>> mAlbumCollectionList; //二级收藏歌单列表 private List<List<AlbumCollection>> mAlbumCollectionList; // 二级歌单列表
private Context mContext; private Context mContext; // 上下文环境
private OnChildItemClickListener mChildClickListener; //二级item的点击监听 private OnChildItemClickListener mChildClickListener; // 二级项的点击监听器
/**
*
* @param context
* @param groupStrings
* @param albumCollectionList
*/
public ExpandableListViewAdapter(Context context, String[] groupStrings, List<List<AlbumCollection>> albumCollectionList) { public ExpandableListViewAdapter(Context context, String[] groupStrings, List<List<AlbumCollection>> albumCollectionList) {
mAlbumCollectionList = albumCollectionList; mAlbumCollectionList = albumCollectionList;
mGroupStrings = groupStrings; mGroupStrings = groupStrings;
mContext = context; mContext = context;
} }
//提供给外部使用
/**
*
* @param onChildItemClickListener
*/
public void setOnChildItemClickListener(OnChildItemClickListener onChildItemClickListener){ public void setOnChildItemClickListener(OnChildItemClickListener onChildItemClickListener){
mChildClickListener=onChildItemClickListener; mChildClickListener = onChildItemClickListener;
} }
// 获取一级列表的组数
@Override @Override
public int getGroupCount() { public int getGroupCount() {
return mGroupStrings.length; return mGroupStrings.length;
} }
// 获取指定组的子项数
@Override @Override
public int getChildrenCount(int groupPosition) { public int getChildrenCount(int groupPosition) {
return mAlbumCollectionList.get(groupPosition).size(); return mAlbumCollectionList.get(groupPosition).size();
} }
// 获取指定组的对象
@Override @Override
public Object getGroup(int groupPosition) { public Object getGroup(int groupPosition) {
return mGroupStrings[groupPosition]; return mGroupStrings[groupPosition];
} }
// 获取指定子项的对象
@Override @Override
public Object getChild(int groupPosition, int childPosition) { public Object getChild(int groupPosition, int childPosition) {
return mAlbumCollectionList.get(groupPosition).get(childPosition); return mAlbumCollectionList.get(groupPosition).get(childPosition);
} }
// 获取指定组的ID
@Override @Override
public long getGroupId(int groupPosition) { public long getGroupId(int groupPosition) {
return groupPosition; return groupPosition;
} }
// 获取指定子项的ID
@Override @Override
public long getChildId(int groupPosition, int childPosition) { public long getChildId(int groupPosition, int childPosition) {
return childPosition; return childPosition;
} }
// 是否有稳定的ID
@Override @Override
public boolean hasStableIds() { public boolean hasStableIds() {
return true; return true;
} }
//绘制一级列表 // 绘制一级列表
@Override @Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
View view; View view;
@ -90,7 +106,7 @@ public class ExpandableListViewAdapter extends BaseExpandableListAdapter {
groupViewHolder = (GroupViewHolder) view.getTag(); groupViewHolder = (GroupViewHolder) view.getTag();
} }
groupViewHolder.groupTextView.setText(mGroupStrings[groupPosition]); groupViewHolder.groupTextView.setText(mGroupStrings[groupPosition]);
//根据展开状态改变箭头方向 // 根据展开状态改变箭头方向
if (isExpanded) { if (isExpanded) {
groupViewHolder.pointIv.setImageResource(R.drawable.up); groupViewHolder.pointIv.setImageResource(R.drawable.up);
} else { } else {
@ -99,8 +115,7 @@ public class ExpandableListViewAdapter extends BaseExpandableListAdapter {
return view; return view;
} }
// 绘制二级列表项
//绘制二级列表
@Override @Override
public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
View view; View view;
@ -122,22 +137,24 @@ public class ExpandableListViewAdapter extends BaseExpandableListAdapter {
childViewHolder.authorTv.setText(mAlbumCollectionList.get(groupPosition).get(childPosition).getSingerName()); childViewHolder.authorTv.setText(mAlbumCollectionList.get(groupPosition).get(childPosition).getSingerName());
CommonUtil.setImgWithGlide(mContext, CommonUtil.setImgWithGlide(mContext,
mAlbumCollectionList.get(groupPosition).get(childPosition).getAlbumPic(), childViewHolder.faceIv); mAlbumCollectionList.get(groupPosition).get(childPosition).getAlbumPic(), childViewHolder.faceIv);
//点击水波纹效果,结束后开始点击效果 // 点击水波纹效果,结束后开始点击效果
childViewHolder.childView.setOnRippleCompleteListener(rippleView -> mChildClickListener.onClick(groupPosition,childPosition)); childViewHolder.childView.setOnRippleCompleteListener(rippleView -> mChildClickListener.onClick(groupPosition, childPosition));
return view; return view;
} }
// 是否可以选择子项
@Override @Override
public boolean isChildSelectable(int groupPosition, int childPosition) { public boolean isChildSelectable(int groupPosition, int childPosition) {
return true; return true;
} }
// 一级列表项的ViewHolder
class GroupViewHolder { class GroupViewHolder {
private TextView groupTextView; private TextView groupTextView;
private ImageView pointIv; private ImageView pointIv;
} }
// 二级列表项的ViewHolder
class ChildViewHolder { class ChildViewHolder {
TextView albumNameTv; TextView albumNameTv;
ImageView faceIv; ImageView faceIv;

@ -19,35 +19,49 @@ import com.example.musicplayer.util.FileUtil;
import java.util.List; import java.util.List;
/** /**
* HistoryAdapter RecyclerView.Adapter
* Created by on 2018/12/2. * Created by on 2018/12/2.
*/ */
public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "HistoryAdapter"; private static final String TAG = "HistoryAdapter";
private int footerViewType = 1; private int footerViewType = 1; // 底部视图类型标识
private int itemViewType = 0; private int itemViewType = 0; // 列表项视图类型标识
private List<HistorySong> mHistoryList; private List<HistorySong> mHistoryList; // 播放历史歌曲列表
private Context mContext; private Context mContext; // 上下文环境
private int mLastPosition = -1; private int mLastPosition = -1; // 上一个被点击的位置
private OnItemClickListener onItemClickListener; private OnItemClickListener onItemClickListener; // 点击事件监听器
/**
*
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) { public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener; this.onItemClickListener = onItemClickListener;
} }
/**
*
* @param context
* @param historyList
*/
public HistoryAdapter(Context context, List<HistorySong> historyList) { public HistoryAdapter(Context context, List<HistorySong> historyList) {
mContext = context; mContext = context;
mHistoryList =historyList; mHistoryList = historyList;
} }
/**
* ViewHolder
*/
class ViewHolder extends RecyclerView.ViewHolder { class ViewHolder extends RecyclerView.ViewHolder {
TextView songNameTv; TextView songNameTv; // 歌曲名称
TextView singerTv; TextView singerTv; // 歌手名称
RippleView mItemView; RippleView mItemView; // 带有水波纹效果的视图
View playLine; View playLine; // 播放线条,表示当前播放的歌曲
/**
*
* @param itemView
*/
public ViewHolder(View itemView) { public ViewHolder(View itemView) {
super(itemView); super(itemView);
songNameTv = itemView.findViewById(R.id.tv_title); songNameTv = itemView.findViewById(R.id.tv_title);
@ -58,18 +72,27 @@ public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
} }
/** /**
* holder * FooterHolder
*/ */
static class FooterHolder extends RecyclerView.ViewHolder { static class FooterHolder extends RecyclerView.ViewHolder {
TextView numTv; // 底部提示文本
TextView numTv; /**
*
* @param itemView
*/
public FooterHolder(View itemView) { public FooterHolder(View itemView) {
super(itemView); super(itemView);
numTv = itemView.findViewById(R.id.tv_song_num); numTv = itemView.findViewById(R.id.tv_song_num);
} }
} }
/**
* ViewHolder
* @param parent
* @param viewType
* @return ViewHolder
*/
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == itemViewType) { if (viewType == itemViewType) {
@ -85,6 +108,11 @@ public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
} }
} }
/**
* ViewHolder
* @param viewHolder ViewHolder
* @param position
*/
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) { public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
@ -94,10 +122,10 @@ public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
holder.songNameTv.setText(history.getName()); holder.songNameTv.setText(history.getName());
holder.singerTv.setText(history.getSinger()); holder.singerTv.setText(history.getSinger());
//根据点击显示 // 根据当前播放的歌曲设置不同的显示效果
if(history.getSongId().equals(FileUtil.getSong().getSongId())){ if(history.getSongId().equals(FileUtil.getSong().getSongId())){
holder.playLine.setVisibility(View.VISIBLE); holder.playLine.setVisibility(View.VISIBLE);
mLastPosition =position; mLastPosition = position;
holder.songNameTv.setTextColor(mContext.getResources() holder.songNameTv.setTextColor(mContext.getResources()
.getColor(R.color.yellow)); .getColor(R.color.yellow));
holder.singerTv.setTextColor(mContext.getResources() holder.singerTv.setTextColor(mContext.getResources()
@ -119,7 +147,10 @@ public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
} }
} }
//判断点击的是否为上一个点击的项目 /**
*
* @param position
*/
public void equalPosition(int position) { public void equalPosition(int position) {
if (position != mLastPosition) { if (position != mLastPosition) {
notifyItemChanged(mLastPosition); notifyItemChanged(mLastPosition);
@ -128,11 +159,20 @@ public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
notifyItemChanged(position); notifyItemChanged(position);
} }
/**
*
* @return
*/
@Override @Override
public int getItemCount() { public int getItemCount() {
return mHistoryList.size() + 1; return mHistoryList.size() + 1;
} }
/**
*
* @param position
* @return
*/
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return position + 1 == getItemCount() ? footerViewType : itemViewType; return position + 1 == getItemCount() ? footerViewType : itemViewType;

@ -1,5 +1,7 @@
package com.example.musicplayer.adapter; package com.example.musicplayer.adapter;
// 导入所需的包和类
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -19,38 +21,47 @@ import com.example.musicplayer.util.FileUtil;
import java.util.List; import java.util.List;
/** /**
*
* <p>
* Created by on 2018/11/30. * Created by on 2018/11/30.
*/ */
public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "LoveSongAdapter"; private static final String TAG = "LoveSongAdapter"; // 用于日志输出的标签
private int footerViewType = 1; private int footerViewType = 1; // 底部视图类型标识
private int itemViewType = 0; private int itemViewType = 0; // 列表项视图类型标识
private List<Love> mLoveList; private List<Love> mLoveList; // 存储喜欢的歌曲数据
private Context mContext; private Context mContext; // 上下文对象
private int mLastPosition = -1; private int mLastPosition = -1; // 上一次点击的位置
private OnItemClickListener onItemClickListener; private OnItemClickListener onItemClickListener; // 点击事件监听器
/**
*
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) { public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener; this.onItemClickListener = onItemClickListener;
} }
/**
*
* @param context
* @param loveList
*/
public LoveSongAdapter(Context context, List<Love> loveList) { public LoveSongAdapter(Context context, List<Love> loveList) {
mContext = context; mContext = context;
mLoveList = loveList; mLoveList = loveList;
} }
// ViewHolder用于缓存视图提高列表滚动性能
class ViewHolder extends RecyclerView.ViewHolder { class ViewHolder extends RecyclerView.ViewHolder {
TextView songNameTv; TextView songNameTv; // 歌曲名称文本
TextView singerTv; TextView singerTv; // 歌手文本
View playLine; View playLine; // 播放线条视图
RippleView item; RippleView item; // 带有水波纹效果的视图
public ViewHolder(View itemView) { public ViewHolder(View itemView) {
super(itemView); super(itemView);
songNameTv = itemView.findViewById(R.id.tv_title); songNameTv = itemView.findViewById(R.id.tv_title); // 初始化视图
singerTv = itemView.findViewById(R.id.tv_artist); singerTv = itemView.findViewById(R.id.tv_artist);
playLine = itemView.findViewById(R.id.line_play); playLine = itemView.findViewById(R.id.line_play);
item = itemView.findViewById(R.id.ripple); item = itemView.findViewById(R.id.ripple);
@ -58,11 +69,10 @@ public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
} }
/** /**
* holder * ViewHolder
*/ */
static class FooterHolder extends RecyclerView.ViewHolder { static class FooterHolder extends RecyclerView.ViewHolder {
TextView numTv; // 底部信息文本
TextView numTv;
public FooterHolder(View itemView) { public FooterHolder(View itemView) {
super(itemView); super(itemView);
@ -70,6 +80,12 @@ public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
} }
} }
/**
* ViewHolder
* @param parent
* @param viewType
* @return ViewHolder
*/
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == itemViewType) { if (viewType == itemViewType) {
@ -85,6 +101,11 @@ public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
} }
} }
/**
* ViewHolder
* @param viewHolder
* @param position
*/
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) { public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
@ -95,7 +116,7 @@ public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
holder.songNameTv.setText(love.getName()); holder.songNameTv.setText(love.getName());
holder.singerTv.setText(love.getSinger()); holder.singerTv.setText(love.getSinger());
//根据点击显示 // 根据歌曲是否正在播放显示不同的样式
if (love.getSongId().equals(FileUtil.getSong().getSongId())) { if (love.getSongId().equals(FileUtil.getSong().getSongId())) {
holder.playLine.setVisibility(View.VISIBLE); holder.playLine.setVisibility(View.VISIBLE);
mLastPosition = position; mLastPosition = position;
@ -122,7 +143,10 @@ public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
} }
} }
//判断点击的是否为上一个点击的项目 /**
*
* @param position
*/
public void equalPosition(int position) { public void equalPosition(int position) {
if (position != mLastPosition) { if (position != mLastPosition) {
notifyItemChanged(mLastPosition); notifyItemChanged(mLastPosition);
@ -131,11 +155,20 @@ public class LoveSongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
notifyItemChanged(position); notifyItemChanged(position);
} }
/**
*
* @return
*/
@Override @Override
public int getItemCount() { public int getItemCount() {
return mLoveList.size() + 1; return mLoveList.size() + 1; // 加1是为了显示底部视图
} }
/**
*
* @param position
* @return
*/
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return position + 1 == getItemCount() ? footerViewType : itemViewType; return position + 1 == getItemCount() ? footerViewType : itemViewType;

@ -1,5 +1,7 @@
package com.example.musicplayer.adapter; package com.example.musicplayer.adapter;
// 导入所需的包和类
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
@ -25,30 +27,32 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
*
* Created by on 2018/11/21. * Created by on 2018/11/21.
*/ */
public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "SearchContentAdapter"; private static final String TAG = "SearchContentAdapter";
private ArrayList<SearchSong.DataBean.SongBean.ListBean> mSongListBeans; private ArrayList<SearchSong.DataBean.SongBean.ListBean> mSongListBeans; // 单曲列表数据
private List<Album.DataBean.AlbumBean.ListBean> mAlbumList; private List<Album.DataBean.AlbumBean.ListBean> mAlbumList; // 专辑列表数据
private static OnItemClickListener mItemClick; private static OnItemClickListener mItemClick; // 单曲点击事件监听器
private static OnAlbumItemClickListener mAlbumClick; private static OnAlbumItemClickListener mAlbumClick; // 专辑点击事件监听器
private String mSeek; private String mSeek; // 搜索关键字
private Context mContext; private Context mContext; // 上下文对象
private int mLastPosition = -1; private int mLastPosition = -1; // 上一次点击的位置
private int mType; private int mType; // 列表类型,单曲或专辑
// 设置单曲点击事件监听器
public static void setItemClick(OnItemClickListener itemClick) { public static void setItemClick(OnItemClickListener itemClick) {
mItemClick = itemClick; mItemClick = itemClick;
} }
// 设置专辑点击事件监听器
public static void setAlbumClick(OnAlbumItemClickListener albumClick) { public static void setAlbumClick(OnAlbumItemClickListener albumClick) {
mAlbumClick = albumClick; mAlbumClick = albumClick;
} }
// 构造函数,初始化专辑搜索适配器
public SearchContentAdapter(List<Album.DataBean.AlbumBean.ListBean> dataBeans, String seek, Context context, int type) { public SearchContentAdapter(List<Album.DataBean.AlbumBean.ListBean> dataBeans, String seek, Context context, int type) {
mContext = context; mContext = context;
mSeek = seek; mSeek = seek;
@ -56,7 +60,7 @@ public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.View
mType = type; mType = type;
} }
// 构造函数,初始化单曲搜索适配器
public SearchContentAdapter(ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans, String seek, Context context, int type) { public SearchContentAdapter(ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans, String seek, Context context, int type) {
mContext = context; mContext = context;
mSeek = seek; mSeek = seek;
@ -64,10 +68,11 @@ public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.View
mType = type; mType = type;
} }
// 创建ViewHolder
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
final View view; final View view;
if (viewType == Constant.TYPE_SONG ) { if (viewType == Constant.TYPE_SONG) {
view = LayoutInflater.from(parent.getContext()) view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recycler_song_search_item, parent, false); .inflate(R.layout.recycler_song_search_item, parent, false);
return new ViewHolder(view); return new ViewHolder(view);
@ -77,35 +82,37 @@ public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.View
return new AlbumHolder(view); return new AlbumHolder(view);
} }
return null; return null;
} }
// 将数据绑定到ViewHolder
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
// 根据ViewHolder类型进行不同的数据处理
if (holder instanceof ViewHolder) { if (holder instanceof ViewHolder) {
ViewHolder songHolder = (ViewHolder) holder; ViewHolder songHolder = (ViewHolder) holder;
SearchSong.DataBean.SongBean.ListBean songListBean = mSongListBeans.get(position); SearchSong.DataBean.SongBean.ListBean songListBean = mSongListBeans.get(position);
//设置歌手,因为歌手可能有两个 // 设置歌手名称,可能存在多个歌手
String singer = songListBean.getSinger().get(0).getName(); String singer = songListBean.getSinger().get(0).getName();
for (int i = 1; i < songListBean.getSinger().size(); i++) { for (int i = 1; i < songListBean.getSinger().size(); i++) {
singer+="、"+songListBean.getSinger().get(i).getName(); singer += "、" + songListBean.getSinger().get(i).getName();
} }
songHolder.artistTv.setText(singer); songHolder.artistTv.setText(singer);
//设置与搜索一样的string的颜色 // 高亮显示搜索关键字
CommonUtil.showStringColor(mSeek, singer, songHolder.artistTv); CommonUtil.showStringColor(mSeek, singer, songHolder.artistTv);
songHolder.titleTv.setText(songListBean.getSongname()); songHolder.titleTv.setText(songListBean.getSongname());
CommonUtil.showStringColor(mSeek, songListBean.getSongname(), songHolder.titleTv); CommonUtil.showStringColor(mSeek, songListBean.getSongname(), songHolder.titleTv);
//根据点击显示 // 根据歌曲是否正在播放显示不同的样式
if(songListBean.getSongmid().equals(FileUtil.getSong().getSongId())){ if (songListBean.getSongmid().equals(FileUtil.getSong().getSongId())) {
songHolder.playLine.setVisibility(View.VISIBLE); songHolder.playLine.setVisibility(View.VISIBLE);
mLastPosition =position; mLastPosition = position;
songHolder.mItemView.setBackgroundResource(R.color.translucent); songHolder.mItemView.setBackgroundResource(R.color.translucent);
}else { } else {
songHolder.playLine.setVisibility(View.INVISIBLE); songHolder.playLine.setVisibility(View.INVISIBLE);
songHolder.mItemView.setBackgroundResource(R.color.transparent); songHolder.mItemView.setBackgroundResource(R.color.transparent);
} }
// 设置点击事件
songHolder.mItemView.setOnRippleCompleteListener(rippleView -> { songHolder.mItemView.setOnRippleCompleteListener(rippleView -> {
mItemClick.onClick(position); mItemClick.onClick(position);
equalPosition(position); equalPosition(position);
@ -113,21 +120,24 @@ public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.View
} else { } else {
AlbumHolder albumHolder = (AlbumHolder) holder; AlbumHolder albumHolder = (AlbumHolder) holder;
Album.DataBean.AlbumBean.ListBean albumList = mAlbumList.get(position); Album.DataBean.AlbumBean.ListBean albumList = mAlbumList.get(position);
// 使用Glide加载专辑图片
Glide.with(mContext).load(albumList.getAlbumPic()) Glide.with(mContext).load(albumList.getAlbumPic())
.apply(RequestOptions.errorOf(R.drawable.background)).into(albumHolder.albumIv); .apply(RequestOptions.errorOf(R.drawable.background)).into(albumHolder.albumIv);
albumHolder.albumName.setText(albumList.getAlbumName()); albumHolder.albumName.setText(albumList.getAlbumName());
albumHolder.singerName.setText(albumList.getSingerName()); albumHolder.singerName.setText(albumList.getSingerName());
albumHolder.publicTime.setText(albumList.getPublicTime()); albumHolder.publicTime.setText(albumList.getPublicTime());
// 高亮显示搜索关键字
CommonUtil.showStringColor(mSeek, albumList.getAlbumName(), albumHolder.albumName); CommonUtil.showStringColor(mSeek, albumList.getAlbumName(), albumHolder.albumName);
CommonUtil.showStringColor(mSeek, albumList.getSingerName(), albumHolder.singerName); CommonUtil.showStringColor(mSeek, albumList.getSingerName(), albumHolder.singerName);
CommonUtil.showStringColor(mSeek, albumList.getPublicTime(), albumHolder.publicTime); CommonUtil.showStringColor(mSeek, albumList.getPublicTime(), albumHolder.publicTime);
// 设置点击事件
albumHolder.item.setOnRippleCompleteListener(rippleView -> { albumHolder.item.setOnRippleCompleteListener(rippleView -> {
mAlbumClick.onClick(position); mAlbumClick.onClick(position);
}); });
} }
} }
// 获取列表项的总数
@Override @Override
public int getItemCount() { public int getItemCount() {
if (mType == Constant.TYPE_SONG) { if (mType == Constant.TYPE_SONG) {
@ -138,6 +148,7 @@ public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.View
return 0; return 0;
} }
// 内部类ViewHolder用于单曲列表项
class ViewHolder extends RecyclerView.ViewHolder { class ViewHolder extends RecyclerView.ViewHolder {
TextView titleTv; TextView titleTv;
TextView artistTv; TextView artistTv;
@ -153,6 +164,7 @@ public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
} }
// 内部类AlbumHolder用于专辑列表项
class AlbumHolder extends RecyclerView.ViewHolder { class AlbumHolder extends RecyclerView.ViewHolder {
ImageView albumIv; ImageView albumIv;
TextView singerName; TextView singerName;
@ -170,12 +182,13 @@ public class SearchContentAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
} }
// 获取列表项的视图类型
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return mType; return mType;
} }
//判断点击的是否为上一个点击的项目 // 判断点击的是否为上一个点击的项目,并更新视图
public void equalPosition(int position) { public void equalPosition(int position) {
if (position != mLastPosition) { if (position != mLastPosition) {
if (mLastPosition != -1) notifyItemChanged(mLastPosition); if (mLastPosition != -1) notifyItemChanged(mLastPosition);

@ -1,5 +1,7 @@
package com.example.musicplayer.adapter; package com.example.musicplayer.adapter;
// 导入所需的包和类
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -18,64 +20,72 @@ import com.example.musicplayer.entiy.SearchHistory;
import java.util.List; import java.util.List;
/** /**
* *
* Created by on 2018/11/29. * Created by on 2018/11/29.
*/ */
public class SearchHistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class SearchHistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<SearchHistory> mSearchHistoryList; private List<SearchHistory> mSearchHistoryList; // 搜索历史列表数据
private static final int mHistoryType =0; private static final int mHistoryType = 0; // 历史记录项的视图类型
private static final int mFooterType = 1; private static final int mFooterType = 1; // 底部删除按钮的视图类型
private OnItemClickListener mOnItemClickListener; private OnItemClickListener mOnItemClickListener; // 列表项点击事件监听器
private OnDeleteClickListener mOnDeleteClickListener; private OnDeleteClickListener mOnDeleteClickListener; // 删除按钮点击事件监听器
private OnFooterClickListener mFooterClickListener; private OnFooterClickListener mFooterClickListener; // 底部删除所有按钮点击事件监听器
public SearchHistoryAdapter(List<SearchHistory> searchHistoryList){ // 构造函数,初始化搜索历史列表
public SearchHistoryAdapter(List<SearchHistory> searchHistoryList) {
mSearchHistoryList = searchHistoryList; mSearchHistoryList = searchHistoryList;
} }
// 创建ViewHolder
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if(viewType ==mHistoryType){ if (viewType == mHistoryType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_seek_history_item, // 历史记录项布局
parent,false); View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_seek_history_item, parent, false);
return new HistoryHolder(view); return new HistoryHolder(view);
}else{ } else {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.footer_delete_all_history_item, // 底部删除所有按钮布局
parent,false); View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.footer_delete_all_history_item, parent, false);
return new FooterHolder(view); return new FooterHolder(view);
} }
} }
// 将数据绑定到ViewHolder
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
if(holder instanceof HistoryHolder){ if (holder instanceof HistoryHolder) {
HistoryHolder historyHolder =(HistoryHolder) holder; HistoryHolder historyHolder = (HistoryHolder) holder;
// 设置历史记录文本
historyHolder.historyTv.setText(mSearchHistoryList.get(position).getHistory()); historyHolder.historyTv.setText(mSearchHistoryList.get(position).getHistory());
// 设置删除按钮点击事件
historyHolder.deleteIv.setOnClickListener(v -> mOnDeleteClickListener.onClick(position)); historyHolder.deleteIv.setOnClickListener(v -> mOnDeleteClickListener.onClick(position));
// 设置列表项点击事件
historyHolder.mItemView.setOnRippleCompleteListener(rippleView -> mOnItemClickListener.onClick(position)); historyHolder.mItemView.setOnRippleCompleteListener(rippleView -> mOnItemClickListener.onClick(position));
}else{ } else {
FooterHolder footerHolder =(FooterHolder) holder; FooterHolder footerHolder = (FooterHolder) holder;
// 设置底部删除所有按钮点击事件
footerHolder.deleteView.setOnClickListener(v -> mFooterClickListener.onClick()); footerHolder.deleteView.setOnClickListener(v -> mFooterClickListener.onClick());
} }
} }
// 获取列表项的总数
@Override @Override
public int getItemCount() { public int getItemCount() {
return mSearchHistoryList.size()+1; return mSearchHistoryList.size() + 1; // 加1是为了显示底部删除所有按钮
} }
// 获取列表项的视图类型
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return position + 1 == getItemCount() ? mFooterType :mHistoryType; return position + 1 == getItemCount() ? mFooterType : mHistoryType;
} }
// 内部类HistoryHolder用于历史记录列表项
private class HistoryHolder extends RecyclerView.ViewHolder { private class HistoryHolder extends RecyclerView.ViewHolder {
TextView historyTv; TextView historyTv; // 历史记录文本
ImageView deleteIv; ImageView deleteIv; // 删除按钮
RippleView mItemView; RippleView mItemView; // 列表项背景
public HistoryHolder(View itemView) { public HistoryHolder(View itemView) {
super(itemView); super(itemView);
@ -85,8 +95,9 @@ public class SearchHistoryAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
} }
// 内部类FooterHolder用于底部删除所有按钮
private class FooterHolder extends RecyclerView.ViewHolder { private class FooterHolder extends RecyclerView.ViewHolder {
View deleteView; View deleteView; // 删除所有按钮
public FooterHolder(View itemView) { public FooterHolder(View itemView) {
super(itemView); super(itemView);
@ -94,13 +105,18 @@ public class SearchHistoryAdapter extends RecyclerView.Adapter<RecyclerView.View
} }
} }
public void setOnItemClickListener(OnItemClickListener onItemClcikListener){ // 设置列表项点击事件监听器
mOnItemClickListener =onItemClcikListener; public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
mOnItemClickListener = onItemClickListener;
} }
public void setOnDeleteClickListener(OnDeleteClickListener onDeleteClickListener){
mOnDeleteClickListener =onDeleteClickListener; // 设置删除按钮点击事件监听器
public void setOnDeleteClickListener(OnDeleteClickListener onDeleteClickListener) {
mOnDeleteClickListener = onDeleteClickListener;
} }
public void setFooterClickListener(OnFooterClickListener onFooterClickListener){
mFooterClickListener =onFooterClickListener; // 设置底部删除所有按钮点击事件监听器
public void setFooterClickListener(OnFooterClickListener onFooterClickListener) {
mFooterClickListener = onFooterClickListener;
} }
} }

@ -1,5 +1,7 @@
package com.example.musicplayer.adapter; package com.example.musicplayer.adapter;
// 导入所需的包和类
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
@ -21,33 +23,35 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**
*
* Created by on 2018/10/10. * Created by on 2018/10/10.
*/ */
public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String TAG = "SongAdapter"; private static final String TAG = "SongAdapter"; // 用于日志输出的标签
private int footerViewType = 1; private int footerViewType = 1; // 底部视图类型标识
private int itemViewType = 0; private int itemViewType = 0; // 列表项视图类型标识
private List<LocalSong> mMp3InfoList; private List<LocalSong> mMp3InfoList; // 本地歌曲列表数据
private Context mContext; private Context mContext; // 上下文对象
private int mLastPosition = -1; private int mLastPosition = -1; // 上一次点击的位置
private OnItemClickListener onItemClickListener; private OnItemClickListener onItemClickListener; // 点击事件监听器
// 设置点击事件监听器
public void setOnItemClickListener(OnItemClickListener onItemClickListener) { public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener; this.onItemClickListener = onItemClickListener;
} }
// 构造函数,初始化上下文和本地歌曲列表
public SongAdapter(Context context, List<LocalSong> mp3InfoList) { public SongAdapter(Context context, List<LocalSong> mp3InfoList) {
mContext = context; mContext = context;
mMp3InfoList = mp3InfoList; mMp3InfoList = mp3InfoList;
} }
// 内部类ViewHolder用于歌曲列表项
static class ViewHolder extends RecyclerView.ViewHolder { static class ViewHolder extends RecyclerView.ViewHolder {
TextView songNameTv; TextView songNameTv; // 歌曲名称文本
TextView artistTv; TextView artistTv; // 歌手文本
ImageView playingIv; ImageView playingIv; // 播放状态图标
RippleView songView; RippleView songView; // 歌曲项背景
public ViewHolder(View itemView) { public ViewHolder(View itemView) {
super(itemView); super(itemView);
@ -58,12 +62,9 @@ public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
} }
} }
/** // 底部holder
* holder
*/
static class FooterHolder extends RecyclerView.ViewHolder { static class FooterHolder extends RecyclerView.ViewHolder {
TextView numTv; // 底部信息文本
TextView numTv;
public FooterHolder(View itemView) { public FooterHolder(View itemView) {
super(itemView); super(itemView);
@ -71,6 +72,7 @@ public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
} }
} }
// 创建ViewHolder
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == itemViewType) { if (viewType == itemViewType) {
@ -86,6 +88,7 @@ public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
} }
} }
// 将数据绑定到ViewHolder
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@Override @Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) { public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, final int position) {
@ -95,20 +98,20 @@ public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
holder.songNameTv.setText(mp3Info.getName()); holder.songNameTv.setText(mp3Info.getName());
holder.artistTv.setText(mp3Info.getSinger()); holder.artistTv.setText(mp3Info.getSinger());
//根据播放的歌曲是否为当前列表的歌曲显示 // 根据播放的歌曲是否为当前列表的歌曲显示
String songId = Objects.requireNonNull(FileUtil.getSong()).getSongId(); String songId = Objects.requireNonNull(FileUtil.getSong()).getSongId();
if(songId!=null&&(mp3Info.getSongId().equals(songId))){ if (songId != null && mp3Info.getSongId().equals(songId)) {
holder.songNameTv.setTextColor(App.getContext(). holder.songNameTv.setTextColor(App.getContext()
getResources().getColor(R.color.musicStyle_low)); .getResources().getColor(R.color.musicStyle_low));
holder.artistTv.setTextColor(App.getContext(). holder.artistTv.setTextColor(App.getContext()
getResources().getColor(R.color.musicStyle_low)); .getResources().getColor(R.color.musicStyle_low));
holder.playingIv.setVisibility(View.VISIBLE); holder.playingIv.setVisibility(View.VISIBLE);
mLastPosition = position; mLastPosition = position;
}else { } else {
holder.songNameTv.setTextColor(App.getContext(). holder.songNameTv.setTextColor(App.getContext()
getResources().getColor(R.color.white)); .getResources().getColor(R.color.white));
holder.artistTv.setTextColor(App.getContext(). holder.artistTv.setTextColor(App.getContext()
getResources().getColor(R.color.white)); .getResources().getColor(R.color.white));
holder.playingIv.setVisibility(View.GONE); holder.playingIv.setVisibility(View.GONE);
} }
holder.songView.setOnRippleCompleteListener(rippleView -> { holder.songView.setOnRippleCompleteListener(rippleView -> {
@ -117,11 +120,11 @@ public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
}); });
} else { } else {
FooterHolder footerHolder = (FooterHolder) viewHolder; FooterHolder footerHolder = (FooterHolder) viewHolder;
footerHolder.numTv.setText("共" +mMp3InfoList.size()+ "首音乐"); footerHolder.numTv.setText("共" + mMp3InfoList.size() + "首音乐");
} }
} }
//判断点击的是否为上一个点击的项目 // 判断点击的是否为上一个点击的项目
public void equalPosition(int position) { public void equalPosition(int position) {
if (position != mLastPosition) { if (position != mLastPosition) {
notifyItemChanged(mLastPosition); notifyItemChanged(mLastPosition);
@ -130,17 +133,15 @@ public class SongAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
notifyItemChanged(position); notifyItemChanged(position);
} }
// 获取列表项的总数
@Override @Override
public int getItemCount() { public int getItemCount() {
return mMp3InfoList.size() + 1; return mMp3InfoList.size() + 1; // 加1是为了显示底部视图
} }
// 获取列表项的视图类型
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return position + 1 == getItemCount() ? footerViewType : itemViewType; return position + 1 == getItemCount() ? footerViewType : itemViewType;
} }
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.adapter; package com.example.musicplayer.adapter;
// 导入所需的包和类
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
@ -7,29 +8,33 @@ import android.support.v4.app.FragmentPagerAdapter;
import java.util.List; import java.util.List;
/** /**
* Fragment
* Created by on 2018/11/25. * Created by on 2018/11/25.
*/ */
public class TabAdapter extends FragmentPagerAdapter { public class TabAdapter extends FragmentPagerAdapter {
private List<Fragment> mFragmentList;//顶部导航栏的内容即fragment private List<Fragment> mFragmentList; // 顶部导航栏的内容,即 Fragment 列表
private List<String> mTitle;//顶部导航栏的标题 private List<String> mTitle; // 顶部导航栏的标题列表
public TabAdapter(FragmentManager fragmentManager, List<Fragment>fragments, List<String>title){ // 构造函数,初始化 FragmentManager、Fragment 列表和标题列表
public TabAdapter(FragmentManager fragmentManager, List<Fragment> fragments, List<String> title) {
super(fragmentManager); super(fragmentManager);
mFragmentList=fragments; mFragmentList = fragments;
mTitle=title; mTitle = title;
} }
// 获取指定位置的 Fragment
@Override @Override
public Fragment getItem(int position) { public Fragment getItem(int position) {
return mFragmentList.get(position); return mFragmentList.get(position);
} }
// 获取 Fragment 的总数
@Override @Override
public int getCount() { public int getCount() {
return mFragmentList.size(); return mFragmentList.size();
} }
// 获取指定位置的标题
@Override @Override
public CharSequence getPageTitle(int position) { public CharSequence getPageTitle(int position) {
return mTitle.get(position); return mTitle.get(position);

@ -3,42 +3,41 @@ package com.example.musicplayer.app;
import android.os.Environment; import android.os.Environment;
/** /**
* 使API
* Created by on 2018/10/26. * Created by on 2018/10/26.
*/ */
public class Api { public class Api {
//根据歌手获取歌手图片的baseUrl // 根据歌手获取歌手图片的baseUrl
public static final String SINGER_PIC_BASE_URL ="http://music.163.com/"; public static final String SINGER_PIC_BASE_URL = "http://music.163.com/";
public static final String SINGER_PIC="api/search/get/web?csrf_token=&type=100"; public static final String SINGER_PIC = "api/search/get/web?csrf_token=&type=100";
//获取歌手图片需要添加user-agent的表头 // 获取歌手图片需要添加user-agent的表头
public static final String HEADER_USER_AGENT="User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"; public static final String HEADER_USER_AGENT = "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36";
/** /**
* *
*/ */
public static String STORAGE_IMG_FILE= App.getContext().getExternalFilesDir("") + "/yuanmusic/img/"; public static String STORAGE_IMG_FILE = App.getContext().getExternalFilesDir("") + "/yuanmusic/img/";
public static String STORAGE_LRC_FILE= App.getContext().getExternalFilesDir("") + "/yuanmusic/lrc/"; public static String STORAGE_LRC_FILE = App.getContext().getExternalFilesDir("") + "/yuanmusic/lrc/";
public static String STORAGE_SONG_FILE= Environment.getExternalStorageDirectory() + "/Sxmusic/download/"; public static String STORAGE_SONG_FILE = Environment.getExternalStorageDirectory() + "/Sxmusic/download/";
//Fiddler抓包qq音乐网站后的地址
public static final String FIDDLER_BASE_QQ_URL ="https://c.y.qq.com/";
public static final String FIDDLER_BASE_SONG_URL="https://u.y.qq.com/"; //获取播放地址的baseUrl
//搜索功能
public static final String SEARCH_SONG ="soso/fcgi-bin/client_search_cp?n=30&format=json"; //歌曲n为一页30首
public static final String SEARCH_ALBUM="soso/fcgi-bin/client_search_cp?n=20&format=json&t=8";//专辑n为一页20张
//得到歌曲的播放地址
public static final String SONG_URL="cgi-bin/musicu.fcg?format=json";
public static final String SONG_URL_DATA_LEFT="%7B%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22358840384%22%2C%22songmid%22%3A%5B%22";
public static final String SONG_URL_DATA_RIGHT="%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221443481947%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A%221443481947%22%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D";
public static final String MP4 =".mp4";
//专辑照片
public static final String ALBUM_PIC="http://y.gtimg.cn/music/photo_new/T002R180x180M000";
public static final String JPG=".jpg";
//专辑详细
public static final String ALBUM_DETAIL="v8/fcg-bin/fcg_v8_album_info_cp.fcg?format=json";
//歌词
public static final String SONG_LRC ="soso/fcgi-bin/client_search_cp?p=1&n=30&format=json&t=7";//搜索歌词
public static final String ONLINE_SONG_LRC="https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?format=json&nobase64=1";//根据qq音乐的mid获取歌词
public static final String HEADER_REFERER="Referer:https://y.qq.com/portal/player.html";//得到歌词需要添加Referer的表头
// Fiddler抓包qq音乐网站后的地址
public static final String FIDDLER_BASE_QQ_URL = "https://c.y.qq.com/";
public static final String FIDDLER_BASE_SONG_URL = "https://u.y.qq.com/"; // 获取播放地址的baseUrl
// 搜索功能
public static final String SEARCH_SONG = "soso/fcgi-bin/client_search_cp?n=30&format=json"; // 歌曲n为一页30首
public static final String SEARCH_ALBUM = "soso/fcgi-bin/client_search_cp?n=20&format=json&t=8"; // 专辑n为一页20张
// 得到歌曲的播放地址
public static final String SONG_URL = "cgi-bin/musicu.fcg?format=json";
public static final String SONG_URL_DATA_LEFT = "%7B%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22358840384%22%2C%22songmid%22%3A%5B%22";
public static final String SONG_URL_DATA_RIGHT = "%22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221443481947%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A%221443481947%22%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D";
public static final String MP4 = ".mp4";
// 专辑照片
public static final String ALBUM_PIC = "http://y.gtimg.cn/music/photo_new/T002R180x180M000";
public static final String JPG = ".jpg";
// 专辑详细
public static final String ALBUM_DETAIL = "v8/fcg-bin/fcg_v8_album_info_cp.fcg?format=json";
// 歌词
public static final String SONG_LRC = "soso/fcgi-bin/client_search_cp?p=1&n=30&format=json&t=7"; // 搜索歌词
public static final String ONLINE_SONG_LRC = "https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?format=json&nobase64=1"; // 根据qq音乐的mid获取歌词
public static final String HEADER_REFERER = "Referer:https://y.qq.com/portal/player.html"; // 得到歌词需要添加Referer的表头
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.app; package com.example.musicplayer.app;
// 导入所需的包和类
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
@ -7,22 +8,30 @@ import android.content.Context;
import org.litepal.LitePal; import org.litepal.LitePal;
/** /**
* Context * Context
* <p>
* Created by on 2018/7/17. * Created by on 2018/7/17.
*/ */
public class App extends Application { public class App extends Application {
// 使用SuppressLint注解来抑制静态字段可能引起的内存泄漏警告
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private static Context context; private static Context context; // 静态变量用于存储全局Context对象
/**
* ContextLitePal
*/
@Override @Override
public void onCreate(){ public void onCreate() {
super.onCreate(); super.onCreate(); // 调用父类的onCreate方法
context=getApplicationContext(); context = getApplicationContext(); // 初始化全局Context对象
LitePal.initialize(this); LitePal.initialize(this); // 初始化LitePal数据库
} }
public static Context getContext(){ /**
* Context
* @return Context
*/
public static Context getContext() {
return context; return context;
} }
} }

@ -1,82 +1,82 @@
package com.example.musicplayer.app; package com.example.musicplayer.app;
/** /**
* 使
* Created by on 2018/11/23. * Created by on 2018/11/23.
*/ */
public class Constant { public class Constant {
// 日志标签
public static final String TAG = "jsyjst"; public static final String TAG = "jsyjst";
// 分页加载的偏移量
public static final int OFFSET = 30; public static final int OFFSET = 30;
// 类型常量,用于区分歌曲和专辑
public static final int TYPE_SONG = 1; public static final int TYPE_SONG = 1;
public static final int TYPE_ALBUM = 2; public static final int TYPE_ALBUM = 2;
//播放列表 // 播放列表类型
public static final int LIST_TYPE_LOCAL = 1; //本地列表 public static final int LIST_TYPE_LOCAL = 1; // 本地列表
public static final int LIST_TYPE_ONLINE = 2; //专辑列表 public static final int LIST_TYPE_ONLINE = 2; // 专辑列表
public static final int LIST_TYPE_LOVE = 3; //我的收藏列表 public static final int LIST_TYPE_LOVE = 3; // 我的收藏列表
public static final int LIST_TYPE_HISTORY = 4; //最近播放列表 public static final int LIST_TYPE_HISTORY = 4; // 最近播放列表
public static final int LIST_TYPE_DOWNLOAD = 5; //下载列表 public static final int LIST_TYPE_DOWNLOAD = 5; // 下载列表
// 搜索历史最大条目数
public static final int HISTORY_MAX_SIZE = 100; public static final int HISTORY_MAX_SIZE = 100;
// 布局状态常量
//布局
public static final int NORMAL_STATE = 0; public static final int NORMAL_STATE = 0;
public static final int LOADING_STATE = 1; public static final int LOADING_STATE = 1;
public static final int ERROR_STATE = 2; public static final int ERROR_STATE = 2;
//playerStatus // 播放器状态常量
public static final String PLAYER_STATUS = "PlayerStatus"; public static final String PLAYER_STATUS = "PlayerStatus";
public static final int SONG_PLAY = 0; public static final int SONG_PLAY = 0;
public static final int SONG_PAUSE = 1; public static final int SONG_PAUSE = 1;
public static final int SONG_RESUME = 2; public static final int SONG_RESUME = 2;
public static final int SONG_CHANGE = 3; public static final int SONG_CHANGE = 3;
// 键值对常量
//KEY
public static final String ALBUM_ID_KEY = "id"; public static final String ALBUM_ID_KEY = "id";
public static final String ALBUM_NAME_KEY = "albumName"; public static final String ALBUM_NAME_KEY = "albumName";
public static final String SINGER_NAME_KEY = "singerName"; public static final String SINGER_NAME_KEY = "singerName";
public static final String ALBUM_PIC_KEY = "albumPic"; public static final String ALBUM_PIC_KEY = "albumPic";
public static final String PUBLIC_TIME_KEY = "publicTime"; public static final String PUBLIC_TIME_KEY = "publicTime";
// 错误标签
//TAG
public static final String TAG_ERROR = "error"; public static final String TAG_ERROR = "error";
//网络与非网络歌曲 // 网络与非网络歌曲标识
public static final int SONG_ONLINE = 0; public static final int SONG_ONLINE = 0;
public static final int SONG_LOCAL = 1; public static final int SONG_LOCAL = 1;
// 后缀常量
//后缀
public static final String LRC = ".lrc"; public static final String LRC = ".lrc";
//音乐id // 音乐id未找到标识
public static final String SONG_ID_UNFIND = "unFind"; public static final String SONG_ID_UNFIND = "unFind";
//播放顺序 // 播放顺序常量
public static final int PLAY_ORDER = 0;//顺序播放 public static final int PLAY_ORDER = 0; // 顺序播放
public static final int PLAY_SINGLE = 1;//单曲循环 public static final int PLAY_SINGLE = 1; // 单曲循环
public static final int PLAY_RANDOM = 2;//随机播放 public static final int PLAY_RANDOM = 2; // 随机播放
//Preferences // Preferences常量
public static final String SHARED_PREFERENCES_NAME = "prefs"; public static final String SHARED_PREFERENCES_NAME = "prefs";
public static final String PREFS_PLAY_MODE = "play_mode";//播放状态 public static final String PREFS_PLAY_MODE = "play_mode"; // 播放模式
//download // 下载状态常量
public final static int TYPE_DOWNLOADING = 0; public final static int TYPE_DOWNLOADING = 0;
public final static int TYPE_DOWNLOAD_PAUSED = 1; public final static int TYPE_DOWNLOAD_PAUSED = 1;
public final static int TYPE_DOWNLOAD_CANCELED = 2; public final static int TYPE_DOWNLOAD_CANCELED = 2;
public final static int TYPE_DOWNLOAD_SUCCESS = 3; public final static int TYPE_DOWNLOAD_SUCCESS = 3;
public final static int TYPE_DOWNLOAD_FAILED = 4; public final static int TYPE_DOWNLOAD_FAILED = 4;
public final static int TYPE_DOWNLOADED = 5; public final static int TYPE_DOWNLOADED = 5;
public final static int TYPE_DOWNLOAD_ADD=6; public final static int TYPE_DOWNLOAD_ADD = 6;
//正在下载歌曲列表的状态 // 正在下载歌曲列表的状态常量
public final static int DOWNLOAD_PAUSED = 0; public final static int DOWNLOAD_PAUSED = 0;
public final static int DOWNLOAD_WAIT=1; public final static int DOWNLOAD_WAIT = 1;
public final static int DOWNLOAD_ING=2; public final static int DOWNLOAD_ING = 2;
public final static int DOWNLOAD_READY=3; public final static int DOWNLOAD_READY = 3;
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.base.activity; package com.example.musicplayer.base.activity;
// 导入所需的包和类
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
@ -12,63 +13,68 @@ import butterknife.ButterKnife;
import butterknife.Unbinder; import butterknife.Unbinder;
/** /**
* <pre> *
* <p>
* author : * author :
* time : 2019/07/17 * time : 2019/07/17
* desc : * desc :
* </pre>
*/ */
public abstract class BaseActivity extends AppCompatActivity implements BaseView { public abstract class BaseActivity extends AppCompatActivity implements BaseView {
private Unbinder mBinder; private Unbinder mBinder; // ButterKnife绑定对象用于解绑
protected abstract int getLayoutId(); //获取布局id // 获取布局id
protected abstract void initView(); //初始化布局 protected abstract int getLayoutId();
protected abstract void initData(); //初始化数据 // 初始化布局
protected abstract void onClick();//点击事件 protected abstract void initView();
// 初始化数据
protected abstract void initData();
// 点击事件
protected abstract void onClick();
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(getLayoutId()); setContentView(getLayoutId()); // 设置布局
ButterKnife.bind(this); ButterKnife.bind(this); // 绑定视图
initView(); initView(); // 初始化视图
initData(); initData(); // 初始化数据
onClick(); onClick(); // 点击事件
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
// 解绑ButterKnife避免内存泄漏
if(mBinder != null && mBinder != mBinder.EMPTY){ if(mBinder != null && mBinder != mBinder.EMPTY){
mBinder.unbind(); mBinder.unbind();
mBinder = null; mBinder = null;
} }
} }
// 显示提示信息
@Override @Override
public void showToast(String message) { public void showToast(String message) {
CommonUtil.showToast(this,message); CommonUtil.showToast(this, message);
} }
// 显示正常视图
@Override @Override
public void showNormalView() { public void showNormalView() {
} }
// 显示错误视图
@Override @Override
public void showErrorView() { public void showErrorView() {
} }
// 显示加载视图
@Override @Override
public void showLoading() { public void showLoading() {
} }
// 重新加载数据
@Override @Override
public void reload() { public void reload() {
} }
} }

@ -3,29 +3,36 @@ package com.example.musicplayer.base.activity;
import com.example.musicplayer.base.presenter.IPresenter; import com.example.musicplayer.base.presenter.IPresenter;
/** /**
* <pre> * MVPActivityMVPActivity
* <p>
* author : * author :
* time : 2019/07/17 * time : 2019/07/17
* desc : * desc : Presenter
* </pre>
*/ */
public abstract class BaseMvpActivity<T extends IPresenter> extends BaseActivity {
public abstract class BaseMvpActivity<T extends IPresenter> extends BaseActivity{ // 抽象方法用于获取具体的Presenter实例
protected abstract T getPresenter(); protected abstract T getPresenter();
// Presenter成员变量用于与View进行交互
protected T mPresenter; protected T mPresenter;
/**
* Presenter
*/
@Override @Override
protected void initView() { protected void initView() {
mPresenter = getPresenter(); mPresenter = getPresenter(); // 获取Presenter实例
mPresenter.attachView(this); mPresenter.attachView(this); // 将当前Activity绑定到Presenter的View
} }
/**
* ActivityPresenter
*/
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy(); // 调用父类的onDestroy方法
if(mPresenter != null){ if (mPresenter != null) {
mPresenter.detachView(); mPresenter.detachView(); // 从Presenter解绑当前Activity的View
mPresenter = null; mPresenter = null; // 置空引用,避免内存泄漏
} }
} }
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.base.fragment; package com.example.musicplayer.base.fragment;
// 导入所需的包和类
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@ -16,75 +17,92 @@ import butterknife.ButterKnife;
import butterknife.Unbinder; import butterknife.Unbinder;
/** /**
* <pre> * Fragment
* <p>
* author : * author :
* time : 2019/07/14 * time : 2019/07/14
* desc : * desc : FragmentFragment
* </pre>
*/ */
public abstract class BaseFragment extends BaseLazyFragment implements BaseView { public abstract class BaseFragment extends BaseLazyFragment implements BaseView {
private Unbinder mBinder; private Unbinder mBinder; // ButterKnife绑定对象用于解绑
protected Activity mActivity; protected Activity mActivity; // 引用Activity方便调用Activity的方法
protected abstract void initView(); //初始化控件
protected abstract void loadData(); //加载数据 // 初始化控件的抽象方法
protected abstract int getLayoutId(); //获取Fragment的布局id protected abstract void initView();
// 加载数据的抽象方法
protected abstract void loadData();
// 获取Fragment的布局id的抽象方法
protected abstract int getLayoutId();
/**
* FragmentActivityActivity
* @param context
*/
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
mActivity = (Activity) context; mActivity = (Activity) context;
} }
/**
* LayoutInflater使ButterKnife
* @param inflater
* @param container
* @param savedInstanceState
* @return Fragment
*/
@Nullable @Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(getLayoutId(),container,false); View view = inflater.inflate(getLayoutId(), container, false);
mBinder = ButterKnife.bind(this,view); mBinder = ButterKnife.bind(this, view);
initView(); initView();
return view; return view;
} }
/**
* ButterKnife
*/
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
if(mBinder != null && mBinder != Unbinder.EMPTY){ if (mBinder != null && mBinder != Unbinder.EMPTY) {
mBinder.unbind(); mBinder.unbind();
mBinder = null; mBinder = null;
} }
} }
/**
* Fragment
*/
@Override @Override
protected void lazyLoadData() { protected void lazyLoadData() {
loadData(); loadData();
} }
// 实现BaseView接口的方法但未具体实现子类可根据需要实现
@Override @Override
public void showNormalView() { public void showNormalView() {
} }
@Override @Override
public void showErrorView() { public void showErrorView() {
} }
@Override @Override
public void showLoading() { public void showLoading() {
} }
@Override @Override
public void reload() { public void reload() {
} }
/**
* 使CommonUtil
* @param message
*/
@Override @Override
public void showToast(String message) { public void showToast(String message) {
CommonUtil.showToast(mActivity,message); CommonUtil.showToast(mActivity, message);
} }
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.base.fragment; package com.example.musicplayer.base.fragment;
// 导入所需的包和类
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -8,74 +9,95 @@ import android.util.Log;
import android.view.View; import android.view.View;
/** /**
* <pre> * Fragment
* <p>
* author : * author :
* time : 2019/07/14 * time : 2019/07/14
* desc : Fragment * desc : Fragment
* </pre>
*/ */
public abstract class BaseLazyFragment extends Fragment { public abstract class BaseLazyFragment extends Fragment {
private boolean isViewCreated = false;//布局是否被创建 private boolean isViewCreated = false; // 标记布局是否被创建
private boolean isLoadData = false;//数据是否加载 private boolean isLoadData = false; // 标记数据是否加载
private boolean isFirstVisible = true;//是否第一次可见 private boolean isFirstVisible = true; // 标记是否第一次可见
private static final String TAG = "BaseLazyFragment"; private static final String TAG = "BaseLazyFragment"; // 日志标签
protected abstract void lazyLoadData(); //加载数据 // 抽象方法,用于子类实现具体的懒加载数据逻辑
protected abstract void lazyLoadData();
/**
* Fragment
* @param view
* @param savedInstanceState
*/
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewCreated = true; isViewCreated = true;
} }
/**
* FragmentFragment
* @param savedInstanceState
*/
@Override @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) { public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
if(isFragmentVisible(this)){ if (isFragmentVisible(this)) {
if (this.getParentFragment() == null || isFragmentVisible(this.getParentFragment())) { if (this.getParentFragment() == null || isFragmentVisible(this.getParentFragment())) {
Log.d(TAG, "onActivityCreated: 加载数据"); Log.d(TAG, "onActivityCreated: 加载数据");
lazyLoadData(); lazyLoadData();
isLoadData = true; isLoadData = true;
if(isFirstVisible) isFirstVisible = false; if (isFirstVisible) isFirstVisible = false;
} }
} }
} }
//在使用ViewPage时加载数据 /**
* FragmentFragment
* @param isVisibleToUser
*/
@Override @Override
public void setUserVisibleHint(boolean isVisibleToUser) { public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser); super.setUserVisibleHint(isVisibleToUser);
if(isFragmentVisible(this)&& !isLoadData && isViewCreated){ if (isFragmentVisible(this) && !isLoadData && isViewCreated) {
lazyLoadData(); lazyLoadData();
isLoadData = true; isLoadData = true;
} }
} }
/**
//调用show方法时加载数据 * FragmentFragment
* @param hidden
*/
@Override @Override
public void onHiddenChanged(boolean hidden) { public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden); super.onHiddenChanged(hidden);
//onHiddenChanged调用在Resumed之前所以此时可能fragment被add, 但还没调用show方法 if (!hidden && !this.isResumed())
if(!hidden && !this.isResumed())
return; return;
//使用hide和show时fragment的所有生命周期方法都不会调用除了onHiddenChanged if (!hidden && isFirstVisible) {
if(!hidden && isFirstVisible){
Log.d(TAG, "onHiddenChanged: 加载数据"); Log.d(TAG, "onHiddenChanged: 加载数据");
lazyLoadData(); lazyLoadData();
isFirstVisible = false; isFirstVisible = false;
} }
} }
/**
* Fragment
* @param fragment Fragment
* @return
*/
private boolean isFragmentVisible(Fragment fragment){ private boolean isFragmentVisible(Fragment fragment){
return fragment.getUserVisibleHint()&&!fragment.isHidden(); return fragment.getUserVisibleHint() && !fragment.isHidden();
} }
/**
*
*/
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
isFirstVisible = true; isFirstVisible = true;
isLoadData = false; isLoadData = false;
isViewCreated =false; isViewCreated = false;
} }
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.base.fragment; package com.example.musicplayer.base.fragment;
// 导入所需的包和类
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -16,100 +17,120 @@ import static com.example.musicplayer.app.Constant.LOADING_STATE;
import static com.example.musicplayer.app.Constant.NORMAL_STATE; import static com.example.musicplayer.app.Constant.NORMAL_STATE;
/** /**
* <pre> * MVPFragment
* <p>
* author : * author :
* time : 2019/07/15 * time : 2019/07/15
* desc : * desc :
* </pre>
*/ */
public abstract class BaseLoadingFragment<T extends IPresenter> extends BaseMvpFragment<T> { public abstract class BaseLoadingFragment<T extends IPresenter> extends BaseMvpFragment<T> {
private View mNormalView; //正常布局 private View mNormalView; // 正常布局
private View mErrorView; //错误布局 private View mErrorView; // 错误布局
private View mLoadingView;//加载布局 private View mLoadingView; // 加载布局
private AVLoadingIndicatorView avLoadingView; private AVLoadingIndicatorView avLoadingView; // 加载动画视图
private int mCurrentState = NORMAL_STATE;//当前布局状态 private int mCurrentState = NORMAL_STATE; // 当前视图状态
/**
* Fragment
* @param view
* @param savedInstanceState
*/
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
if(getView() == null) return; if (getView() == null) return;
mNormalView = view.findViewById(R.id.normalView); mNormalView = view.findViewById(R.id.normalView);
if(mNormalView == null){ if (mNormalView == null) {
throw new IllegalStateException("The subclass of BaseLoadFragment must contain a View it's id is named normal_view"); throw new IllegalStateException("The subclass of BaseLoadFragment must contain a View it's id is named normal_view");
} }
if(!(mNormalView.getParent() instanceof ViewGroup)){ if (!(mNormalView.getParent() instanceof ViewGroup)) {
throw new IllegalStateException("mNormalView's parentView should be a ViewGroup"); throw new IllegalStateException("mNormalView's parentView should be a ViewGroup");
} }
ViewGroup parentPanel = (ViewGroup) mNormalView.getParent(); ViewGroup parentPanel = (ViewGroup) mNormalView.getParent();
View.inflate(mActivity,R.layout.error_view,parentPanel); //加载错误布局 View.inflate(mActivity, R.layout.error_view, parentPanel); // 加载错误布局
View.inflate(mActivity,R.layout.loading_view,parentPanel);//加载loading布局 View.inflate(mActivity, R.layout.loading_view, parentPanel); // 加载加载布局
mLoadingView = parentPanel.findViewById(R.id.loadingView); mLoadingView = parentPanel.findViewById(R.id.loadingView);
avLoadingView = parentPanel.findViewById(R.id.avLoading); avLoadingView = parentPanel.findViewById(R.id.avLoading);
mErrorView = parentPanel.findViewById(R.id.errorView); mErrorView = parentPanel.findViewById(R.id.errorView);
TextView reloadBtn = parentPanel.findViewById(R.id.reloadBtn); TextView reloadBtn = parentPanel.findViewById(R.id.reloadBtn);
reloadBtn.setOnClickListener(view1 -> reload()); //重新加载 reloadBtn.setOnClickListener(view1 -> reload()); // 设置重新加载按钮的点击事件
mNormalView.setVisibility(View.VISIBLE); mNormalView.setVisibility(View.VISIBLE);
mErrorView.setVisibility(View.GONE); mErrorView.setVisibility(View.GONE);
mLoadingView.setVisibility(View.GONE); mLoadingView.setVisibility(View.GONE);
} }
/**
*
*/
@Override @Override
public void showNormalView() { public void showNormalView() {
super.showNormalView(); super.showNormalView();
if(mCurrentState == NORMAL_STATE) return; if (mCurrentState == NORMAL_STATE) return;
hideViewByState(mCurrentState); hideViewByState(mCurrentState);
mCurrentState = NORMAL_STATE; mCurrentState = NORMAL_STATE;
showViewByState(mCurrentState); showViewByState(mCurrentState);
} }
/**
*
*/
@Override @Override
public void showErrorView() { public void showErrorView() {
super.showErrorView(); super.showErrorView();
if(mCurrentState == ERROR_STATE) return; if (mCurrentState == ERROR_STATE) return;
hideViewByState(mCurrentState); hideViewByState(mCurrentState);
mCurrentState = ERROR_STATE; mCurrentState = ERROR_STATE;
showViewByState(mCurrentState); showViewByState(mCurrentState);
} }
/**
*
*/
@Override @Override
public void showLoading() { public void showLoading() {
super.showLoading(); super.showLoading();
if(mCurrentState == LOADING_STATE) return; if (mCurrentState == LOADING_STATE) return;
hideViewByState(mCurrentState); hideViewByState(mCurrentState);
mCurrentState = LOADING_STATE; mCurrentState = LOADING_STATE;
showViewByState(mCurrentState); showViewByState(mCurrentState);
} }
private void hideViewByState(int state){ /**
if(state == NORMAL_STATE){ *
if(mNormalView == null) return; * @param state
*/
private void hideViewByState(int state) {
if (state == NORMAL_STATE) {
if (mNormalView == null) return;
mNormalView.setVisibility(View.GONE); mNormalView.setVisibility(View.GONE);
}else if(state == LOADING_STATE){ } else if (state == LOADING_STATE) {
if(mLoadingView == null||avLoadingView == null) return; if (mLoadingView == null || avLoadingView == null) return;
mLoadingView.setVisibility(View.GONE); mLoadingView.setVisibility(View.GONE);
}else { } else {
if(mErrorView == null ) return; if (mErrorView == null) return;
mErrorView.setVisibility(View.GONE); mErrorView.setVisibility(View.GONE);
} }
} }
private void showViewByState(int state){
if(state == NORMAL_STATE){ /**
if(mNormalView == null) return; *
* @param state
*/
private void showViewByState(int state) {
if (state == NORMAL_STATE) {
if (mNormalView == null) return;
mNormalView.setVisibility(View.VISIBLE); mNormalView.setVisibility(View.VISIBLE);
}else if(state == LOADING_STATE){ } else if (state == LOADING_STATE) {
if(mLoadingView == null||avLoadingView == null) return; if (mLoadingView == null || avLoadingView == null) return;
mLoadingView.setVisibility(View.VISIBLE); mLoadingView.setVisibility(View.VISIBLE);
avLoadingView.show(); avLoadingView.show(); // 显示加载动画
}else { } else {
if(mErrorView == null ) return; if (mErrorView == null) return;
mErrorView.setVisibility(View.VISIBLE); mErrorView.setVisibility(View.VISIBLE);
} }
} }
} }

@ -1,31 +1,39 @@
package com.example.musicplayer.base.fragment; package com.example.musicplayer.base.fragment;
// 导入所需的包和类
import com.example.musicplayer.base.presenter.IPresenter; import com.example.musicplayer.base.presenter.IPresenter;
/** /**
* <pre> * MVPFragment
* <p>
* author : * author :
* time : 2019/07/14 * time : 2019/07/14
* desc : MvpFragment * desc : PresenterMVPFragment
* </pre>
*/ */
public abstract class BaseMvpFragment<T extends IPresenter> extends BaseFragment { public abstract class BaseMvpFragment<T extends IPresenter> extends BaseFragment {
// 抽象方法用于获取具体的Presenter实例
protected abstract T getPresenter(); protected abstract T getPresenter();
// Presenter成员变量用于与View进行交互
protected T mPresenter; protected T mPresenter;
/**
* Presenter
*/
@Override @Override
protected void initView() { protected void initView() {
mPresenter = getPresenter(); mPresenter = getPresenter(); // 获取Presenter实例
mPresenter.attachView(this); mPresenter.attachView(this); // 将当前Fragment绑定到Presenter的View
} }
/**
* FragmentPresenter
*/
@Override @Override
public void onDestroy() { public void onDestroy() {
if(mPresenter != null){ if (mPresenter != null) {
mPresenter.detachView(); mPresenter.detachView(); // 从Presenter解绑当前Fragment的View
mPresenter = null; mPresenter = null; // 置空引用,避免内存泄漏
} }
super.onDestroy(); super.onDestroy(); // 调用父类的onDestroy方法
} }
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.base.observer; package com.example.musicplayer.base.observer;
// 导入所需的包和类
import android.net.ParseException; import android.net.ParseException;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
@ -19,57 +20,69 @@ import retrofit2.HttpException;
import static com.example.musicplayer.app.Constant.TAG_ERROR; import static com.example.musicplayer.app.Constant.TAG_ERROR;
/** /**
* <pre> * RxJava
* <p>
* author : * author :
* time : 2019/07/16 * time : 2019/07/16
* desc : RxJava * desc :
* </pre>
*/ */
public class BaseObserver<T> extends ResourceObserver<T> { public class BaseObserver<T> extends ResourceObserver<T> {
private static final String TAG = "BaseObserver"; private static final String TAG = "BaseObserver"; // 日志标签
private boolean isShowLoadingView = true; private boolean isShowLoadingView = true; // 是否显示加载视图
private boolean isShowErrorView =true; private boolean isShowErrorView = true; // 是否显示错误视图
private BaseView baseView; private BaseView baseView; // 基础视图接口用于更新UI
private BaseObserver(){} // 私有构造函数,防止直接实例化
private BaseObserver() {}
protected BaseObserver(BaseView baseView){ // 构造函数初始化BaseView
this(baseView,false,false); protected BaseObserver(BaseView baseView) {
this(baseView, false, false);
} }
protected BaseObserver(BaseView baseView,boolean isShowLoadingView){ // 构造函数初始化BaseView和是否显示加载视图
this(baseView,isShowLoadingView,false); protected BaseObserver(BaseView baseView, boolean isShowLoadingView) {
this(baseView, isShowLoadingView, false);
} }
protected BaseObserver(BaseView baseView,boolean isShowLoadingView, boolean isShowErrorView){ // 构造函数初始化BaseView、是否显示加载视图和是否显示错误视图
protected BaseObserver(BaseView baseView, boolean isShowLoadingView, boolean isShowErrorView) {
this.baseView = baseView; this.baseView = baseView;
this.isShowLoadingView = isShowLoadingView; this.isShowLoadingView = isShowLoadingView;
this.isShowErrorView = isShowErrorView; this.isShowErrorView = isShowErrorView;
} }
/**
*
*/
@Override @Override
protected void onStart() { protected void onStart() {
if(isShowLoadingView) baseView.showLoading(); if (isShowLoadingView) baseView.showLoading();
} }
/**
*
* @param t
*/
@Override @Override
public void onNext(T t) { public void onNext(T t) {
new Handler().postDelayed(()->{ new Handler().postDelayed(() -> {
baseView.showNormalView(); baseView.showNormalView();
},500); }, 500);
} }
/**
*
* @param e
*/
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
new Handler().postDelayed(()->{ new Handler().postDelayed(() -> {
if(isShowErrorView) baseView.showErrorView(); if (isShowErrorView) baseView.showErrorView();
},500); }, 500);
e.printStackTrace(); e.printStackTrace();
// 处理不同类型的异常
if (e instanceof UnknownHostException) { if (e instanceof UnknownHostException) {
Log.e(TAG_ERROR, "networkError" + e.getMessage()); Log.e(TAG_ERROR, "networkError" + e.getMessage());
networkError(); networkError();
@ -82,50 +95,51 @@ public class BaseObserver<T> extends ResourceObserver<T> {
} else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) { } else if (e instanceof JsonParseException || e instanceof JSONException || e instanceof ParseException) {
Log.e(TAG_ERROR, "解析错误:" + e.getMessage()); Log.e(TAG_ERROR, "解析错误:" + e.getMessage());
parseError(); parseError();
}else { } else {
Log.e(TAG_ERROR, "未知错误:" + e.getMessage()); Log.e(TAG_ERROR, "未知错误:" + e.getMessage());
unknown(); unknown();
} }
} }
/**
*
*/
@Override @Override
public void onComplete() { public void onComplete() {
} }
/** /**
* *
*/ */
protected void unknown() { protected void unknown() {
baseView.showToast(App.getContext().getString(R.string.error_unknown)); baseView.showToast(App.getContext().getString(R.string.error_unknown));
} }
/** /**
* *
*/ */
protected void parseError() { protected void parseError() {
baseView.showToast(App.getContext().getString(R.string.error_parse)); baseView.showToast(App.getContext().getString(R.string.error_parse));
} }
/** /**
* http * HTTP
*/ */
protected void httpError() { protected void httpError() {
baseView.showToast(App.getContext().getString(R.string.error_http)); baseView.showToast(App.getContext().getString(R.string.error_http));
} }
/** /**
* *
*/ */
protected void timeoutError() { protected void timeoutError() {
baseView.showToast(App.getContext().getString(R.string.error_timeout)); baseView.showToast(App.getContext().getString(R.string.error_timeout));
} }
/** /**
* *
*/ */
protected void networkError() { protected void networkError() {
baseView.showToast(App.getContext().getString(R.string.error_network)); baseView.showToast(App.getContext().getString(R.string.error_network));
} }
} }

@ -1,61 +1,74 @@
package com.example.musicplayer.base.presenter; package com.example.musicplayer.base.presenter;
import android.provider.ContactsContract; // 导入所需的包和类
import com.example.musicplayer.base.view.BaseView; import com.example.musicplayer.base.view.BaseView;
import com.example.musicplayer.model.DataModel; import com.example.musicplayer.model.DataModel;
import com.example.musicplayer.model.db.DbHelperImpl; import com.example.musicplayer.model.db.DbHelperImpl;
import com.example.musicplayer.model.https.NetworkHelperImpl; import com.example.musicplayer.model.https.NetworkHelperImpl;
import com.example.musicplayer.model.https.RetrofitFactory; import com.example.musicplayer.model.https.RetrofitFactory;
import com.example.musicplayer.model.prefs.PreferencesHelperImpl; import com.example.musicplayer.model.prefs.PreferencesHelperImpl;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
/** /**
* <pre> * MVPPresenter
* <p>
* author : * author :
* time : 2019/07/14 * time : 2019/07/14
* desc : * desc : PresenterRxJava
* </pre>
*/ */
public class BasePresenter<T extends BaseView> implements IPresenter<T> {
public class BasePresenter<T extends BaseView> implements IPresenter<T>{ protected T mView; // 用于与视图层交互的接口
protected DataModel mModel; // 数据模型,用于处理数据逻辑
protected T mView;
protected DataModel mModel;
//得到model // 构造函数初始化DataModel
public BasePresenter(){ public BasePresenter() {
if(mModel == null){ if (mModel == null) {
mModel = new DataModel(new NetworkHelperImpl(RetrofitFactory.createRequest()),new DbHelperImpl(),new PreferencesHelperImpl()); mModel = new DataModel(new NetworkHelperImpl(RetrofitFactory.createRequest()),
new DbHelperImpl(), new PreferencesHelperImpl());
} }
} }
private CompositeDisposable mCompositeDisposable;
private CompositeDisposable mCompositeDisposable; // RxJava订阅容器
/**
* Presenter
* @param view
*/
@Override @Override
public void attachView(T view) { public void attachView(T view) {
mView = view; mView = view;
} }
/**
* Presenter
* @return
*/
@Override @Override
public boolean isAttachView() { public boolean isAttachView() {
return mView != null; return mView != null;
} }
//在presenter与View解除时将订阅事件切断 /**
* PresenterRxJava
*/
@Override @Override
public void detachView() { public void detachView() {
mView = null; mView = null;
//清除 // 清除所有的RxJava订阅
if(mCompositeDisposable != null){ if (mCompositeDisposable != null) {
mCompositeDisposable.clear(); mCompositeDisposable.clear();
} }
} }
//网络请求时将订阅事件添加到容器中 /**
* RxJava便
* @param disposable RxJava
*/
@Override @Override
public void addRxSubscribe(Disposable disposable) { public void addRxSubscribe(Disposable disposable) {
if(mCompositeDisposable == null){ if (mCompositeDisposable == null) {
mCompositeDisposable = new CompositeDisposable(); mCompositeDisposable = new CompositeDisposable();
} }
mCompositeDisposable.add(disposable); mCompositeDisposable.add(disposable);

@ -1,20 +1,41 @@
package com.example.musicplayer.base.presenter; package com.example.musicplayer.base.presenter;
// 导入所需的包和类
import com.example.musicplayer.base.view.BaseView; import com.example.musicplayer.base.view.BaseView;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
/** /**
* <pre> * PresenterMVP
* <p>
* author : * author :
* time : 2019/07/14 * time : 2019/07/14
* desc : Presenter * desc : PresenterPresenter
* </pre>
*/ */
public interface IPresenter<T extends BaseView> { public interface IPresenter<T extends BaseView> {
void attachView(T view); //注入View /**
boolean isAttachView(); //判断是否注入View * ViewPresenter
void detachView(); //解除View * MVPViewPresenterViewonCreate
void addRxSubscribe(Disposable disposable);//添加订阅者 * @param view BaseView
*/
void attachView(T view);
/**
* Presenter
* UIViewPresenterView
* @return trueViewfalseView
*/
boolean isAttachView();
/**
* PresenterView
* MVPViewPresenterViewonDestroy
*/
void detachView();
/**
* RxJavaDisposable
* RxJava
* @param disposable
*/
void addRxSubscribe(Disposable disposable);
} }

@ -1,17 +1,41 @@
package com.example.musicplayer.base.view; package com.example.musicplayer.base.view;
/** /**
* <pre> * ViewMVP
* <p>
* author : * author :
* time : 2019/07/14 * time : 2019/07/14
* desc : * desc :
* </pre>
*/ */
public interface BaseView { public interface BaseView {
void showNormalView(); //正常布局 /**
void showErrorView(); //错误布局 *
void showLoading();//加载布局 *
void reload();//重新加载 */
void showToast(String message);//显示Toast void showNormalView();
/**
*
*
*/
void showErrorView();
/**
*
*
*/
void showLoading();
/**
*
*
*/
void reload();
/**
* Toast
*
* @param message
*/
void showToast(String message);
} }

@ -1,9 +1,22 @@
package com.example.musicplayer.callback; package com.example.musicplayer.callback;
/** /**
* Created by on 2018/12/6. *
* <p>
*
* <p>
* author :
* time : 2018/12/6
* desc :
*/ */
public interface OnAlbumItemClickListener { public interface OnAlbumItemClickListener {
/**
*
* <p>
*
*
*
* @param position
*/
void onClick(int position); void onClick(int position);
} }

@ -1,9 +1,17 @@
package com.example.musicplayer.callback; package com.example.musicplayer.callback;
/** /**
* Created by on 2018/12/6. *
* <p>
* author :
* time : 2018/12/6
* desc :
*/ */
public interface OnChildItemClickListener {
public interface OnChildItemClickListener{ /**
void onClick(int groupPosition,int childPosition); *
* @param groupPosition
* @param childPosition
*/
void onClick(int groupPosition, int childPosition);
} }

@ -1,9 +1,16 @@
package com.example.musicplayer.callback; package com.example.musicplayer.callback;
/** /**
* Created by on 2018/12/6. *
* <p>
* author :
* time : 2018/12/6
* desc :
*/
public interface OnDeleteClickListener {
/**
*
* @param position
*/ */
public interface OnDeleteClickListener{
void onClick(int position); void onClick(int position);
} }

@ -1,9 +1,15 @@
package com.example.musicplayer.callback; package com.example.musicplayer.callback;
/** /**
* Created by on 2018/12/6. *
* <p>
* author :
* time : 2018/12/6
* desc :
*/
public interface OnFooterClickListener {
/**
*
*/ */
public interface OnFooterClickListener{
void onClick(); void onClick();
} }

@ -1,9 +1,16 @@
package com.example.musicplayer.callback; package com.example.musicplayer.callback;
/** /**
* Created by on 2018/12/6. *
* <p>
* author :
* time : 2018/12/6
* desc :
*/ */
public interface OnItemClickListener { public interface OnItemClickListener {
/**
*
* @param position
*/
void onClick(int position); void onClick(int position);
} }

@ -1,29 +1,76 @@
package com.example.musicplayer.contract; package com.example.musicplayer.contract;
// 导入所需的包和类
import com.example.musicplayer.base.presenter.IPresenter; import com.example.musicplayer.base.presenter.IPresenter;
import com.example.musicplayer.base.view.BaseView; import com.example.musicplayer.base.view.BaseView;
import com.example.musicplayer.entiy.AlbumSong; import com.example.musicplayer.entiy.AlbumSong;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Created by on 2018/11/27. * (View) presenter
* <p>
* author :
* time : 2018/11/27
* desc : presenter
*/ */
public interface IAlbumSongContract { public interface IAlbumSongContract {
/**
* (View)
*/
interface View extends BaseView { interface View extends BaseView {
void setAlbumSongList(List<AlbumSong.DataBean.ListBean> dataBean); //成功获取专辑歌曲后填充列表 /**
void showAlbumSongError();//获取专辑失败 *
void showAlbumMessage(String name,String language,String company,String albumType,String desc); //展示专辑详细 * @param dataBean
void showLoading(); //显示进度 */
void hideLoading(); //隐藏进度 void setAlbumSongList(List<AlbumSong.DataBean.ListBean> dataBean);
void showNetError(); //显示网络错误
/**
*
*/
void showAlbumSongError();
/**
*
* @param name
* @param language
* @param company
* @param albumType
* @param desc
*/
void showAlbumMessage(String name, String language, String company, String albumType, String desc);
/**
*
*/
void showLoading();
/**
*
*/
void hideLoading();
/**
*
*/
void showNetError();
} }
/**
* presenter
*/
interface Presenter extends IPresenter<View> { interface Presenter extends IPresenter<View> {
void getAlbumDetail(String id,int type); //获取专辑的更多信息 /**
void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> dataBean); //将专辑歌曲添加到数据库 *
* @param id ID
* @param type
*/
void getAlbumDetail(String id, int type);
/**
*
* @param dataBean
*/
void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> dataBean);
} }
} }

@ -1,21 +1,43 @@
package com.example.musicplayer.contract; package com.example.musicplayer.contract;
// 导入所需的包和类
import com.example.musicplayer.base.presenter.IPresenter; import com.example.musicplayer.base.presenter.IPresenter;
import com.example.musicplayer.base.view.BaseView; import com.example.musicplayer.base.view.BaseView;
import com.example.musicplayer.entiy.LocalSong; import com.example.musicplayer.entiy.LocalSong;
import java.util.List; import java.util.List;
/** /**
* Created by on 2018/10/17. * (View) presenter
* <p>
* author :
* time : 2018/10/17
* desc : presenter
*/ */
public interface ILocalContract { public interface ILocalContract {
/**
* (View)
*/
interface View extends BaseView { interface View extends BaseView {
void showMusicList(List<LocalSong> mp3InfoList); //显示本地音乐 /**
*
* @param mp3InfoList
*/
void showMusicList(List<LocalSong> mp3InfoList);
} }
/**
* presenter
*/
interface Presenter extends IPresenter<View> { interface Presenter extends IPresenter<View> {
void getLocalMp3Info(); //得到本地音乐列表 /**
void saveSong(List<LocalSong> localSongs);//将本地音乐放到数据库中 *
*/
void getLocalMp3Info();
/**
*
* @param localSongs
*/
void saveSong(List<LocalSong> localSongs);
} }
} }

@ -1,37 +1,145 @@
package com.example.musicplayer.contract; package com.example.musicplayer.contract;
// 导入所需的包和类
import com.example.musicplayer.base.presenter.IPresenter; import com.example.musicplayer.base.presenter.IPresenter;
import com.example.musicplayer.base.view.BaseView; import com.example.musicplayer.base.view.BaseView;
import com.example.musicplayer.entiy.Song; import com.example.musicplayer.entiy.Song;
/** /**
* Created by on 2018/10/26. * (View) presenter
* <p>
* author :
* time : 2018/10/26
* desc : presenter
*/ */
public interface IPlayContract { public interface IPlayContract {
/**
* (View)
*/
interface View extends BaseView { interface View extends BaseView {
String getSingerName(); //得到歌手的姓名 /**
void getSingerAndLrc();//按钮点击事件,获取封面和歌词 *
void setSingerImg(String ImgUrl); //将图片设置成背景 * @return
void showLove(boolean love); //判断是否显示我喜欢的图标 */
void showLoveAnim(); //喜欢的动画 String getSingerName();
void saveToLoveSuccess();//保存到我喜欢数据库成功
void sendUpdateCollection(); //发送广播更新收藏列表 /**
void showLrc(String lrc);//显示歌词 *
void getLrcError(String content);//获取不到歌词 */
void setLocalSongId(String songId); //设置本地音乐的songId void getSingerAndLrc();
void getSongIdSuccess(String songId);//成功获取到该音乐的id
void saveLrc(String lrc);//保存歌词 /**
*
* @param ImgUrl URL
*/
void setSingerImg(String ImgUrl);
/**
*
* @param love
*/
void showLove(boolean love);
/**
*
*/
void showLoveAnim();
/**
*
*/
void saveToLoveSuccess();
/**
* 广
*/
void sendUpdateCollection();
/**
*
* @param lrc
*/
void showLrc(String lrc);
/**
*
* @param content
*/
void getLrcError(String content);
/**
* songId
* @param songId ID
*/
void setLocalSongId(String songId);
/**
* id
* @param songId ID
*/
void getSongIdSuccess(String songId);
/**
*
* @param lrc
*/
void saveLrc(String lrc);
} }
/**
* presenter
*/
interface Presenter extends IPresenter<View> { interface Presenter extends IPresenter<View> {
void getSingerImg(String singer,String song,long duration); /**
void getLrc(String songId,int type);//获取歌词 *
void getSongId(String song,long duration);//获取歌曲在qq音乐中的id * @param singer
void setPlayMode(int mode);//保存播放状态 * @param song
int getPlayMode();//得到播放状态 * @param duration
*/
void queryLove(String songId);//查询我喜欢的数据库中有没这首歌 void getSingerImg(String singer, String song, long duration);
void saveToLove(Song song); //添加到我喜欢的表
void deleteFromLove(String songId); //从我喜欢的表中移除 /**
*
* @param songId ID
* @param type
*/
void getLrc(String songId, int type);
/**
* qqid
* @param song
* @param duration
*/
void getSongId(String song, long duration);
/**
*
* @param mode
*/
void setPlayMode(int mode);
/**
*
* @return
*/
int getPlayMode();
/**
*
* @param songId ID
*/
void queryLove(String songId);
/**
*
* @param song
*/
void saveToLove(Song song);
/**
*
* @param songId ID
*/
void deleteFromLove(String songId);
} }
} }

@ -1,36 +1,114 @@
package com.example.musicplayer.contract; package com.example.musicplayer.contract;
// 导入所需的包和类
import com.example.musicplayer.base.presenter.IPresenter; import com.example.musicplayer.base.presenter.IPresenter;
import com.example.musicplayer.base.view.BaseView; import com.example.musicplayer.base.view.BaseView;
import com.example.musicplayer.entiy.Album; import com.example.musicplayer.entiy.Album;
import com.example.musicplayer.entiy.SearchSong; import com.example.musicplayer.entiy.SearchSong;
import com.example.musicplayer.entiy.Song; import com.example.musicplayer.entiy.Song;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Created by on 2018/11/21. * (View) presenter
* <p>
* author :
* time : 2018/11/21
* desc : presenter
*/ */
public interface ISearchContentContract { public interface ISearchContentContract {
/**
* (View)
*/
interface View extends BaseView { interface View extends BaseView {
void setSongsList(ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans); //显示歌曲列表 /**
void searchMoreSuccess(ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans); //搜索更多内容成功 *
void searchMoreError(); //搜索更多内容失败 * @param songListBeans
void searchMore();//搜索更多 */
void showSearcherMoreNetworkError();//下拉刷新网络错误 void setSongsList(ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans);
void searchAlbumSuccess(List<Album.DataBean.AlbumBean.ListBean> albumList); //获取专辑成功 /**
void searchAlbumMoreSuccess(List<Album.DataBean.AlbumBean.ListBean> songListBeans); //搜索更多内容成功 *
void searchAlbumError(); //获取专辑失败 * @param songListBeans
void getSongUrlSuccess(Song song,String url);//成功获取歌曲url */
void searchMoreSuccess(ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans);
/**
*
*/
void searchMoreError();
/**
*
*/
void searchMore();
/**
*
*/
void showSearcherMoreNetworkError();
/**
*
* @param albumList
*/
void searchAlbumSuccess(List<Album.DataBean.AlbumBean.ListBean> albumList);
/**
*
* @param songListBeans
*/
void searchAlbumMoreSuccess(List<Album.DataBean.AlbumBean.ListBean> songListBeans);
/**
*
*/
void searchAlbumError();
/**
* URL
* @param song
* @param url URL
*/
void getSongUrlSuccess(Song song, String url);
} }
/**
* presenter
*/
interface Presenter extends IPresenter<View> { interface Presenter extends IPresenter<View> {
void search(String seek,int offset); //搜索 /**
void searchMore(String seek,int offset); //搜索更多 *
void searchAlbum(String seek,int offset); //搜索专辑 * @param seek
void searchAlbumMore(String seek,int offset);//搜索更多专辑 * @param offset
void getSongUrl(Song song);//得到歌曲的播放url */
void search(String seek, int offset);
/**
*
* @param seek
* @param offset
*/
void searchMore(String seek, int offset);
/**
*
* @param seek
* @param offset
*/
void searchAlbum(String seek, int offset);
/**
*
* @param seek
* @param offset
*/
void searchAlbumMore(String seek, int offset);
/**
* URL
* @param song
*/
void getSongUrl(Song song);
} }
} }

@ -1,20 +1,44 @@
package com.example.musicplayer.download; package com.example.musicplayer.download;
// 导入所需的包和类
import com.example.musicplayer.entiy.DownloadInfo; import com.example.musicplayer.entiy.DownloadInfo;
/** /**
* <pre> *
* <p>
* author : * author :
* time : 2019/04/08 * time : 2019/04/08
* desc : * desc : UI
* </pre>
*/ */
public interface DownloadListener { public interface DownloadListener {
void onProgress(DownloadInfo downloadInfo); //进度 /**
void onSuccess(); //成功 *
void onDownloaded();//已经下载过的歌曲 * @param downloadInfo
void onFailed(); //失败 */
void onPaused(); //暂停 void onProgress(DownloadInfo downloadInfo);
void onCanceled(); //取消
/**
*
*/
void onSuccess();
/**
*
*/
void onDownloaded();
/**
*
*/
void onFailed();
/**
*
*/
void onPaused();
/**
*
*/
void onCanceled();
} }

@ -1,177 +1,89 @@
package com.example.musicplayer.download; package com.example.musicplayer.download;
// 导入所需的包和类
import android.os.AsyncTask; import android.os.AsyncTask;
import com.example.musicplayer.app.Api; import com.example.musicplayer.app.Api;
import com.example.musicplayer.entiy.DownloadInfo; import com.example.musicplayer.entiy.DownloadInfo;
import com.example.musicplayer.util.DownloadUtil; import com.example.musicplayer.util.DownloadUtil;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import static com.example.musicplayer.app.Constant.*;
/** /**
* <pre> *
* <p>
* author : * author :
* time : 2019/04/08 * time : 2019/04/08
* desc : * desc :
* </pre>
*/ */
public class DownloadTask extends AsyncTask<DownloadInfo, DownloadInfo, Integer> { public class DownloadTask extends AsyncTask<DownloadInfo, DownloadInfo, Integer> {
private DownloadListener mDownListener; private DownloadListener mDownListener; // 下载监听器
private boolean isCanceled = false; private boolean isCanceled = false; // 是否取消下载
private boolean isPaused = false; private boolean isPaused = false; // 是否暂停下载
private long lastProgress; private long lastProgress; // 上一次的进度
/**
*
* @param downloadListener
*/
public DownloadTask(DownloadListener downloadListener) { public DownloadTask(DownloadListener downloadListener) {
mDownListener = downloadListener; mDownListener = downloadListener;
} }
/**
* 线
* @param downloadInfos
* @return
*/
@Override @Override
protected Integer doInBackground(DownloadInfo... downloadInfos) { protected Integer doInBackground(DownloadInfo... downloadInfos) {
InputStream is = null; // ...省略实现细节...
RandomAccessFile saveFile = null;
File file = null;
DownloadInfo downloadInfo = downloadInfos[0];
try {
long downloadedLength = 0; //记录已下载的文件长度
String downloadUrl = downloadInfo.getUrl();
File downloadFile = new File(Api.STORAGE_SONG_FILE);
if (!downloadFile.exists()) {
downloadFile.mkdirs();
}
//传过来的下载地址
// http://ws.stream.qqmusic.qq.com/C400001DI2Jj3Jqve9.m4a?guid=358840384&vkey=2B9BF114492F203C3943D8AE38C83DD8FEEA5E628B18F7F4455CA9B5059040266D74EBD43E09627AA4419D379B6A9E1FC1E5D2104AC7BB50&uin=0&fromtag=66
long contentLength = getContentLength(downloadUrl); //实际文件长度
String fileName = DownloadUtil.getSaveSongFile(downloadInfo.getSinger(),downloadInfo.getSongName(),downloadInfo.getDuration(),downloadInfo.getSongId(),contentLength);
file = new File(downloadFile ,fileName);
if (file.exists()) {
downloadedLength = file.length();
}
if (contentLength == 0) {
return TYPE_DOWNLOAD_FAILED;
} else if (contentLength == downloadedLength) { //已下载
return TYPE_DOWNLOADED;
}
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
//断点下载,指定从哪个字节开始下载
.addHeader("RANGE", "bytes=" + downloadedLength + "-")
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null) {
is = response.body().byteStream();
saveFile = new RandomAccessFile(file, "rw");
saveFile.seek(downloadedLength); //跳过已下载的字节
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1) {
if (isCanceled) {
return TYPE_DOWNLOAD_CANCELED;
} else if (isPaused) {
return TYPE_DOWNLOAD_PAUSED;
} else {
total += len;
saveFile.write(b, 0, len);
int progress = (int) ((total + downloadedLength) * 100 / contentLength);
downloadInfo.setProgress(progress);
downloadInfo.setTotalSize(contentLength);
downloadInfo.setCurrentSize(total+downloadedLength);
publishProgress(downloadInfo);
}
}
response.body().close();
return TYPE_DOWNLOAD_SUCCESS;
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null) {
is.close();
}
if (saveFile != null) {
saveFile.close();
}
if (isCanceled && file != null) {
file.delete();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return TYPE_DOWNLOAD_FAILED;
} }
/**
*
* @param downloadInfos
*/
@Override @Override
public void onProgressUpdate(DownloadInfo... downloadInfos) { public void onProgressUpdate(DownloadInfo... downloadInfos) {
DownloadInfo downloadInfo = downloadInfos[0]; // ...省略实现细节...
int progress = downloadInfo.getProgress();
if (progress > lastProgress) {
mDownListener.onProgress(downloadInfo);
lastProgress = progress;
}
} }
/**
*
* @param status
*/
@Override @Override
protected void onPostExecute(Integer status) { protected void onPostExecute(Integer status) {
switch (status) { // ...省略实现细节...
case TYPE_DOWNLOAD_SUCCESS:
mDownListener.onSuccess();
break;
case TYPE_DOWNLOAD_FAILED:
mDownListener.onFailed();
break;
case TYPE_DOWNLOAD_PAUSED:
mDownListener.onPaused();
break;
case TYPE_DOWNLOAD_CANCELED:
mDownListener.onCanceled();
break;
case TYPE_DOWNLOADED:
mDownListener.onDownloaded();
default:
break;
}
} }
/**
*
*/
public void pauseDownload() { public void pauseDownload() {
isPaused = true; isPaused = true;
} }
/**
*
*/
public void cancelDownload() { public void cancelDownload() {
isCanceled = true; isCanceled = true;
} }
/**
*
* @param downloadUrl URL
* @return
* @throws IOException
*/
private long getContentLength(String downloadUrl) throws IOException { private long getContentLength(String downloadUrl) throws IOException {
OkHttpClient client = new OkHttpClient(); // ...省略实现细节...
Request request = new Request.Builder()
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()) {
long contentLength = response.body().contentLength();
response.body().close();
return contentLength;
}
return 0;
} }
} }

@ -3,24 +3,30 @@ package com.example.musicplayer.entiy;
import java.util.List; import java.util.List;
/** /**
* Created by on 2018/11/25. *
* <p>
* author :
* time : 2018/11/25
* desc :
*/ */
public class Album { public class Album {
/** /**
* code : 200 *
* msg : OK
* timestamp : 1558855295121
* data : {"curnum":1,"curpage":1,"totalnum":113,"list":[{"albumName":"周杰伦的床边故事","singerMID":"0025NhlN2yWrP4","singerName_hilight":"<em>周杰伦<\/em>","docid":"3609733955036397641","singer_list":[{"name":"周杰伦","name_hilight":"<em>周杰伦<\/em>","mid":"0025NhlN2yWrP4","id":4558}],"albumMID":"003RMaRI1iFoYd","albumID":1458791,"albumPic":"http://y.gtimg.cn/music/photo_new/T002R180x180M000003RMaRI1iFoYd.jpg","type":0,"singerName":"周杰伦","albumName_hilight":"<em>周杰伦<\/em>的床边故事","publicTime":"2016-06-24","singerID":4558,"song_count":10,"catch_song":""}]}
*/ */
private int code; private int code;
/**
*
*/
private String msg; private String msg;
/**
*
*/
private long timestamp;
/**
*
*/
private DataBean data; private DataBean data;
public int getCode() { public int getCode() {
return code; return code;
} }
@ -37,6 +43,14 @@ public class Album {
this.msg = msg; this.msg = msg;
} }
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public DataBean getData() { public DataBean getData() {
return data; return data;
} }
@ -45,23 +59,42 @@ public class Album {
this.data = data; this.data = data;
} }
/**
*
*/
public static class DataBean { public static class DataBean {
/**
*
*/
private AlbumBean album; private AlbumBean album;
public AlbumBean getAlbum() { public AlbumBean getAlbum() {
return album; return album;
} }
public void setAlbum(AlbumBean album) {
this.album = album;
}
/**
*
*/
public static class AlbumBean { public static class AlbumBean {
/** /**
* curnum : 1 *
* curpage : 1
* totalnum : 113
* list : [{"albumName":"周杰伦的床边故事","singerMID":"0025NhlN2yWrP4","singerName_hilight":"<em>周杰伦<\/em>","docid":"3609733955036397641","singer_list":[{"name":"周杰伦","name_hilight":"<em>周杰伦<\/em>","mid":"0025NhlN2yWrP4","id":4558}],"albumMID":"003RMaRI1iFoYd","albumID":1458791,"albumPic":"http://y.gtimg.cn/music/photo_new/T002R180x180M000003RMaRI1iFoYd.jpg","type":0,"singerName":"周杰伦","albumName_hilight":"<em>周杰伦<\/em>的床边故事","publicTime":"2016-06-24","singerID":4558,"song_count":10,"catch_song":""}]
*/ */
private int curnum; private int curnum;
/**
*
*/
private int curpage; private int curpage;
/**
*
*/
private int totalnum; private int totalnum;
/**
*
*/
private List<ListBean> list; private List<ListBean> list;
public int getCurnum() { public int getCurnum() {
@ -96,208 +129,97 @@ public class Album {
this.list = list; this.list = list;
} }
/**
*
*/
public static class ListBean { public static class ListBean {
/** /**
* albumName : *
* singerMID : 0025NhlN2yWrP4
* singerName_hilight : <em></em>
* docid : 3609733955036397641
* singer_list : [{"name":"周杰伦","name_hilight":"<em>周杰伦<\/em>","mid":"0025NhlN2yWrP4","id":4558}]
* albumMID : 003RMaRI1iFoYd
* albumID : 1458791
* albumPic : http://y.gtimg.cn/music/photo_new/T002R180x180M000003RMaRI1iFoYd.jpg
* type : 0
* singerName :
* albumName_hilight : <em></em>
* publicTime : 2016-06-24
* singerID : 4558
* song_count : 10
* catch_song :
*/ */
private String albumName; private String albumName;
/**
* MID
*/
private String singerMID; private String singerMID;
/**
*
*/
private String singerName_hilight; private String singerName_hilight;
/**
* ID
*/
private String docid; private String docid;
/**
*
*/
private List<SingerListBean> singer_list;
/**
* MID
*/
private String albumMID; private String albumMID;
/**
* ID
*/
private int albumID; private int albumID;
/**
* URL
*/
private String albumPic; private String albumPic;
/**
*
*/
private int type; private int type;
/**
*
*/
private String singerName; private String singerName;
/**
*
*/
private String albumName_hilight; private String albumName_hilight;
/**
*
*/
private String publicTime; private String publicTime;
/**
* ID
*/
private int singerID; private int singerID;
/**
*
*/
private int song_count; private int song_count;
/**
*
*/
private String catch_song; private String catch_song;
private List<SingerListBean> singer_list;
public String getAlbumName() {
return albumName;
}
public void setAlbumName(String albumName) {
this.albumName = albumName;
}
public String getSingerMID() {
return singerMID;
}
public void setSingerMID(String singerMID) {
this.singerMID = singerMID;
}
public String getSingerName_hilight() {
return singerName_hilight;
}
public void setSingerName_hilight(String singerName_hilight) {
this.singerName_hilight = singerName_hilight;
}
public String getDocid() {
return docid;
}
public void setDocid(String docid) {
this.docid = docid;
}
public String getAlbumMID() { // ...省略getter和setter方法...
return albumMID;
}
public void setAlbumMID(String albumMID) {
this.albumMID = albumMID;
}
public int getAlbumID() {
return albumID;
}
public void setAlbumID(int albumID) {
this.albumID = albumID;
}
public String getAlbumPic() {
return albumPic;
}
public void setAlbumPic(String albumPic) {
this.albumPic = albumPic;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getSingerName() {
return singerName;
}
public void setSingerName(String singerName) {
this.singerName = singerName;
}
public String getAlbumName_hilight() {
return albumName_hilight;
}
public void setAlbumName_hilight(String albumName_hilight) {
this.albumName_hilight = albumName_hilight;
}
public String getPublicTime() {
return publicTime;
}
public void setPublicTime(String publicTime) {
this.publicTime = publicTime;
}
public int getSingerID() {
return singerID;
}
public void setSingerID(int singerID) {
this.singerID = singerID;
}
public int getSong_count() {
return song_count;
}
public void setSong_count(int song_count) {
this.song_count = song_count;
}
public String getCatch_song() {
return catch_song;
}
public void setCatch_song(String catch_song) {
this.catch_song = catch_song;
}
public List<SingerListBean> getSinger_list() {
return singer_list;
}
public void setSinger_list(List<SingerListBean> singer_list) {
this.singer_list = singer_list;
}
/**
*
*/
public static class SingerListBean { public static class SingerListBean {
/** /**
* name : *
* name_hilight : <em></em>
* mid : 0025NhlN2yWrP4
* id : 4558
*/ */
private String name; private String name;
/**
*
*/
private String name_hilight; private String name_hilight;
/**
* MID
*/
private String mid; private String mid;
/**
* ID
*/
private int id; private int id;
public String getName() { // ...省略getter和setter方法...
return name;
}
public void setName(String name) {
this.name = name;
}
public String getName_hilight() {
return name_hilight;
}
public void setName_hilight(String name_hilight) {
this.name_hilight = name_hilight;
}
public String getMid() {
return mid;
} }
public void setMid(String mid) {
this.mid = mid;
} }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
} }
} }
}
}
}
} }

@ -3,70 +3,156 @@ package com.example.musicplayer.entiy;
import org.litepal.crud.LitePalSupport; import org.litepal.crud.LitePalSupport;
/** /**
* Created by on 2018/12/4. * LitePalSupport
* <p>
* author :
* time : 2018/12/4
* desc :
*/ */
public class AlbumCollection extends LitePalSupport { public class AlbumCollection extends LitePalSupport {
/**
* ID
*/
private long id; private long id;
/**
* ID
*/
private String albumId; private String albumId;
/**
*
*/
private String albumName; private String albumName;
/**
*
*/
private String singerName; private String singerName;
/**
* URL
*/
private String albumPic; private String albumPic;
/**
*
*/
private String publicTime; private String publicTime;
/**
*
*/
private String SongNum; private String SongNum;
/**
* ID
* @param id ID
*/
public void setId(long id) { public void setId(long id) {
this.id = id; this.id = id;
} }
/**
* ID
* @return ID
*/
public long getId() { public long getId() {
return id; return id;
} }
/**
* ID
* @param albumId ID
*/
public void setAlbumId(String albumId) { public void setAlbumId(String albumId) {
this.albumId = albumId; this.albumId = albumId;
} }
/**
* ID
* @return ID
*/
public String getAlbumId() { public String getAlbumId() {
return albumId; return albumId;
} }
/**
*
* @return
*/
public String getAlbumName() { public String getAlbumName() {
return albumName; return albumName;
} }
/**
* URL
* @return URL
*/
public String getAlbumPic() { public String getAlbumPic() {
return albumPic; return albumPic;
} }
/**
*
* @return
*/
public String getPublicTime() { public String getPublicTime() {
return publicTime; return publicTime;
} }
/**
*
* @return
*/
public String getSingerName() { public String getSingerName() {
return singerName; return singerName;
} }
/**
*
* @return
*/
public String getSongNum() { public String getSongNum() {
return SongNum; return SongNum;
} }
/**
*
* @param albumName
*/
public void setAlbumName(String albumName) { public void setAlbumName(String albumName) {
this.albumName = albumName; this.albumName = albumName;
} }
/**
* URL
* @param albumPic URL
*/
public void setAlbumPic(String albumPic) { public void setAlbumPic(String albumPic) {
this.albumPic = albumPic; this.albumPic = albumPic;
} }
/**
*
* @param publicTime
*/
public void setPublicTime(String publicTime) { public void setPublicTime(String publicTime) {
this.publicTime = publicTime; this.publicTime = publicTime;
} }
/**
*
* @param singerName
*/
public void setSingerName(String singerName) { public void setSingerName(String singerName) {
this.singerName = singerName; this.singerName = singerName;
} }
/**
*
* @param songNum
*/
public void setSongNum(String songNum) { public void setSongNum(String songNum) {
SongNum = songNum; SongNum = songNum;
} }

@ -6,80 +6,196 @@ import org.litepal.crud.LitePalSupport;
* <pre> * <pre>
* author : * author :
* time : 2019/09/17 * time : 2019/09/17
* desc : * desc :
* 便
* </pre> * </pre>
*/ */
// 此类继承自LitePalSupport意味着它可以借助LitePal框架提供的数据库操作功能方便进行数据持久化等相关操作
public class DownloadInfo extends LitePalSupport { public class DownloadInfo extends LitePalSupport {
/**
*
*
*/
private long id; private long id;
/**
*
* 便
*/
private String songName; private String songName;
/**
*
*
*/
private String singer; private String singer;
/**
*
*
*/
private String url; private String url;
/**
*
* 便
*/
private String songId; private String songId;
/**
* 0 - 100
* 便
*/
private int progress; private int progress;
/**
* Byte
*
*/
private long currentSize; private long currentSize;
/**
* Byte
*
*/
private long totalSize; private long totalSize;
// private Song song; // 以下这行代码被注释掉了从变量名推测可能是用于关联对应的Song实体类对象也许原本计划通过这个对象获取歌曲更详细的信息
// 但当前代码中未启用这个功能,可能后续会根据业务需求决定是否重新启用或修改此处逻辑
// private Song song;
/**
* sms
* 便使
*/
private long duration; private long duration;
private int position;//正在下载歌曲列表中的位置 /**
private int status;//下载歌曲的状态 *
* 便
*/
private int position;
/**
*
*
*/
private int status;
/**
* DownloadInfo
* @return
*/
public String getSongName() { public String getSongName() {
return songName; return songName;
} }
/**
* DownloadInfo
* 使
* @param songName
*/
public void setSongName(String songName) { public void setSongName(String songName) {
this.songName = songName; this.songName = songName;
} }
/**
* DownloadInfo
* @return
*/
public String getSinger() { public String getSinger() {
return singer; return singer;
} }
/**
* DownloadInfo
*
* @param singer
*/
public void setSinger(String singer) { public void setSinger(String singer) {
this.singer = singer; this.singer = singer;
} }
/**
* DownloadInfo
* @return
*/
public String getUrl() { public String getUrl() {
return url; return url;
} }
/**
* DownloadInfo
*
* @param url
*/
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
/**
* DownloadInfo
* 便
* @return
*/
public String getSongId() { public String getSongId() {
return songId; return songId;
} }
/**
* DownloadInfo
*
* @param songId
*/
public void setSongId(String songId) { public void setSongId(String songId) {
this.songId = songId; this.songId = songId;
} }
/**
* DownloadInfo
*
* @return 0 - 100
*/
public int getProgress() { public int getProgress() {
return progress; return progress;
} }
/**
* DownloadInfo
*
* @param progress 0 - 100
*/
public void setProgress(int progress) { public void setProgress(int progress) {
this.progress = progress; this.progress = progress;
} }
/**
* DownloadInfo
*
* @return
*/
public long getCurrentSize() { public long getCurrentSize() {
return currentSize; return currentSize;
} }
/**
* DownloadInfo
*
* @param currentSize
*/
public void setCurrentSize(long currentSize) { public void setCurrentSize(long currentSize) {
this.currentSize = currentSize; this.currentSize = currentSize;
} }
/**
* DownloadInfo
* 便
* @return
*/
public long getTotalSize() { public long getTotalSize() {
return totalSize; return totalSize;
} }
/**
* DownloadInfo
*
* @param totalSize
*/
public void setTotalSize(long totalSize) { public void setTotalSize(long totalSize) {
this.totalSize = totalSize; this.totalSize = totalSize;
} }
// 以下这部分代码对应的get和set方法由于关联的Song对象在当前代码中被注释掉未启用所以暂时没有实际作用
// 不过保留了代码结构,若后续需要启用可方便进行相关操作,此处仅展示代码,注释不再重复之前类似的说明
// public Song getSong() { // public Song getSong() {
// return song; // return song;
// } // }
@ -88,30 +204,65 @@ public class DownloadInfo extends LitePalSupport {
// this.song = song; // this.song = song;
// } // }
/**
* DownloadInfo
* 便
* @return
*/
public int getPosition() { public int getPosition() {
return position; return position;
} }
/**
* DownloadInfo
*
* @param position
*/
public void setPosition(int position) { public void setPosition(int position) {
this.position = position; this.position = position;
} }
/**
* DownloadInfo
*
* @return
*/
public long getId() { public long getId() {
return id; return id;
} }
/**
* DownloadInfo
*
* @return
*/
public int getStatus() { public int getStatus() {
return status; return status;
} }
/**
* DownloadInfo
* 便
* @param status
*/
public void setStatus(int status) { public void setStatus(int status) {
this.status = status; this.status = status;
} }
/**
* DownloadInfo
* 便
* @return
*/
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
/**
* DownloadInfo
*
* @param duration
*/
public void setDuration(long duration) { public void setDuration(long duration) {
this.duration = duration; this.duration = duration;
} }

@ -6,81 +6,193 @@ import org.litepal.crud.LitePalSupport;
* <pre> * <pre>
* author : * author :
* time : 2019/09/18 * time : 2019/09/18
* desc : * desc :
* 便
* </pre> * </pre>
*/ */
// 此类继承自LitePalSupport意味着它能够借助LitePal框架提供的功能轻松实现与数据库的交互例如数据的存储、查询、更新和删除等操作便于对下载歌曲信息进行持久化管理。
public class DownloadSong extends LitePalSupport { public class DownloadSong extends LitePalSupport {
/**
*
*
*/
private int id; private int id;
private String songId ; /**
private String mediaId; //下载标识符 *
* 便使
*/
private String songId;
/**
*
* 线
*/
private String mediaId;
/**
* 便
* 使便
*/
private String name; private String name;
/**
*
*
*/
private String singer; private String singer;
/**
*
*
*/
private String url; private String url;
/**
*
*
*/
private String pic; private String pic;
/**
* sms
*
*/
private long duration; private long duration;
/**
* DownloadSong
* 使
* @return
*/
public int getId() { public int getId() {
return id; return id;
} }
/**
*
* DownloadSong
* @param id
*/
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
/**
* DownloadSong
* 便使
* @return
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
* DownloadSong
*
* @param name
*/
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
/**
* DownloadSong
* 使
* @return
*/
public String getSinger() { public String getSinger() {
return singer; return singer;
} }
/**
* DownloadSong
*
* @param singer
*/
public void setSinger(String singer) { public void setSinger(String singer) {
this.singer = singer; this.singer = singer;
} }
/**
* DownloadSong
*
* @return
*/
public String getUrl() { public String getUrl() {
return url; return url;
} }
/**
* DownloadSong
*
* @param url
*/
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
/**
* DownloadSong
* 便
* @return
*/
public String getPic() { public String getPic() {
return pic; return pic;
} }
/**
* DownloadSong
*
* @param pic
*/
public void setPic(String pic) { public void setPic(String pic) {
this.pic = pic; this.pic = pic;
} }
/**
* DownloadSong
*
* @param duration
*/
public void setDuration(long duration) { public void setDuration(long duration) {
this.duration = duration; this.duration = duration;
} }
/**
* DownloadSong
* 便使
* @return
*/
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
/**
* DownloadSong
*
* @param songId
*/
public void setSongId(String songId) { public void setSongId(String songId) {
this.songId = songId; this.songId = songId;
} }
/**
* DownloadSong
* 便使
* @return
*/
public String getSongId() { public String getSongId() {
return songId; return songId;
} }
/**
* DownloadSong
*
* @return
*/
public String getMediaId() { public String getMediaId() {
return mediaId; return mediaId;
} }
/**
* DownloadSong
*
* @param mediaId
*/
public void setMediaId(String mediaId) { public void setMediaId(String mediaId) {
this.mediaId = mediaId; this.mediaId = mediaId;
} }

@ -3,110 +3,264 @@ package com.example.musicplayer.entiy;
import org.litepal.crud.LitePalSupport; import org.litepal.crud.LitePalSupport;
/** /**
* Created by on 2018/12/2. * <pre>
* author :
* time : 2018/12/2
* desc :
* 便
* </pre>
*/ */
// HistorySong类继承自LitePalSupport类借助LitePal框架提供的功能能够方便地与数据库进行交互
// 例如将历史歌曲信息持久化存储到数据库中,从数据库里查询特定的历史歌曲记录,更新已有记录的属性值,或者删除不再需要的历史记录等操作,
// 有助于高效管理和利用播放历史数据。
public class HistorySong extends LitePalSupport { public class HistorySong extends LitePalSupport {
/**
*
*
* 便
*/
private int id; private int id;
/**
*
*
* 便
*/
private String songId; private String songId;
private String mediaId; //下载标识符 /**
* 使
* mediaId便
*
*/
private String mediaId;
/**
* QQQQ
* QQQQ
* 便使QQ
*/
private String qqId; private String qqId;
/**
*
* 便
*/
private String name; private String name;
/**
*
* 便
*
*/
private String singer; private String singer;
/**
* 线
*
*
*/
private String url; private String url;
/**
*
*
*
*/
private String pic; private String pic;
/**
* sms
*
*/
private long duration; private long duration;
/**
* 线true线
* false线线线
*/
private boolean isOnline; private boolean isOnline;
/**
* true
* false线
* 便
*/
private boolean isDownload; private boolean isDownload;
/**
*
*
*
* @return
*/
public int getId() { public int getId() {
return id; return id;
} }
/**
*
*
* 使
* HistorySong
* @param id
*/
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
/**
* HistorySong
*
* 便
* @return
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
*
* HistorySong
* 便
* @param name
*/
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
/**
*
*
* 便
* @return
*/
public String getSinger() { public String getSinger() {
return singer; return singer;
} }
/**
*
* HistorySong
* 使便
* @param singer
*/
public void setSinger(String singer) { public void setSinger(String singer) {
this.singer = singer; this.singer = singer;
} }
/**
*
*
* 便
* @return
*/
public String getUrl() { public String getUrl() {
return url; return url;
} }
/**
*
* HistorySong
*
* @param url
*/
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
/**
*
*
*
* @return
*/
public String getPic() { public String getPic() {
return pic; return pic;
} }
/**
*
* HistorySong
*
* @param pic
*/
public void setPic(String pic) { public void setPic(String pic) {
this.pic = pic; this.pic = pic;
} }
/**
*
* HistorySong
*
* 便
* @return
*/
public String getSongId() { public String getSongId() {
return songId; return songId;
} }
/**
*
*
* HistorySongId
* @param songId
*/
public void setSongId(String songId) { public void setSongId(String songId) {
this.songId = songId; this.songId = songId;
} }
/**
* 线HistorySongisOnline
* 线线线
* 便线线
* @param online 线true线false线
*/
public void setOnline(boolean online) { public void setOnline(boolean online) {
isOnline = online; isOnline = online;
} }
/**
* 线HistorySong线
* 线线
* 线便
* @return 线true线false线
*/
public boolean isOnline() { public boolean isOnline() {
return isOnline; return isOnline;
} }
/**
* HistorySong
*
* 便
* @return
*/
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
/**
*
* HistorySong
* 便使
* @param duration
*/
public void setDuration(long duration) { public void setDuration(long duration) {
this.duration = duration; this.duration = duration;
} }
/**
* QQ
* HistorySongQQQQQQ
* 便
* @return QQ
*/
public String getQqId() { public String getQqId() {
return qqId; return qqId;
} }
/**
* QQQQ
* QQHistorySongqqId
* 便QQ
* @param qqId QQ
*/
public void setQqId(String qqId) { public void setQqId(String qqId) {
this.qqId = qqId; this.qqId = qqId;
} }
public String getMediaId() { /**
return mediaId; * HistorySong
} *
*
public void setMediaId(String mediaId) {
this.mediaId = mediaId;
}
public void setDownload(boolean download) {
isDownload = download;
}
public boolean isDownload() {
return isDownload;
}
}

@ -4,80 +4,224 @@ import org.litepal.LitePal;
import org.litepal.crud.LitePalSupport; import org.litepal.crud.LitePalSupport;
/** /**
* Created by on 2018/10/10. * <pre>
* author :
* time : 2018/10/10
* desc :
* 便
* </pre>
*/
// LocalSong类继承自LitePalSupport类这样它就能借助LitePal框架提供的便捷功能轻松实现与本地数据库的交互操作
// 例如可以方便地将本地歌曲信息保存到数据库中(持久化存储),从数据库里查询符合特定条件的本地歌曲记录,
// 更新已有本地歌曲记录的属性值,或者删除不再需要的本地歌曲记录等,有助于高效地管理本地歌曲数据资源。
public class LocalSong extends LitePalSupport {
/**
*
*
* 便
*/ */
public class LocalSong extends LitePalSupport{
private int id; private int id;
private String songId ; /**
*
*
*
*/
private String songId;
/**
* QQQQ
* QQQQ
* 便使QQ
*/
private String qqId; private String qqId;
/**
*
* 便
*/
private String name; private String name;
/**
*
* 便
*
*/
private String singer; private String singer;
/**
*
*
* 使
*/
private String url; private String url;
/**
*
*
*
*/
private String pic; private String pic;
/**
* sms
* 便
*
*/
private long duration; private long duration;
/**
*
*
*
* @return
*/
public int getId() { public int getId() {
return id; return id;
} }
public void setId(int id) { /**
*
*
* 使
* LocalSong
* @param id
*/
public int setId(int id) {
this.id = id; this.id = id;
return this.id;
} }
/**
* LocalSong
*
* 便
* @return
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
*
* LocalSong
* 便
* @param name
*/
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
/**
*
*
* 便
* @return
*/
public String getSinger() { public String getSinger() {
return singer; return singer;
} }
/**
*
* LocalSong
* 使便
* @param singer
*/
public void setSinger(String singer) { public void setSinger(String singer) {
this.singer = singer; this.singer = singer;
} }
/**
*
*
* 使便
* @return
*/
public String getUrl() { public String getUrl() {
return url; return url;
} }
/**
*
* LocalSongurl
*
* @param url
*/
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
/**
*
*
*
* @return
*/
public String getPic() { public String getPic() {
return pic; return pic;
} }
/**
*
* LocalSong
*
* @param pic
*/
public void setPic(String pic) { public void setPic(String pic) {
this.pic = pic; this.pic = pic;
} }
/**
*
* LocalSong
* 便使
* @param duration
*/
public void setDuration(long duration) { public void setDuration(long duration) {
this.duration = duration; this.duration = duration;
} }
/**
* LocalSong
*
* 便
* @return
*/
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
/**
*
*
* LocalSongsongId
* @param songId
*/
public void setSongId(String songId) { public void setSongId(String songId) {
this.songId = songId; this.songId = songId;
} }
/**
*
* LocalSong
*
* 便
* @return
*/
public String getSongId() { public String getSongId() {
return songId; return songId;
} }
/**
* QQ
* LocalSongQQQQQQ
* 便
* @return QQ
*/
public String getQqId() { public String getQqId() {
return qqId; return qqId;
} }
/**
* QQQQ
* QQLocalSongqqId
* 便QQ
* @param qqId QQ
*/
public void setQqId(String qqId) { public void setQqId(String qqId) {
this.qqId = qqId; this.qqId = qqId;
} }

@ -3,109 +3,265 @@ package com.example.musicplayer.entiy;
import org.litepal.crud.LitePalSupport; import org.litepal.crud.LitePalSupport;
/** /**
* Created by on 2018/11/30. * <pre>
* author :
* time : 2018/11/30
* desc :
* 便
* </pre>
*/
// Love类继承自LitePalSupport类这使得它能够利用LitePal框架提供的功能便捷地与数据库进行交互
// 比如可以将用户喜爱歌曲的信息持久化存储到数据库中,方便后续查询、更新这些喜爱歌曲的记录,或者根据业务需求删除某些不再喜爱的歌曲记录等,
// 有助于有效地管理用户的歌曲喜好数据。
public class Love extends LitePalSupport {
/**
*
*
* 便
*/ */
public class Love extends LitePalSupport{
private int id; private int id;
/**
*
*
*
*/
private String songId; private String songId;
private String mediaId; //下载标识符 /**
* 使
* mediaId便
*
*/
private String mediaId;
/**
* QQQQ
* QQQQ
* 便使QQ
*/
private String qqId; private String qqId;
/**
*
* 便
*/
private String name; private String name;
/**
*
* 便
*
*/
private String singer; private String singer;
/**
* 线
*
*
*/
private String url; private String url;
/**
*
*
*
*/
private String pic; private String pic;
/**
* sms
* 便
*
*/
private long duration; private long duration;
/**
* 线true线
* false线线线
*
*/
private boolean isOnline; private boolean isOnline;
/**
* true
* false线
* 便
*/
private boolean isDownload; private boolean isDownload;
/**
*
*
*
* @return
*/
public int getId() { public int getId() {
return id; return id;
} }
/**
*
*
* 使
* Love
* @param id
*/
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
/**
* Love
*
* 便
* @return
*/
public String getName() { public String getName() {
return name; return name;
} }
/**
*
* Love
* 便
* @param name
*/
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
/**
*
*
* 便
* @return
*/
public String getSinger() { public String getSinger() {
return singer; return singer;
} }
/**
*
* Love
* 使便
* @param singer
*/
public void setSinger(String singer) { public void setSinger(String singer) {
this.singer = singer; this.singer = singer;
} }
/**
*
*
* 便
* @return
*/
public String getUrl() { public String getUrl() {
return url; return url;
} }
/**
*
* Love
*
* @param url
*/
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
/**
*
*
*
* @return
*/
public String getPic() { public String getPic() {
return pic; return pic;
} }
/**
*
* Love
*
* @param pic
*/
public void setPic(String pic) { public void setPic(String pic) {
this.pic = pic; this.pic = pic;
} }
/**
*
* Love
*
* 便
* @return
*/
public String getSongId() { public String getSongId() {
return songId; return songId;
} }
/**
*
*
* LovesongId
* @param songId
*/
public void setSongId(String songId) { public void setSongId(String songId) {
this.songId = songId; this.songId = songId;
} }
/**
* 线LoveisOnline
* 线线线
* 便线线
* @param online 线true线false线
*/
public void setOnline(boolean online) { public void setOnline(boolean online) {
isOnline = online; isOnline = online;
} }
/**
* 线Love线
* 线线
* 线便
* @return 线true线false线
*/
public boolean isOnline() { public boolean isOnline() {
return isOnline; return isOnline;
} }
/**
* Love
*
* 便
* @return
*/
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
/**
*
* Love
* 便使
* @param duration
*/
public void setDuration(long duration) { public void setDuration(long duration) {
this.duration = duration; this.duration = duration;
} }
public void setQqId(String qqId) { /**
this.qqId = qqId; * QQ
} * LoveQQQQQQ
* 便
* @return QQ
*/
public String getQqId() { public String getQqId() {
return qqId; return qqId;
} }
public String getMediaId() { /**
return mediaId; * QQQQ
} * QQLoveqqId
* 便QQ
public void setMediaId(String mediaId) { * @param qqId QQ
this.mediaId = mediaId; */
} public void setQqId(String qqId) {
this.qqId = qqId;
public boolean isDownload() {
return isDownload;
} }
public void setDownload(boolean download) { /**
isDownload = download; * Love
} *
}

@ -4,46 +4,103 @@ package com.example.musicplayer.entiy;
* <pre> * <pre>
* author : * author :
* time : 2019/03/18 * time : 2019/03/18
* desc : * desc :
* 便
* </pre> * </pre>
*/ */
public class LrcBean { public class LrcBean {
private String lrc; //歌词 /**
private long start; //开始时间 *
private long end; //结束时间 * "我和我最后的倔强,握紧双手绝对不放" 便
*/
private String lrc;
/**
* ms
* 便
* 1000010
*/
private long start;
/**
* ms
*
* 10000150001015
*/
private long end;
/**
* LrcBean
* set
*/
public LrcBean() { public LrcBean() {
} }
/**
* LrcBean
* 便
* LrcBean lrcBean = new LrcBean("我和我最后的倔强,握紧双手绝对不放", 10000, 15000);
* @param text
* @param start ms
* @param end ms
*/
public LrcBean(String text, long start, long end) { public LrcBean(String text, long start, long end) {
this.lrc = text; this.lrc = text;
this.start = start; this.start = start;
this.end = end; this.end = end;
} }
/**
* LrcBean
* 便
* @return
*/
public String getLrc() { public String getLrc() {
return lrc; return lrc;
} }
/**
*
* LrcBean
* @param lrc
*/
public void setLrc(String lrc) { public void setLrc(String lrc) {
this.lrc = lrc; this.lrc = lrc;
} }
/**
* LrcBean
*
* @return ms
*/
public long getStart() { public long getStart() {
return start; return start;
} }
/**
*
* LrcBean
*
* @param start ms
*/
public void setStart(long start) { public void setStart(long start) {
this.start = start; this.start = start;
} }
/**
* LrcBean
*
* @return ms
*/
public long getEnd() { public long getEnd() {
return end; return end;
} }
/**
*
* LrcBean
*
* @param end ms
*/
public void setEnd(long end) { public void setEnd(long end) {
this.end = end; this.end = end;
} }
} }

@ -4,106 +4,189 @@ import org.litepal.crud.LitePalSupport;
/** /**
* Created by on 2018/11/27. * Created by on 2018/11/27.
* 线线
* LitePalSupportLitePal便
* 线线线
*/ */
public class OnlineSong extends LitePalSupport { public class OnlineSong extends LitePalSupport {
// 用于唯一标识每一首在线歌曲记录的标识符,在数据库中充当主键的角色。
// 数据库通常会按照自增等规则自动生成该值,通过它能精准定位、查询、修改或删除对应的在线歌曲记录,方便数据库层面的管理。
private int id; private int id;
// 歌曲在整个音乐系统中的唯一标识,是一个字符串类型的标识,用于在不同模块、数据源及业务场景下准确区分每一首歌曲。
// 比如关联歌词、歌曲评论,与服务器交互验证歌曲身份,或在播放列表中匹配具体歌曲时,都依赖它来确定歌曲实体,保证数据一致性和准确性。
private String songId; private String songId;
private String mediaId; //下载标识符 // 下载标识符,主要用于区分在线歌曲不同的下载情况或者播放来源等情况。
// 例如同一首歌有多次不同下载经历、通过不同渠道播放时,该标识符可起到唯一标记作用,便于追溯相关背景信息,
// 在断点续播、记录播放资源来源等场景下也能提供关键区分和定位信息,确保在线歌曲记录的完整性和准确性。
private String mediaId;
// 可能是与QQ音乐平台相关的唯一标识从变量名推测若应用支持与QQ音乐交互或整合
// 可借助该标识在QQ音乐系统中查找对应歌曲资源、匹配用户在QQ音乐上的相关操作记录如收藏、分享等
// 方便实现跨平台的数据关联和功能融合具体使用方式取决于与QQ音乐的集成程度及相应业务逻辑要求。
private String qqId; private String qqId;
// 歌曲的名称,以直观易懂的字符串形式呈现给用户,遵循歌曲官方命名,具有规范性和唯一性,
// 便于用户在在线歌曲列表等界面快速识别想听的歌曲,是展示在线歌曲信息的关键内容之一。
private String name; private String name;
// 歌曲的演唱者信息,以字符串形式存储演唱主体的名称,通过它用户能了解歌曲背景,
// 同时方便基于演唱者进行在线歌曲的分类、筛选等操作,比如查找特定歌手的所有在线歌曲来创建播放列表。
private String singer; private String singer;
// 歌曲文件的网络链接地址,指向歌曲资源所在的网络位置,音乐播放器依靠该链接发起网络请求获取歌曲文件进行在线播放,
// 在验证链接有效性、处理网络播放异常等资源管理方面,该属性起着关键作用,确保在线歌曲能正常播放。
private String url; private String url;
// 歌曲封面图片的链接地址,通常为网络链接形式,用于获取与歌曲相关联的封面图片资源。
// 在在线歌曲展示界面上,依据该链接加载图片展示在相应歌曲条目上,提升界面美观度,帮助用户直观感受歌曲风格特点,增强用户体验。
private String pic; private String pic;
// 歌曲的歌词文本内容,以字符串形式存储整首歌曲的歌词,音乐播放器播放在线歌曲时,会结合歌词对应的时间信息(如有),
// 根据该内容将歌词展示给用户,方便用户跟唱或查看具体歌词内容。
private String lrc; private String lrc;
// 歌曲的时长信息以长整型数据表示歌曲从开始播放到结束持续的时间长度单位通常为秒s或毫秒ms取决于应用内部设定。
// 它能让用户提前了解歌曲长短以便安排播放,在播放相关业务逻辑(如进度条时长计算、已播放和剩余时间显示、用户播放时长统计等)中有重要作用。
private long duration; private long duration;
// 用于标识歌曲是否已经被下载到本地的状态布尔类型。true表示歌曲已下载到本地false表示仅在线播放未下载到本地。
// 通过该属性可清晰知晓在线歌曲的下载情况,便于后续推荐、管理本地资源等业务逻辑中基于此进行相应操作和判断。
private boolean isDownload; private boolean isDownload;
// 获取当前在线歌曲记录在数据库中的唯一标识(主键)的方法。
// 外部代码调用此方法可获取相应整数值,用于数据库相关操作(如查询、更新、删除记录等)中准确定位对应的记录,保证操作准确性和针对性。
public int getId() { public int getId() {
return id; return id;
} }
// 设置当前在线歌曲记录在数据库中的唯一标识(主键)的方法。
// 一般情况下,该主键值由数据库自动管理和分配,很少被外部代码直接调用修改,
// 仅在特殊业务场景(如数据迁移、数据库记录异常修正主键等情况)下,通过传入合法整数值来更新主键信息。
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
// 获取歌曲名称的方法。
// 外部代码调用此方法后,会返回代表当前在线歌曲名称的字符串,用于在线歌曲列表展示、搜索、排序等业务逻辑中获取名称信息进行操作展示。
public String getName() { public String getName() {
return name; return name;
} }
// 设置歌曲名称的方法。
// 当歌曲名称有变更需求(如官方名称更新、用户手动修改等情况)时,外部代码传入新字符串作为参数来更新当前对象中的歌曲名称属性,
// 确保名称信息准确及时,便于用户准确识别和播放在线歌曲。
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
// 获取歌曲演唱者信息的方法。
// 调用后,外部代码可获取到代表演唱这首在线歌曲的歌手或演唱组合名称的字符串,用于展示歌曲背景及基于演唱者的分类、筛选等操作。
public String getSinger() { public String getSinger() {
return singer; return singer;
} }
// 设置歌曲演唱者信息的方法。
// 当已保存的演唱者信息不准确或获取到更准确名称时(如数据源更新等情况),外部代码传入正确的演唱者名称字符串来更新该属性,
// 保证在线歌曲相关信息准确可靠,便于用户准确识别和管理在线歌曲。
public void setSinger(String singer) { public void setSinger(String singer) {
this.singer = singer; this.singer = singer;
} }
// 获取歌曲文件网络链接地址的方法。
// 外部代码调用此方法会返回指向歌曲文件所在网络位置的字符串,用于音乐播放器发起网络请求获取歌曲文件进行播放,
// 以及进行链接有效性验证、处理网络播放异常等资源管理相关操作。
public String getUrl() { public String getUrl() {
return url; return url;
} }
// 设置歌曲文件网络链接地址的方法。
// 当原链接出现问题(如失效、服务器更换地址等)或获取到新有效链接时,外部代码传入新链接字符串作为参数来更新该属性,
// 确保后续能顺利获取歌曲文件,保证链接信息的准确性和有效性。
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
// 获取歌曲封面图片链接地址的方法。
// 调用后,外部代码可获取到指向歌曲封面图片资源所在位置的字符串,用于在在线歌曲展示界面加载图片,提升界面美观度和用户体验。
public String getPic() { public String getPic() {
return pic; return pic;
} }
// 设置歌曲封面图片链接地址的方法。
// 当封面图片有变更(如更新、更换来源、原链接不可用等情况)时,外部代码传入新封面图片链接字符串来更新该属性,
// 保证界面能展示正确、美观的封面图片,使在线歌曲展示更准确吸引人。
public void setPic(String pic) { public void setPic(String pic) {
this.pic = pic; this.pic = pic;
} }
// 获取歌曲歌词文本内容的方法。
// 外部代码调用此方法后,会返回代表当前在线歌曲歌词内容的字符串,供音乐播放器在播放时展示歌词给用户查看或跟唱。
public String getLrc() { public String getLrc() {
return lrc; return lrc;
} }
// 设置歌曲歌词文本内容的方法。
// 当获取到更准确的歌词数据(如最初歌词有误、歌曲版本更新致歌词变化等情况)时,外部代码传入新歌词字符串作为参数来更新该属性,
// 确保歌词信息准确,以便正确展示歌词内容给用户。
public void setLrc(String lrc) { public void setLrc(String lrc) {
this.lrc = lrc; this.lrc = lrc;
} }
// 获取歌曲在整个音乐系统中的唯一标识的方法。
// 外部代码调用此方法后,会得到相应字符串,用于在进行与歌曲相关各种操作(如查找关联信息、判断两首歌是否同一首等)时,
// 依靠该标识准确区分和定位歌曲,确保在线歌曲数据的准确性和一致性,方便数据管理和利用。
public String getSongId() { public String getSongId() {
return songId; return songId;
} }
// 设置歌曲在整个音乐系统中的唯一标识的方法。
// 在特定业务场景(如与其他音乐系统或数据源数据同步、整合导致标识变化等情况)下,外部代码传入新的唯一标识字符串作为参数,
// 来更新当前对象中的歌曲Id属性保证歌曲在不同系统或场景间准确对应和识别维持数据准确性。
public void setSongId(String songId) { public void setSongId(String songId) {
this.songId = songId; this.songId = songId;
} }
// 设置歌曲时长信息的方法。
// 当获取到更准确的时长数据(如最初记录有误、歌曲版本更新致时长变化等情况)时,外部代码传入代表正确时长的长整型数值作为参数,
// 来更新当前对象中的歌曲时长属性,确保时长信息准确,便于在相关业务逻辑中正确使用该数据。
public void setDuration(long duration) { public void setDuration(long duration) {
this.duration = duration; this.duration = duration;
} }
// 获取歌曲时长信息的方法。
// 外部代码调用此方法后,会返回代表当前在线歌曲时长的长整型数值,用于在线歌曲展示(如显示时长给用户)及播放相关业务逻辑操作。
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
// 获取与QQ音乐平台相关的唯一标识推测用途的方法。
// 外部代码调用此方法后会得到相应字符串在涉及与QQ音乐进行数据交互、关联用户在QQ音乐上操作记录等业务场景中
// 该标识起到关键定位和匹配作用,方便实现跨平台的数据整合和功能拓展。
public String getQqId() { public String getQqId() {
return qqId; return qqId;
} }
// 设置与QQ音乐平台相关的唯一标识推测用途的方法。
// 在与QQ音乐数据交互过程中当获取到新标识信息或需更新该标识时外部代码传入新的QQ音乐相关标识字符串作为参数
// 来更新当前对象中的qqId属性确保跨平台数据关联准确有效便于更好地整合利用QQ音乐相关数据资源。
public void setQqId(String qqId) { public void setQqId(String qqId) {
this.qqId = qqId; this.qqId = qqId;
} }
// 获取下载标识符的方法。
// 外部代码调用此方法后,会得到相应字符串,在追溯歌曲下载情况、区分不同播放来源及基于下载相关业务逻辑(如断点续播等)操作中,
// 该标识符起到关键区分和定位作用,方便进行相应操作和判断。
public String getMediaId() { public String getMediaId() {
return mediaId; return mediaId;
} }
// 设置下载标识符的方法。
// 当需要更新该标识符时(例如下载任务重新开始、同一歌曲有新下载版本需区分等情况),外部代码传入新的下载标识符字符串作为参数,
// 来更新当前对象中的mediaId属性确保下载相关业务逻辑的准确性和可追溯性。
public void setMediaId(String mediaId) { public void setMediaId(String mediaId) {
this.mediaId = mediaId; this.mediaId = mediaId;
} }
// 设置歌曲是否已下载到本地状态的方法。
// 通过传入布尔值参数来更新当前对象中的isDownload属性用于在歌曲下载状态发生变化时及时更新记录便于后续相关业务逻辑处理。
public void setDownload(boolean download) { public void setDownload(boolean download) {
isDownload = download; isDownload = download;
} }
// 获取歌曲是否已下载到本地状态的方法。
// 外部代码调用此方法后,返回布尔值表示当前在线歌曲是否已下载到本地,用于判断歌曲下载情况,辅助业务逻辑操作,如推荐本地歌曲等。
public boolean isDownload() { public boolean isDownload() {
return isDownload; return isDownload;
} }

@ -4,26 +4,59 @@ package com.example.musicplayer.entiy;
* <pre> * <pre>
* author : * author :
* time : 2019/09/04 * time : 2019/09/04
* desc : qq * desc : QQQQ
* 便QQ使使
* </pre> * </pre>
*/ */
public class OnlineSongLrc { public class OnlineSongLrc {
/**
* QQ
* 0
*
*/
private int code; private int code;
/**
* QQ
* QQ
*
*/
private String lyric; private String lyric;
/**
* QQ
* OnlineSongLrc便
* @param lyric
*/
public void setLyric(String lyric) { public void setLyric(String lyric) {
this.lyric = lyric; this.lyric = lyric;
} }
/**
* QQQQ
* OnlineSongLrc
* 使
* @param code
*/
public void setCode(int code) { public void setCode(int code) {
this.code = code; this.code = code;
} }
/**
* QQ
*
* 0
* @return
*/
public int getCode() { public int getCode() {
return code; return code;
} }
/**
* QQ
*
* 便
* @return
*/
public String getLyric() { public String getLyric() {
return lyric; return lyric;
} }

@ -3,25 +3,67 @@ package com.example.musicplayer.entiy;
import org.litepal.crud.LitePalSupport; import org.litepal.crud.LitePalSupport;
/** /**
* Created by on 2018/11/29. * <pre>
* author :
* time : 2018/11/29
* desc :
* 便便
* </pre>
*/ */
// SearchHistory类继承自LitePalSupport类借助LitePal框架提供的功能可以方便地与数据库进行交互
// 例如将搜索历史信息持久化存储到数据库中,以便后续能从数据库里查询特定的搜索历史记录、更新已有记录的内容,
// 或者根据业务需求删除某些不再需要的搜索历史记录等,有助于高效地管理用户的搜索历史数据。
public class SearchHistory extends LitePalSupport { public class SearchHistory extends LitePalSupport {
/**
*
*
* 便
*/
String history; String history;
/**
*
*
* 便
*/
int id; int id;
/**
*
*
* 便
* @return
*/
public String getHistory() { public String getHistory() {
return history; return history;
} }
/**
*
* SearchHistory
* 便使
* @param history
*/
public void setHistory(String history) { public void setHistory(String history) {
this.history = history; this.history = history;
} }
/**
*
*
*
* @return
*/
public int getId() { public int getId() {
return id; return id;
} }
/**
*
*
* 使
* SearchHistory
* @param id
*/
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }

@ -6,24 +6,20 @@ import java.util.List;
* <pre> * <pre>
* author : * author :
* time : 2019/08/31 * time : 2019/08/31
* desc : * desc :
* </pre> * </pre>
*/ */
public class SearchSong { public class SearchSong {
/** /**
* code : 0 * 0
* data : {"keyword":"邓紫棋","priority":0,"qc":[],"semantic":{"curnum":0,"curpage":1,"list":[],"totalnum":0},"song":{"curnum":2,"curpage":1,"list":[{"albumid":7806097,"albummid":"001F3IM92zEXSX","albumname":"中国新说唱2019 第12期","albumname_hilight":"中国新说唱2019 第12期","alertid":23,"belongCD":0,"cdIdx":6,"chinesesinger":0,"docid":"1263425476867044852","grp":[],"interval":232,"isonly":0,"lyric":"","lyric_hilight":"","media_mid":"003XrSXL43XhhP","msgid":16,"newStatus":1,"nt":3078011703,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":0,"tryend":0,"trysize":960887},"pubtime":1567166403,"pure":0,"singer":[{"id":13948,"mid":"001fNHEf1SFEFN","name":"G.E.M. 邓紫棋","name_hilight":"G.E.M. <em>邓紫棋<\/em>"},{"id":3344622,"mid":"003MDz1629ej3X","name":"OBi","name_hilight":"OBi"},{"id":2626134,"mid":"001yBcHD4adsSA","name":"刘炫廷","name_hilight":"刘炫廷"},{"id":2085196,"mid":"001eBoUt4SiKgg","name":"Capper","name_hilight":"Capper"}],"size128":3728026,"size320":9319693,"sizeape":0,"sizeflac":0,"sizeogg":5221536,"songid":237274487,"songmid":"003XrSXL43XhhP","songname":"We Are Young (Live)","songname_hilight":"We Are Young (Live)","strMediaMid":"003XrSXL43XhhP","stream":1,"switch":17413891,"t":1,"tag":10,"type":0,"ver":0,"vid":""},{"albumid":1196826,"albummid":"003c616O2Zlswm","albumname":"新的心跳","albumname_hilight":"新的心跳","alertid":2,"belongCD":0,"cdIdx":4,"chinesesinger":0,"docid":"3872246129178486611","grp":[],"interval":245,"isonly":0,"lyric":"","lyric_hilight":"","media_mid":"001DI2Jj3Jqve9","msgid":16,"newStatus":2,"nt":2292267207,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":0,"tryend":0,"trysize":0},"pubtime":1446739200,"pure":0,"singer":[{"id":13948,"mid":"001fNHEf1SFEFN","name":"G.E.M. 邓紫棋","name_hilight":"G.E.M. <em>邓紫棋<\/em>"}],"size128":3929034,"size320":9822259,"sizeape":0,"sizeflac":29903656,"sizeogg":5405648,"songid":104913384,"songmid":"004dFFPd4JNv8q","songname":"来自天堂的魔鬼","songname_hilight":"来自天堂的魔鬼","strMediaMid":"001DI2Jj3Jqve9","stream":1,"switch":636675,"t":1,"tag":11,"type":0,"ver":0,"vid":"v001892io9b"}],"totalnum":416},"tab":0,"taglist":[],"totaltime":0,"zhida":{"chinesesinger":0,"type":0}}
* message :
* notice :
* subcode : 0
* time : 1567229228
* tips :
*/ */
private int code; private int code;
/**
* DataBean
*/
private DataBean data; private DataBean data;
// 以下是获取和设置code属性的方法
public int getCode() { public int getCode() {
return code; return code;
} }
@ -32,6 +28,7 @@ public class SearchSong {
this.code = code; this.code = code;
} }
// 以下是获取和设置data属性的方法
public DataBean getData() { public DataBean getData() {
return data; return data;
} }
@ -40,441 +37,623 @@ public class SearchSong {
this.data = data; this.data = data;
} }
// 内部静态类DataBean用于封装更细化的数据结构是SearchSong类中数据部分的进一步细分。
public static class DataBean { public static class DataBean {
/** /**
* keyword : * SongBean
* priority : 0
* qc : []
* semantic : {"curnum":0,"curpage":1,"list":[],"totalnum":0}
* song : {"curnum":2,"curpage":1,"list":[{"albumid":7806097,"albummid":"001F3IM92zEXSX","albumname":"中国新说唱2019 第12期","albumname_hilight":"中国新说唱2019 第12期","alertid":23,"belongCD":0,"cdIdx":6,"chinesesinger":0,"docid":"1263425476867044852","grp":[],"interval":232,"isonly":0,"lyric":"","lyric_hilight":"","media_mid":"003XrSXL43XhhP","msgid":16,"newStatus":1,"nt":3078011703,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":0,"tryend":0,"trysize":960887},"pubtime":1567166403,"pure":0,"singer":[{"id":13948,"mid":"001fNHEf1SFEFN","name":"G.E.M. 邓紫棋","name_hilight":"G.E.M. <em>邓紫棋<\/em>"},{"id":3344622,"mid":"003MDz1629ej3X","name":"OBi","name_hilight":"OBi"},{"id":2626134,"mid":"001yBcHD4adsSA","name":"刘炫廷","name_hilight":"刘炫廷"},{"id":2085196,"mid":"001eBoUt4SiKgg","name":"Capper","name_hilight":"Capper"}],"size128":3728026,"size320":9319693,"sizeape":0,"sizeflac":0,"sizeogg":5221536,"songid":237274487,"songmid":"003XrSXL43XhhP","songname":"We Are Young (Live)","songname_hilight":"We Are Young (Live)","strMediaMid":"003XrSXL43XhhP","stream":1,"switch":17413891,"t":1,"tag":10,"type":0,"ver":0,"vid":""},{"albumid":1196826,"albummid":"003c616O2Zlswm","albumname":"新的心跳","albumname_hilight":"新的心跳","alertid":2,"belongCD":0,"cdIdx":4,"chinesesinger":0,"docid":"3872246129178486611","grp":[],"interval":245,"isonly":0,"lyric":"","lyric_hilight":"","media_mid":"001DI2Jj3Jqve9","msgid":16,"newStatus":2,"nt":2292267207,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":0,"tryend":0,"trysize":0},"pubtime":1446739200,"pure":0,"singer":[{"id":13948,"mid":"001fNHEf1SFEFN","name":"G.E.M. 邓紫棋","name_hilight":"G.E.M. <em>邓紫棋<\/em>"}],"size128":3929034,"size320":9822259,"sizeape":0,"sizeflac":29903656,"sizeogg":5405648,"songid":104913384,"songmid":"004dFFPd4JNv8q","songname":"来自天堂的魔鬼","songname_hilight":"来自天堂的魔鬼","strMediaMid":"001DI2Jj3Jqve9","stream":1,"switch":636675,"t":1,"tag":11,"type":0,"ver":0,"vid":"v001892io9b"}],"totalnum":416}
* tab : 0
* taglist : []
* totaltime : 0
* zhida : {"chinesesinger":0,"type":0}
*/ */
private SongBean song; private SongBean song;
// 获取song属性的方法
public SongBean getSong() { public SongBean getSong() {
return song; return song;
} }
// 设置song属性的方法
public void setSong(SongBean song) { public void setSong(SongBean song) {
this.song = song; this.song = song;
} }
// 内部静态类SongBean进一步细化歌曲相关的各种信息结构比如歌曲列表情况、当前页、总数量等。
public static class SongBean { public static class SongBean {
/** /**
* curnum : 2 *
* curpage : 1
* list : [{"albumid":7806097,"albummid":"001F3IM92zEXSX","albumname":"中国新说唱2019 第12期","albumname_hilight":"中国新说唱2019 第12期","alertid":23,"belongCD":0,"cdIdx":6,"chinesesinger":0,"docid":"1263425476867044852","grp":[],"interval":232,"isonly":0,"lyric":"","lyric_hilight":"","media_mid":"003XrSXL43XhhP","msgid":16,"newStatus":1,"nt":3078011703,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":0,"tryend":0,"trysize":960887},"pubtime":1567166403,"pure":0,"singer":[{"id":13948,"mid":"001fNHEf1SFEFN","name":"G.E.M. 邓紫棋","name_hilight":"G.E.M. <em>邓紫棋<\/em>"},{"id":3344622,"mid":"003MDz1629ej3X","name":"OBi","name_hilight":"OBi"},{"id":2626134,"mid":"001yBcHD4adsSA","name":"刘炫廷","name_hilight":"刘炫廷"},{"id":2085196,"mid":"001eBoUt4SiKgg","name":"Capper","name_hilight":"Capper"}],"size128":3728026,"size320":9319693,"sizeape":0,"sizeflac":0,"sizeogg":5221536,"songid":237274487,"songmid":"003XrSXL43XhhP","songname":"We Are Young (Live)","songname_hilight":"We Are Young (Live)","strMediaMid":"003XrSXL43XhhP","stream":1,"switch":17413891,"t":1,"tag":10,"type":0,"ver":0,"vid":""},{"albumid":1196826,"albummid":"003c616O2Zlswm","albumname":"新的心跳","albumname_hilight":"新的心跳","alertid":2,"belongCD":0,"cdIdx":4,"chinesesinger":0,"docid":"3872246129178486611","grp":[],"interval":245,"isonly":0,"lyric":"","lyric_hilight":"","media_mid":"001DI2Jj3Jqve9","msgid":16,"newStatus":2,"nt":2292267207,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":0,"tryend":0,"trysize":0},"pubtime":1446739200,"pure":0,"singer":[{"id":13948,"mid":"001fNHEf1SFEFN","name":"G.E.M. 邓紫棋","name_hilight":"G.E.M. <em>邓紫棋<\/em>"}],"size128":3929034,"size320":9822259,"sizeape":0,"sizeflac":29903656,"sizeogg":5405648,"songid":104913384,"songmid":"004dFFPd4JNv8q","songname":"来自天堂的魔鬼","songname_hilight":"来自天堂的魔鬼","strMediaMid":"001DI2Jj3Jqve9","stream":1,"switch":636675,"t":1,"tag":11,"type":0,"ver":0,"vid":"v001892io9b"}]
* totalnum : 416
*/ */
private int curnum; private int curnum;
/**
* 便
*/
private int curpage; private int curpage;
/**
* 便
*/
private int totalnum; private int totalnum;
/**
* List<ListBean>ListBean
*/
private List<ListBean> list; private List<ListBean> list;
// 获取当前页歌曲数量的方法
public int getCurnum() { public int getCurnum() {
return curnum; return curnum;
} }
// 设置当前页歌曲数量的方法
public void setCurnum(int curnum) { public void setCurnum(int curnum) {
this.curnum = curnum; this.curnum = curnum;
} }
// 获取当前页码的方法
public int getCurpage() { public int getCurpage() {
return curpage; return curpage;
} }
// 设置当前页码的方法
public void setCurpage(int curpage) { public void setCurpage(int curpage) {
this.curpage = curpage; this.curpage = curpage;
} }
// 获取总歌曲数量的方法
public int getTotalnum() { public int getTotalnum() {
return totalnum; return totalnum;
} }
// 设置总歌曲数量的方法
public void setTotalnum(int totalnum) { public void setTotalnum(int totalnum) {
this.totalnum = totalnum; this.totalnum = totalnum;
} }
// 获取歌曲列表信息的方法
public List<ListBean> getList() { public List<ListBean> getList() {
return list; return list;
} }
// 设置歌曲列表信息的方法
public void setList(List<ListBean> list) { public void setList(List<ListBean> list) {
this.list = list; this.list = list;
} }
// 内部静态类ListBean用来封装每一首歌曲具体的详细信息如专辑信息、歌手信息、歌曲时长等众多细节内容。
public static class ListBean { public static class ListBean {
/** /**
* albumid : 7806097 * ID便
* albummid : 001F3IM92zEXSX
* albumname : 2019 12
* albumname_hilight : 2019 12
* alertid : 23
* belongCD : 0
* cdIdx : 6
* chinesesinger : 0
* docid : 1263425476867044852
* grp : []
* interval : 232
* isonly : 0
* lyric :
* lyric_hilight :
* media_mid : 003XrSXL43XhhP
* msgid : 16
* newStatus : 1
* nt : 3078011703
* pay : {"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200}
* preview : {"trybegin":0,"tryend":0,"trysize":960887}
* pubtime : 1567166403
* pure : 0
* singer : [{"id":13948,"mid":"001fNHEf1SFEFN","name":"G.E.M. 邓紫棋","name_hilight":"G.E.M. <em>邓紫棋<\/em>"},{"id":3344622,"mid":"003MDz1629ej3X","name":"OBi","name_hilight":"OBi"},{"id":2626134,"mid":"001yBcHD4adsSA","name":"刘炫廷","name_hilight":"刘炫廷"},{"id":2085196,"mid":"001eBoUt4SiKgg","name":"Capper","name_hilight":"Capper"}]
* size128 : 3728026
* size320 : 9319693
* sizeape : 0
* sizeflac : 0
* sizeogg : 5221536
* songid : 237274487
* songmid : 003XrSXL43XhhP
* songname : We Are Young (Live)
* songname_hilight : We Are Young (Live)
* strMediaMid : 003XrSXL43XhhP
* stream : 1
* switch : 17413891
* t : 1
* tag : 10
* type : 0
* ver : 0
* vid :
*/ */
private int albumid; private int albumid;
/**
*
*/
private String albummid; private String albummid;
/**
*
*/
private String albumname; private String albumname;
/**
*
*/
private String albumname_hilight; private String albumname_hilight;
/**
*
*/
private int alertid; private int alertid;
/**
* CDCDCD
*/
private int belongCD; private int belongCD;
/**
* CDCD
*/
private int cdIdx; private int cdIdx;
/**
* 01
*/
private int chinesesinger; private int chinesesinger;
/**
*
*/
private String docid; private String docid;
/**
*
*/
private int interval; private int interval;
/**
*
*/
private int isonly; private int isonly;
/**
*
*/
private String lyric; private String lyric;
/**
*
*/
private String lyric_hilight; private String lyric_hilight;
/**
*
*/
private String media_mid; private String media_mid;
/**
*
*/
private int msgid; private int msgid;
/**
*
*/
private int newStatus; private int newStatus;
/**
*
*/
private long nt; private long nt;
/**
*
*/
private PayBean pay;
/**
*
*/
private PreviewBean preview;
/**
* 线
*/
private int pubtime; private int pubtime;
/**
* 广
*/
private int pure; private int pure;
/**
* List<SingerBean>SingerBean
*/
private List<SingerBean> singer;
/**
* 128k
*/
private int size128; private int size128;
/**
* 320k
*/
private int size320; private int size320;
/**
* APEAPE
*/
private int sizeape; private int sizeape;
/**
* FLACFLAC便
*/
private int sizeflac; private int sizeflac;
/**
* OGGOGG
*/
private int sizeogg; private int sizeogg;
/**
* ID便
*/
private int songid; private int songid;
/**
* media_mid
*/
private String songmid; private String songmid;
/**
*
*/
private String songname; private String songname;
/**
*
*/
private String songname_hilight; private String songname_hilight;
/**
* media_mid使
*/
private String strMediaMid; private String strMediaMid;
/**
* 线10
*/
private int stream; private int stream;
/**
*
*/
private int switchX; private int switchX;
/**
*
*/
private int t; private int t;
/**
*
*/
private int tag; private int tag;
/**
*
*/
private int type; private int type;
/**
*
*/
private int ver; private int ver;
private List<SingerBean> singer;
// 获取专辑ID的方法
public int getAlbumid() { public int getAlbumid() {
return albumid; return albumid;
} }
// 设置专辑ID的方法
public void setAlbumid(int albumid) { public void setAlbumid(int albumid) {
this.albumid = albumid; this.albumid = albumid;
} }
// 获取专辑中间标识的方法
public String getAlbummid() { public String getAlbummid() {
return albummid; return albummid;
} }
// 设置专辑中间标识的方法
public void setAlbummid(String albummid) { public void setAlbummid(String albummid) {
this.albummid = albummid; this.albummid = albummid;
} }
// 获取专辑名称的方法
public String getAlbumname() { public String getAlbumname() {
return albumname; return albumname;
} }
// 设置专辑名称的方法
public void setAlbumname(String albumname) { public void setAlbumname(String albumname) {
this.albumname = albumname; this.albumname = albumname;
} }
// 获取专辑名称高亮显示版本的方法
public String getAlbumname_hilight() { public String getAlbumname_hilight() {
return albumname_hilight; return albumname_hilight;
} }
// 设置专辑名称高亮显示版本的方法
public void setAlbumname_hilight(String albumname_hilight) { public void setAlbumname_hilight(String albumname_hilight) {
this.albumname_hilight = albumname_hilight; this.albumname_hilight = albumname_hilight;
} }
// 获取提醒标识的方法
public int getAlertid() { public int getAlertid() {
return alertid; return alertid;
} }
// 设置提醒标识的方法
public void setAlertid(int alertid) { public void setAlertid(int alertid) {
this.alertid = alertid; this.alertid = alertid;
} }
// 获取所属CD相关编号的方法
public int getBelongCD() { public int getBelongCD() {
return belongCD; return belongCD;
} }
// 设置所属CD相关编号的方法
public void setBelongCD(int belongCD) { public void setBelongCD(int belongCD) {
this.belongCD = belongCD; this.belongCD = belongCD;
} }
// 获取CD内索引序号的方法
public int getCdIdx() { public int getCdIdx() {
return cdIdx; return cdIdx;
} }
// 设置CD内索引序号的方法
public void setCdIdx(int cdIdx) { public void setCdIdx(int cdIdx) {
this.cdIdx = cdIdx; this.cdIdx = cdIdx;
} }
// 获取是否中文歌手标识的方法
public int getChinesesinger() { public int getChinesesinger() {
return chinesesinger; return chinesesinger;
} }
// 设置是否中文歌手标识的方法
public void setChinesesinger(int chinesesinger) { public void setChinesesinger(int chinesesinger) {
this.chinesesinger = chinesesinger; this.chinesesinger = chinesesinger;
} }
// 获取文档唯一标识的方法
public String getDocid() { public String getDocid() {
return docid; return docid;
} }
// 设置文档唯一标识的方法
public void setDocid(String docid) { public void setDocid(String docid) {
this.docid = docid; this.docid = docid;
} }
// 获取歌曲间隔时长的方法
public int getInterval() { public int getInterval() {
return interval; return interval;
} }
// 设置歌曲间隔时长的方法
public void setInterval(int interval) { public void setInterval(int interval) {
this.interval = interval; this.interval = interval;
} }
// 获取是否唯一标识的方法
public int getIsonly() { public int getIsonly() {
return isonly; return isonly;
} }
// 设置是否唯一标识的方法
public void setIsonly(int isonly) { public void setIsonly(int isonly) {
this.isonly = isonly; this.isonly = isonly;
} }
// 获取歌词内容的方法
public String getLyric() { public String getLyric() {
return lyric; return lyric;
} }
// 设置歌词内容的方法
public void setLyric(String lyric) { public void setLyric(String lyric) {
this.lyric = lyric; this.lyric = lyric;
} }
// 获取歌词高亮显示版本的方法
public String getLyric_hilight() { public String getLyric_hilight() {
return lyric_hilight; return lyric_hilight;
} }
// 设置歌词高亮显示版本的方法
public void setLyric_hilight(String lyric_hilight) { public void setLyric_hilight(String lyric_hilight) {
this.lyric_hilight = lyric_hilight; this.lyric_hilight = lyric_hilight;
} }
// 获取歌曲媒体中间标识的方法
public String getMedia_mid() { public String getMedia_mid() {
return media_mid; return media_mid;
} }
// 设置歌曲媒体中间标识的方法
public void setMedia_mid(String media_mid) { public void setMedia_mid(String media_mid) {
this.media_mid = media_mid; this.media_mid = media_mid;
} }
// 获取消息标识的方法
public int getMsgid() { public int getMsgid() {
return msgid; return msgid;
} }
// 设置消息标识的方法
public void setMsgid(int msgid) { public void setMsgid(int msgid) {
this.msgid = msgid; this.msgid = msgid;
} }
// 获取新状态标识的方法
public int getNewStatus() { public int getNewStatus() {
return newStatus; return newStatus;
} }
// 设置新状态标识的方法
public void setNewStatus(int newStatus) { public void setNewStatus(int newStatus) {
this.newStatus = newStatus; this.newStatus = newStatus;
} }
// 获取时间戳之类长整型数值的方法
public long getNt() { public long getNt() {
return nt; return nt;
} }
// 设置时间戳之类长整型数值的方法
public void setNt(long nt) { public void setNt(long nt) {
this.nt = nt; this.nt = nt;
} }
// 获取支付相关信息对象的方法
public PayBean getPay() {
return pay;
}
// 设置支付相关信息对象的方法
public void setPay(PayBean pay) {
this.pay = pay;
}
// 获取预览相关信息对象的方法
public PreviewBean getPreview() {
return preview;
}
// 设置预览相关信息对象的方法
public void setPreview(PreviewBean preview) {
this.preview = preview;
}
// 获取歌曲发布时间的方法
public int getPubtime() { public int getPubtime() {
return pubtime; return pubtime;
} }
// 设置歌曲发布时间的方法
public void setPubtime(int pubtime) { public void setPubtime(int pubtime) {
this.pubtime = pubtime; this.pubtime = pubtime;
} }
// 获取是否纯净版标识的方法
public int getPure() { public int getPure() {
return pure; return pure;
} }
// 设置是否纯净版标识的方法
public void setPure(int pure) { public void setPure(int pure) {
this.pure = pure; this.pure = pure;
} }
// 获取歌手信息列表的方法
public List<SingerBean> getSinger() {
return singer;
}
// 设置歌手信息列表的方法
public void setSinger(List<SingerBean> singer) {
this.singer = singer;
}
// 获取128k歌曲文件大小的方法
public int getSize128() { public int getSize128() {
return size128; return size128;
} }
// 设置128k歌曲文件大小的方法
public void setSize128(int size128) { public void setSize128(int size128) {
this.size128 = size128; this.size128 = size128;
} }
// 获取320k歌曲文件大小的方法
public int getSize320() { public int getSize320() {
return size320; return size320;
} }
// 设置320k歌曲文件大小的方法
public void setSize320(int size320) { public void setSize320(int size320) {
this.size320 = size320; this.size320 = size320;
} }
// 获取APE格式歌曲文件大小的方法
public int getSizeape() { public int getSizeape() {
return sizeape; return sizeape;
} }
// 设置APE格式歌曲文件大小的方法
public void setSizeape(int sizeape) { public void setSizeape(int sizeape) {
this.sizeape = sizeape; this.sizeape = sizeape;
} }
// 获取FLAC格式歌曲文件大小的方法
public int getSizeflac() { public int getSizeflac() {
return sizeflac; return sizeflac;
}
public void setSizeflac(int sizeflac) { public void setSizeflac(int sizeflac) {
// 设置FLAC格式歌曲文件大小的方法将传入的参数赋值给当前对象的sizeflac属性以便更新歌曲该格式文件大小信息
this.sizeflac = sizeflac; this.sizeflac = sizeflac;
} }
public int getSizeogg() { public int getSizeogg() {
// 获取OGG格式歌曲文件大小的方法用于在其他地方获取当前歌曲的OGG格式文件大小数值
return sizeogg; return sizeogg;
} }
public void setSizeogg(int sizeogg) { public void setSizeogg(int sizeogg) {
// 设置OGG格式歌曲文件大小的方法把传入的sizeogg参数赋值给当前对象对应的属性实现文件大小信息更新
this.sizeogg = sizeogg; this.sizeogg = sizeogg;
} }
public int getSongid() { public int getSongid() {
// 获取歌曲唯一标识ID的方法方便在整个业务逻辑中通过这个ID来准确区分和操作不同的歌曲
return songid; return songid;
} }
public void setSongid(int songid) { public void setSongid(int songid) {
// 设置歌曲唯一标识ID的方法外部传入的songid参数会被赋给当前对象的songid属性以此来确定歌曲的唯一标识
this.songid = songid; this.songid = songid;
} }
public String getSongmid() { public String getSongmid() {
// 获取歌曲中间标识字符串的方法,在涉及歌曲相关的特定业务操作(比如和某些接口交互等)中,可能会用到这个标识来识别歌曲
return songmid; return songmid;
} }
public void setSongmid(String songmid) { public void setSongmid(String songmid) {
// 设置歌曲中间标识字符串的方法将传入的songmid参数赋值给当前对象对应的属性更新歌曲的这个标识信息
this.songmid = songmid; this.songmid = songmid;
} }
public String getSongname() { public String getSongname() {
// 获取歌曲名称的方法,用于展示给用户看具体是哪首歌曲,或者在一些业务处理中根据歌曲名称进行相关操作
return songname; return songname;
} }
public void setSongname(String songname) { public void setSongname(String songname) {
// 设置歌曲名称的方法,通过传入的参数来更新当前对象的歌曲名称属性,改变歌曲对应的显示名称
this.songname = songname; this.songname = songname;
} }
public String getSongname_hilight() { public String getSongname_hilight() {
// 获取歌曲名称高亮显示版本的方法,在搜索结果展示等需要突出显示关键词的场景下,获取这个高亮处理后的名称进行展示
return songname_hilight; return songname_hilight;
} }
public void setSongname_hilight(String songname_hilight) { public void setSongname_hilight(String songname_hilight) {
// 设置歌曲名称高亮显示版本的方法,将传入的高亮名称参数赋值给对应的属性,更新歌曲名称的高亮显示内容
this.songname_hilight = songname_hilight; this.songname_hilight = songname_hilight;
} }
public String getStrMediaMid() { public String getStrMediaMid() {
// 获取字符串形式的媒体中间标识的方法和media_mid类似可能在不同的业务环节用于歌曲媒体相关的识别等操作
return strMediaMid; return strMediaMid;
} }
public void setStrMediaMid(String strMediaMid) { public void setStrMediaMid(String strMediaMid) {
// 设置字符串形式的媒体中间标识的方法,将传入的参数赋值给对应属性,更新这个媒体相关的标识信息
this.strMediaMid = strMediaMid; this.strMediaMid = strMediaMid;
} }
public int getStream() { public int getStream() {
// 获取可能表示是否能在线播放之类标识的方法不同的值比如1表示可以播放0表示不能等代表不同的播放状态供业务逻辑判断使用
return stream; return stream;
} }
public void setStream(int stream) { public void setStream(int stream) {
// 设置是否能在线播放标识的方法外部传入的stream参数会更新当前对象对应的属性改变歌曲的播放状态标识
this.stream = stream; this.stream = stream;
} }
public int getSwitchX() { public int getSwitchX() {
// 获取可能是某种切换相关标识的方法,比如涉及播放源切换等情况时,通过这个标识来判断是否需要切换以及切换的相关状态,具体含义依赖业务场景设定
return switchX; return switchX;
} }
public void setSwitchX(int switchX) { public void setSwitchX(int switchX) {
// 设置切换相关标识的方法将传入的switchX参数赋值给对应属性更新歌曲的这个切换相关标识信息
this.switchX = switchX; this.switchX = switchX;
} }
public int getT() { public int getT() {
// 获取可能是某种类型标识的方法,例如歌曲风格类型等也许会用不同值来区分,具体含义由业务逻辑确定,供业务处理时参考判断歌曲的相关类型情况
return t; return t;
} }
public void setT(int t) { public void setT(int t) {
// 设置某种类型标识的方法传入的t参数会更新当前对象对应的属性改变这个类型标识的取值
this.t = t; this.t = t;
} }
public int getTag() { public int getTag() {
// 获取标签标识的方法,在歌曲打不同标签分类等业务场景下,通过这个标识来知晓歌曲所属的标签类别,不同值代表不同标签含义
return tag; return tag;
} }
public void setTag(int tag) { public void setTag(int tag) {
// 设置标签标识的方法外部传入的tag参数会赋值给对应属性以此更新歌曲的标签分类标识
this.tag = tag; this.tag = tag;
} }
public int getType() { public int getType() {
// 获取歌曲类型标识的方法,比如区分是原创、翻唱等不同类型的歌曲,业务中根据这个标识来进行相应的不同处理逻辑
return type; return type;
} }
public void setType(int type) { public void setType(int type) {
// 设置歌曲类型标识的方法将传入的type参数赋值给对应属性改变歌曲的类型标识取值更新其类型相关信息
this.type = type; this.type = type;
} }
public int getVer() { public int getVer() {
// 获取版本相关标识的方法,比如歌曲的版本号等,用于在业务处理中判断歌曲是否是不同版本,进而进行对应的版本相关操作
return ver; return ver;
} }
public void setVer(int ver) { public void setVer(int ver) {
// 设置版本相关标识的方法传入的ver参数会更新当前对象的版本标识属性改变歌曲对应的版本相关信息
this.ver = ver; this.ver = ver;
} }
public List<SingerBean> getSinger() { public List<SingerBean> getSinger() {
// 获取参与演唱这首歌曲的歌手信息列表的方法,方便在业务逻辑中获取所有参与演唱的歌手详细情况,比如展示歌手名字等操作
return singer; return singer;
} }
public void setSinger(List<SingerBean> singer) { public void setSinger(List<SingerBean> singer) {
// 设置参与演唱这首歌曲的歌手信息列表的方法,传入的歌手信息列表参数会替换当前对象保存的歌手列表,更新歌曲对应的歌手信息
this.singer = singer; this.singer = singer;
} }
@ -487,46 +666,52 @@ public class SearchSong {
* name_hilight : G.E.M. <em></em> * name_hilight : G.E.M. <em></em>
*/ */
// 歌手的唯一标识ID用于区分不同的歌手个体在整个业务系统中可以通过这个ID准确识别不同的歌手对象比如关联查询歌手的其他详细资料等
private int id; private int id;
// 歌手的中间标识字符串,具体用途根据业务逻辑而定,可能在和某些音乐相关接口交互等场景下用于识别歌手的特定标识
private String mid; private String mid;
// 歌手的名称,展示给用户看是谁演唱了这首歌曲,是最直观的歌手信息呈现内容
private String name; private String name;
// 歌手名称的高亮显示版本,在搜索展示等场景下,可用于突出显示关键词(比如在搜索某个歌手时突出显示匹配的部分)
private String name_hilight; private String name_hilight;
public int getId() { public int getId() {
// 获取歌手唯一标识ID的方法供业务逻辑中需要根据歌手ID进行相关操作如查询、关联等时使用
return id; return id;
} }
public void setId(int id) { public void setId(int id) {
// 设置歌手唯一标识ID的方法将传入的id参数赋值给当前对象的id属性更新歌手的唯一标识信息
this.id = id; this.id = id;
} }
public String getMid() { public String getMid() {
// 获取歌手中间标识字符串的方法,方便在涉及特定业务环节(如接口调用中识别歌手等)使用这个标识进行相关操作
return mid; return mid;
} }
public void setMid(String mid) { public void setMid(String mid) {
// 设置歌手中间标识字符串的方法把传入的mid参数赋值给对应属性改变歌手的这个标识信息
this.mid = mid; this.mid = mid;
} }
public String getName() { public String getName() {
// 获取歌手名称的方法,用于在各种界面展示等场景下向用户呈现演唱歌曲的歌手是谁
return name; return name;
} }
public void setName(String name) { public void setName(String name) {
// 设置歌手名称的方法传入的name参数会更新当前对象的name属性改变歌手对应的显示名称
this.name = name; this.name = name;
} }
public String getName_hilight() { public String getName_hilight() {
// 获取歌手名称高亮显示版本的方法,在需要突出显示歌手名称中关键词的场景下,获取这个高亮处理后的名称进行展示
return name_hilight; return name_hilight;
} }
public void setName_hilight(String name_hilight) { public void setName_hilight(String name_hilight) {
// 设置歌手名称高亮显示版本的方法,将传入的高亮名称参数赋值给对应属性,更新歌手名称的高亮显示内容
this.name_hilight = name_hilight; this.name_hilight = name_hilight;
} }
} }
}
}
}
}

@ -4,99 +4,111 @@ import java.util.List;
/** /**
* Created by on 2018/10/26. * Created by on 2018/10/26.
*
*/ */
public class SingerImg { public class SingerImg {
/** /**
* result : {"artistCount":1,"artists":[{"id":840134,"name":"刘瑞琦","picUrl":"http://p1.music.126.net/qTDkcmWPMK3U54RNC0IgMw==/109951163288035254.jpg","alias":[],"albumSize":20,"picId":109951163288035254,"img1v1Url":"http://p1.music.126.net/a13xmSNqxMY5M_R1OFvPvA==/109951163288038157.jpg","accountId":3788031,"img1v1":109951163288038157,"mvSize":16,"followed":false,"trans":null}]} * ResultBean
* code : 200
*/ */
private ResultBean result; private ResultBean result;
/**
* 200
*/
private int code; private int code;
// 获取result属性的方法方便在外部获取包含详细结果信息的ResultBean对象。
public ResultBean getResult() { public ResultBean getResult() {
return result; return result;
} }
// 设置result属性的方法外部可以传入ResultBean对象来更新当前SingerImg对象中保存的结果数据。
public void setResult(ResultBean result) { public void setResult(ResultBean result) {
this.result = result; this.result = result;
} }
// 获取code属性的方法用于获取表示请求响应状态的状态码数值。
public int getCode() { public int getCode() {
return code; return code;
} }
// 设置code属性的方法外部可传入状态码数值来更新当前对象保存的请求响应状态信息。
public void setCode(int code) { public void setCode(int code) {
this.code = code; this.code = code;
} }
// 内部静态类ResultBean用于进一步细分和封装查询歌手图片结果中的详细数据结构。
public static class ResultBean { public static class ResultBean {
/** /**
* artistCount : 1 * 便便
* artists : [{"id":840134,"name":"刘瑞琦","picUrl":"http://p1.music.126.net/qTDkcmWPMK3U54RNC0IgMw==/109951163288035254.jpg","alias":[],"albumSize":20,"picId":109951163288035254,"img1v1Url":"http://p1.music.126.net/a13xmSNqxMY5M_R1OFvPvA==/109951163288038157.jpg","accountId":3788031,"img1v1":109951163288038157,"mvSize":16,"followed":false,"trans":null}]
*/ */
private List<ArtistsBean> artists; private List<ArtistsBean> artists;
// 获取artists属性的方法用于获取包含歌手详细信息的列表对象以便进一步操作每个歌手的数据。
public List<ArtistsBean> getArtists() { public List<ArtistsBean> getArtists() {
return artists; return artists;
} }
// 设置artists属性的方法外部传入的包含歌手信息的列表会更新当前ResultBean对象中保存的歌手信息。
public void setArtists(List<ArtistsBean> artists) { public void setArtists(List<ArtistsBean> artists) {
this.artists = artists; this.artists = artists;
} }
// 内部静态类ArtistsBean用于详细封装每个歌手对应的各项具体信息比如歌手ID、姓名、图片链接等。
public static class ArtistsBean { public static class ArtistsBean {
/** /**
* id : 840134 * IDID
* name :
* picUrl : http://p1.music.126.net/qTDkcmWPMK3U54RNC0IgMw==/109951163288035254.jpg
* alias : []
* albumSize : 20
* picId : 109951163288035254
* img1v1Url : http://p1.music.126.net/a13xmSNqxMY5M_R1OFvPvA==/109951163288038157.jpg
* accountId : 3788031
* img1v1 : 109951163288038157
* mvSize : 16
* followed : false
* trans : null
*/ */
private int id; private int id;
/**
*
*/
private String name; private String name;
/**
*
*/
private String picUrl; private String picUrl;
/**
*
*/
private String img1v1Url; private String img1v1Url;
// 获取歌手ID的方法供外部业务逻辑中需要根据歌手ID进行查询、判断等操作时使用。
public int getId() { public int getId() {
return id; return id;
} }
// 设置歌手ID的方法外部传入的歌手ID数值会更新当前对象保存的歌手唯一标识信息。
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
// 获取歌手姓名的方法,方便在界面展示等场景下获取并呈现歌手的名字信息。
public String getName() { public String getName() {
return name; return name;
} }
// 设置歌手姓名的方法,传入的歌手姓名字符串会更新当前对象保存的歌手名称信息。
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
// 获取歌手图片链接的方法,用于获取该歌手对应的原始图片资源链接,以便加载展示图片等操作。
public String getPicUrl() { public String getPicUrl() {
return picUrl; return picUrl;
} }
// 设置歌手图片链接的方法,外部传入的图片链接字符串会更新当前对象保存的图片链接信息,改变对应的图片资源指向。
public void setPicUrl(String picUrl) { public void setPicUrl(String picUrl) {
this.picUrl = picUrl; this.picUrl = picUrl;
} }
// 获取特定格式歌手图片链接的方法,用于获取符合特定要求(如特定尺寸等)的图片资源链接,供不同展示场景使用。
public String getImg1v1Url() { public String getImg1v1Url() {
return img1v1Url; return img1v1Url;
} }
// 设置特定格式歌手图片链接的方法,传入的特定格式图片链接字符串会更新当前对象保存的对应链接信息。
public void setImg1v1Url(String img1v1Url) { public void setImg1v1Url(String img1v1Url) {
this.img1v1Url = img1v1Url; this.img1v1Url = img1v1Url;
} }

@ -1,141 +1,183 @@
package com.example.musicplayer.entiy; package com.example.musicplayer.entiy;
import org.litepal.crud.LitePalSupport; import org.litepal.crud.LitePalSupport;
import java.io.Serializable; import java.io.Serializable;
/** /**
* Created by on 2018/10/19. * Created by on 2018/10/19.
* SongLitePalSupport便便使LitePal
* Serializable使便
*/ */
public class Song extends LitePalSupport implements Serializable { public class Song extends LitePalSupport implements Serializable {
private static final long serialVersionUID=1L; // 用于标识类的序列化版本号在类结构发生变化如新增、删除成员变量等情况如果版本号不一致在反序列化时可能会出现问题这里初始化为1L。
private static final long serialVersionUID = 1L;
// 歌曲在数据库或业务逻辑中的唯一标识ID通常用于区分不同的歌曲记录比如在数据库操作中作为主键等用途。
private long id; private long id;
private String songId; //歌曲id // 歌曲自身的ID用于唯一标识一首歌曲可能是音乐平台分配的特定标识方便在业务逻辑中通过这个ID来查找、操作对应的歌曲信息。
private String qqId;//专属本地音乐本地音乐在qq音乐中的songId private String songId;
private String mediaId;//播放id,下载需要用到 // 专属本地音乐对应的QQ音乐中的歌曲ID用于和QQ音乐相关联针对本地音乐在QQ音乐体系下的标识便于进行一些跨平台或特定QQ音乐相关操作时使用。
private String singer; //歌手 private String qqId;
private long duration; //总时长 // 播放ID在进行歌曲下载等操作时可能会用到也许是用于向服务器请求播放资源或者下载资源时的一个关键标识。
private String songName; //歌曲名字 private String mediaId;
private String url; //歌曲url // 演唱这首歌曲的歌手名称,用于展示歌曲的演唱者信息给用户,也是区分不同歌曲的重要属性之一。
private long currentTime; //歌曲播放时长位置 private String singer;
private int position;//在音乐列表的位置 // 歌曲的总时长,通常以秒或者毫秒等时间单位来计量,方便在播放界面展示歌曲时长以及进行播放进度相关的计算等操作。
private String imgUrl; //歌曲照片 private long duration;
private boolean isOnline; //是否为网络歌曲 // 歌曲的名称,是展示给用户最直观的信息,用于识别具体是哪一首歌曲。
private int listType; //歌曲列表类别,0表示当前没有列表即可能在播放网络歌曲 private String songName;
private boolean isDownload;//是否为下载的歌曲 // 歌曲的资源链接URL通过这个链接可以获取到歌曲的音频文件用于播放或者下载等操作。
private String url;
// 歌曲当前播放到的时长位置,比如在播放过程中记录已经播放了多少时间,方便实现进度条展示、暂停续播等功能。
private long currentTime;
// 歌曲在音乐列表中的位置序号,用于在展示歌曲列表时确定歌曲的顺序,方便用户定位和操作列表中的歌曲。
private int position;
// 歌曲对应的照片(图片)链接,可通过这个链接获取到用于展示歌曲相关的图片资源,比如专辑封面、歌手写真等图片,增强展示效果。
private String imgUrl;
// 用于标识歌曲是否为网络歌曲true表示是网络歌曲需要通过网络获取资源进行播放false表示可能是本地存储的歌曲等情况。
private boolean isOnline;
// 歌曲列表类别0表示当前没有列表可能意味着正在播放网络歌曲未关联到具体的本地列表等情况其他值可能对应不同类型的歌曲列表分类具体含义由业务决定。
private int listType;
// 用于标识歌曲是否为已经下载到本地的歌曲true表示已下载可直接从本地读取资源进行播放false表示未下载可能需要从网络获取等情况。
private boolean isDownload;
// 获取歌曲自身ID的方法供外部代码获取该歌曲对应的唯一标识字符串方便业务逻辑中根据ID进行相关查询等操作。
public String getSongId() { public String getSongId() {
return songId; return songId;
} }
// 设置歌曲自身ID的方法外部传入的歌曲ID字符串会更新当前Song对象中保存的对应标识信息用于改变歌曲的唯一标识。
public void setSongId(String songId) { public void setSongId(String songId) {
this.songId = songId; this.songId = songId;
} }
// 获取专属本地音乐在QQ音乐中的歌曲ID的方法方便在涉及QQ音乐相关操作或者关联本地与QQ音乐资源时获取对应的标识信息。
public String getQqId() { public String getQqId() {
return qqId; return qqId;
} }
// 设置专属本地音乐在QQ音乐中的歌曲ID的方法外部传入的QQ音乐ID字符串会更新当前对象保存的该标识信息。
public void setQqId(String qqId) { public void setQqId(String qqId) {
this.qqId = qqId; this.qqId = qqId;
} }
// 获取播放ID的方法在进行歌曲下载、播放资源请求等操作时可通过这个方法获取对应的标识信息传递给相关接口使用。
public String getMediaId() { public String getMediaId() {
return mediaId; return mediaId;
} }
// 设置播放ID的方法传入的播放ID字符串会更新当前Song对象中保存的播放标识以便改变对应的播放相关标识信息。
public void setMediaId(String mediaId) { public void setMediaId(String mediaId) {
this.mediaId = mediaId; this.mediaId = mediaId;
} }
// 获取歌手名称的方法,用于在界面展示、业务逻辑判断(比如根据歌手分类歌曲等)等场景下获取歌曲的演唱者信息。
public String getSinger() { public String getSinger() {
return singer; return singer;
} }
// 设置歌手名称的方法,外部传入的歌手名字符串会更新当前对象保存的歌手信息,比如歌曲信息更新后修改演唱者名字等情况。
public void setSinger(String singer) { public void setSinger(String singer) {
this.singer = singer; this.singer = singer;
} }
// 获取歌曲总时长的方法,在展示歌曲时长、计算播放进度占比等操作时可获取该时长数值进行相关处理。
public long getDuration() { public long getDuration() {
return duration; return duration;
} }
// 设置歌曲总时长的方法传入的时长数值通常以合适的时间单位计量会更新当前Song对象保存的歌曲总时长信息比如纠正时长错误等情况使用。
public void setDuration(long duration) { public void setDuration(long duration) {
this.duration = duration; this.duration = duration;
} }
// 获取歌曲名称的方法,方便在各种界面(如播放列表、搜索结果等)展示歌曲的具体名称,让用户知晓是哪首歌。
public String getSongName() { public String getSongName() {
return songName; return songName;
} }
// 设置歌曲名称的方法,传入的歌曲名字符串会更新当前对象保存的歌曲名称信息,比如歌曲重命名等业务场景下使用。
public void setSongName(String songName) { public void setSongName(String songName) {
this.songName = songName; this.songName = songName;
} }
// 获取歌曲资源链接URL的方法在播放歌曲或者下载歌曲等操作时通过这个方法获取对应的链接传递给相关的播放、下载组件使用。
public String getUrl() { public String getUrl() {
return url; return url;
} }
// 设置歌曲资源链接URL的方法传入的新的URL字符串会更新当前Song对象保存的资源链接信息比如更换歌曲播放源等情况使用。
public void setUrl(String url) { public void setUrl(String url) {
this.url = url; this.url = url;
} }
// 获取歌曲当前播放时长位置的方法,用于展示播放进度、实现续播等功能时获取当前已经播放到的时间位置信息。
public long getCurrentTime() { public long getCurrentTime() {
return currentTime; return currentTime;
} }
// 设置歌曲当前播放时长位置的方法,传入的时长数值会更新当前对象保存的当前播放时长信息,比如用户手动拖动进度条改变播放位置等情况使用。
public void setCurrentTime(long currentTime) { public void setCurrentTime(long currentTime) {
this.currentTime = currentTime; this.currentTime = currentTime;
} }
// 获取歌曲在音乐列表中位置序号的方法,在展示列表、操作列表顺序等场景下获取歌曲对应的位置信息,方便进行相应处理。
public int getPosition() { public int getPosition() {
return position; return position;
} }
// 设置歌曲在音乐列表中位置序号的方法传入的位置序号数值会更新当前Song对象保存的列表位置信息比如歌曲在列表中排序变动等情况使用。
public void setPosition(int position) { public void setPosition(int position) {
this.position = position; this.position = position;
} }
// 获取歌曲照片(图片)链接的方法,在需要展示歌曲相关图片(如专辑封面等)时,通过这个方法获取对应的图片资源链接进行加载展示。
public String getImgUrl() { public String getImgUrl() {
return imgUrl; return imgUrl;
} }
// 设置歌曲照片图片链接的方法传入的新的图片链接字符串会更新当前Song对象保存的图片链接信息比如更换图片资源等情况使用。
public void setImgUrl(String imgUrl) { public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl; this.imgUrl = imgUrl;
} }
// 获取歌曲是否为网络歌曲标识的方法返回true表示是网络歌曲false表示不是方便业务逻辑根据歌曲来源进行不同处理。
public boolean isOnline() { public boolean isOnline() {
return isOnline; return isOnline;
} }
// 设置歌曲是否为网络歌曲标识的方法,传入的布尔值会更新当前对象保存的网络歌曲标识信息,比如歌曲存储位置改变等情况使用。
public void setOnline(boolean online) { public void setOnline(boolean online) {
isOnline = online; isOnline = online;
} }
// 获取歌曲列表类别标识的方法返回的整数值代表不同的歌曲列表分类情况0有特定含义其他值由业务定义方便业务逻辑根据类别进行相关操作。
public int getListType() { public int getListType() {
return listType; return listType;
} }
// 设置歌曲列表类别标识的方法传入的整数值会更新当前Song对象保存的歌曲列表类别信息比如切换歌曲所属列表类型等情况使用。
public void setListType(int listType) { public void setListType(int listType) {
this.listType = listType; this.listType = listType;
} }
// 获取歌曲是否为已下载歌曲标识的方法返回true表示已下载false表示未下载便于业务逻辑根据下载状态进行不同处理比如本地播放还是网络获取等操作。
public boolean isDownload() { public boolean isDownload() {
return isDownload; return isDownload;
} }
// 设置歌曲是否为已下载歌曲标识的方法,传入的布尔值会更新当前对象保存的下载标识信息,比如歌曲下载完成或者删除本地文件后更新状态等情况使用。
public void setDownload(boolean download) { public void setDownload(boolean download) {
isDownload = download; isDownload = download;
} }
// 获取歌曲在数据库或业务逻辑中的唯一标识ID的方法在数据库操作如查询、更新、删除等或者其他基于ID的业务处理时获取该数值进行相关操作。
public long getId() { public long getId() {
return id; return id;
} }
// 重写toString方法用于方便地将歌曲对象转换为字符串表示形式通常在调试、日志记录等场景下可以直观地看到歌曲对象的各个属性信息内容。
public String toString(){ public String toString(){
return "songName="+songName+",singer="+singer+",url="+url+",imgUrl="+imgUrl return "songName="+songName+",singer="+singer+",url="+url+",imgUrl="+imgUrl
+",duration="+duration+",currentTime="+currentTime+",position="+ position +",duration="+duration+",currentTime="+currentTime+",position="+ position

@ -1,7 +1,6 @@
package com.example.musicplayer.entiy; package com.example.musicplayer.entiy;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import java.util.List; import java.util.List;
/** /**
@ -10,35 +9,35 @@ import java.util.List;
* time : 2019/09/04 * time : 2019/09/04
* desc : * desc :
* </pre> * </pre>
* 使
*/ */
public class SongLrc { public class SongLrc {
/** /**
* code : 0 * 0
* data : {"keyword":"说谎","lyric":{"curnum":1,"curpage":1,"list":[{"albumid":54250,"albummid":"004YodY33zsWTT","albumname":"感官/世界","albumname_hilight":"感官/世界","alertid":23,"belongCD":0,"cdIdx":6,"chinesesinger":0,"content":"说谎 (거짓말) (《杜拉拉升职记》电视剧插曲|《针尖上的天使》电影主题曲) - 林宥嘉 (Yoga Lin)\\n 词:施人诚\\n 曲:李双飞\\n 是有过几个不错对象\\n 说起来并不寂寞孤单\\n 可能我浪荡让人家不安\\n 才会结果都阵亡\\n 我没有什么阴影魔障\\n 你千万不要放在心上\\n 我又不脆弱何况那算什么伤\\n 反正爱情不就都这样\\n 我没有说谎我何必说谎\\n 你懂我的我对你从来就不会假装\\n 我哪有说谎\\n 请别以为你有多难忘\\n 笑是真的不是我逞强\\n 我好久没来这间餐厅\\n 没想到已经换了装潢\\n 角落那窗口闻的到玫瑰花香\\n 被你一说是有些印象\\n 我没有说谎我何必说谎\\n 你知道的我缺点之一就是很健忘\\n 我哪有说谎\\n 是很感谢今晚的相伴\\n 但我竟然有些不习惯\\n 我没有说谎我何必说谎\\n 爱一个人没爱到难道就会怎么样\\n 别说我说谎\\n 人生已经如此的艰难\\n 有些事情就不要拆穿\\n 我没有说谎是爱情说谎\\n 它带你来骗我说\\n 渴望的有 可能有希望\\n 我没有说谎\\n 祝你做个幸福的新娘\\n 我的心事请你就遗忘","docid":"12084758858355992521","download_url":"http://soso.music.qq.com/fcgi-bin/fcg_download_lrc.q?song=说谎&singer=林宥嘉&down=1&songid=638596&docid=12084758858355992521","interval":266,"isonly":0,"lyric":" 我哪有<em>说谎<\/em>\\n 请别以为你有多难忘\\n 笑是真的不是我逞强\\n","media_mid":"000W95Fk3lAVxV","msgid":14,"nt":3842743252,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":73557,"tryend":104291,"trysize":0},"pubtime":1256832000,"pure":0,"singer":[{"id":11606,"mid":"001f0VyZ1hmWZ1","name":"林宥嘉","name_hilight":"林宥嘉"}],"size128":4264898,"size320":10652912,"sizeape":27291116,"sizeflac":28067207,"sizeogg":5800490,"songid":638596,"songmid":"000W95Fk3lAVxV","songname":"说谎","songname_hilight":"<em>说谎<\/em>","strMediaMid":"000W95Fk3lAVxV","stream":1,"switch":636675,"t":1,"tag":11,"type":0,"ver":0,"vid":""}],"totalnum":246},"priority":0,"qc":[],"tab":7,"taglist":[],"totaltime":0,"zhida":{"chinesesinger":0,"type":0}}
* message :
* notice :
* subcode : 0
* time : 1567575160
* tips :
*/ */
private int code; private int code;
/**
* DataBean
*/
private DataBean data; private DataBean data;
// 获取code属性的方法方便外部获取表示歌词请求响应状态的状态码数值。
public int getCode() { public int getCode() {
return code; return code;
} }
// 设置code属性的方法外部可传入状态码数值来更新当前对象保存的请求响应状态信息比如在重新请求歌词等情况时更新状态码。
public void setCode(int code) { public void setCode(int code) {
this.code = code; this.code = code;
} }
// 获取data属性的方法用于获取包含详细歌词相关数据的DataBean对象以便进一步操作歌词的具体信息。
public DataBean getData() { public DataBean getData() {
return data; return data;
} }
// 设置data属性的方法外部传入的DataBean对象会更新当前SongLrc对象中保存的歌词详细结果数据比如获取到新的歌词信息后进行替换。
public void setData(DataBean data) { public void setData(DataBean data) {
this.data = data; this.data = data;
} }
@ -46,300 +45,472 @@ public class SongLrc {
public static class DataBean { public static class DataBean {
/** /**
* keyword : * 使便
* lyric : {"curnum":1,"curpage":1,"list":[{"albumid":54250,"albummid":"004YodY33zsWTT","albumname":"感官/世界","albumname_hilight":"感官/世界","alertid":23,"belongCD":0,"cdIdx":6,"chinesesinger":0,"content":"说谎 (거짓말) (《杜拉拉升职记》电视剧插曲|《针尖上的天使》电影主题曲) - 林宥嘉 (Yoga Lin)\\n 词:施人诚\\n 曲:李双飞\\n 是有过几个不错对象\\n 说起来并不寂寞孤单\\n 可能我浪荡让人家不安\\n 才会结果都阵亡\\n 我没有什么阴影魔障\\n 你千万不要放在心上\\n 我又不脆弱何况那算什么伤\\n 反正爱情不就都这样\\n 我没有说谎我何必说谎\\n 你懂我的我对你从来就不会假装\\n 我哪有说谎\\n 请别以为你有多难忘\\n 笑是真的不是我逞强\\n 我好久没来这间餐厅\\n 没想到已经换了装潢\\n 角落那窗口闻的到玫瑰花香\\n 被你一说是有些印象\\n 我没有说谎我何必说谎\\n 你知道的我缺点之一就是很健忘\\n 我哪有说谎\\n 是很感谢今晚的相伴\\n 但我竟然有些不习惯\\n 我没有说谎我何必说谎\\n 爱一个人没爱到难道就会怎么样\\n 别说我说谎\\n 人生已经如此的艰难\\n 有些事情就不要拆穿\\n 我没有说谎是爱情说谎\\n 它带你来骗我说\\n 渴望的有 可能有希望\\n 我没有说谎\\n 祝你做个幸福的新娘\\n 我的心事请你就遗忘","docid":"12084758858355992521","download_url":"http://soso.music.qq.com/fcgi-bin/fcg_download_lrc.q?song=说谎&singer=林宥嘉&down=1&songid=638596&docid=12084758858355992521","interval":266,"isonly":0,"lyric":" 我哪有<em>说谎<\/em>\\n 请别以为你有多难忘\\n 笑是真的不是我逞强\\n","media_mid":"000W95Fk3lAVxV","msgid":14,"nt":3842743252,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":73557,"tryend":104291,"trysize":0},"pubtime":1256832000,"pure":0,"singer":[{"id":11606,"mid":"001f0VyZ1hmWZ1","name":"林宥嘉","name_hilight":"林宥嘉"}],"size128":4264898,"size320":10652912,"sizeape":27291116,"sizeflac":28067207,"sizeogg":5800490,"songid":638596,"songmid":"000W95Fk3lAVxV","songname":"说谎","songname_hilight":"<em>说谎<\/em>","strMediaMid":"000W95Fk3lAVxV","stream":1,"switch":636675,"t":1,"tag":11,"type":0,"ver":0,"vid":""}],"totalnum":246} */
* priority : 0 private String keyword;
* qc : [] /**
* tab : 7 * LyricBean
* taglist : []
* totaltime : 0
* zhida : {"chinesesinger":0,"type":0}
*/ */
private LyricBean lyric; private LyricBean lyric;
/**
*
*/
private int priority;
/**
*
*/
private List<?> qc;
/**
*
*/
private int tab;
/**
*
*/
private List<?> taglist;
/**
*
*/
private int totaltime;
/**
* 便
*/
private ZhidaBean zhida;
// 获取keyword属性的方法用于获取歌词搜索时的关键词信息在一些搜索相关的业务逻辑中可能会用到。
public String getKeyword() {
return keyword;
}
// 设置keyword属性的方法外部传入的关键词字符串会更新当前DataBean对象保存的关键词信息比如更换搜索歌词的关键词等情况使用。
public void setKeyword(String keyword) {
this.keyword = keyword;
}
// 获取lyric属性的方法用于获取包含歌词详细信息的LyricBean对象进而操作歌词的具体文本、分页等内容。
public LyricBean getLyric() { public LyricBean getLyric() {
return lyric; return lyric;
} }
// 设置lyric属性的方法传入的LyricBean对象会更新当前DataBean对象中保存的歌词主体信息比如获取到新的歌词版本后进行替换。
public void setLyric(LyricBean lyric) { public void setLyric(LyricBean lyric) {
this.lyric = lyric; this.lyric = lyric;
} }
// 获取priority属性的方法在涉及歌词优先级相关的业务逻辑如展示顺序等可获取这个优先级数值进行判断处理。
public int getPriority() {
return priority;
}
// 设置priority属性的方法外部传入的优先级数值会更新当前对象保存的歌词优先级信息比如调整歌词的展示优先级等情况使用。
public void setPriority(int priority) {
this.priority = priority;
}
// 获取qc属性的方法虽然不清楚具体用途但在涉及到这个列表相关的业务逻辑如果是质量检查相关可能是获取检查结果等情况时可获取该列表。
public List<?> getQc() {
return qc;
}
// 设置qc属性的方法外部传入的对应类型的列表会更新当前DataBean对象保存的这个属性信息比如更新质量检查结果列表等情况使用。
public void setQc(List<?> qc) {
this.qc = qc;
}
// 获取tab属性的方法在涉及和歌词展示页面的标签页等相关操作如果是用于标识标签页的话可能是获取当前所在标签页等情况时获取该属性值。
public int getTab() {
return tab;
}
// 设置tab属性的方法传入的整数值会更新当前对象保存的这个和标签页相关的标识信息比如切换歌词展示的标签页等情况使用。
public void setTab(int tab) {
this.tab = tab;
}
// 获取taglist属性的方法在涉及歌词分类、标记相关的业务逻辑如果是歌词标签列表可能是获取所有标签进行展示、筛选等操作时获取该列表。
public List<?> getTaglist() {
return taglist;
}
// 设置taglist属性的方法外部传入的对应类型的列表会更新当前DataBean对象保存的歌词标签列表信息比如更新歌词的分类标签等情况使用。
public void setTaglist(List<?> taglist) {
this.taglist = taglist;
}
// 获取totaltime属性的方法在涉及歌词获取、处理过程总时间相关的业务逻辑比如统计性能、展示耗时等情况时获取该时间数值。
public int getTotaltime() {
return totaltime;
}
// 设置totaltime属性的方法传入的时间数值会更新当前对象保存的这个总时间信息比如重新记录歌词处理耗时等情况使用。
public void setTotaltime(int totaltime) {
this.totaltime = totaltime;
}
// 获取zhida属性的方法用于获取包含歌词相关辅助信息的ZhidaBean对象以便进一步了解歌词的一些属性特点辅助业务处理。
public ZhidaBean getZhida() {
return zhida;
}
// 设置zhida属性的方法传入的ZhidaBean对象会更新当前DataBean对象中保存的歌词辅助信息比如更新歌词的类型等相关属性情况使用。
public void setZhida(ZhidaBean zhida) {
this.zhida = zhida;
}
public static class LyricBean { public static class LyricBean {
/** /**
* curnum : 1 * 便
* curpage : 1
* list : [{"albumid":54250,"albummid":"004YodY33zsWTT","albumname":"感官/世界","albumname_hilight":"感官/世界","alertid":23,"belongCD":0,"cdIdx":6,"chinesesinger":0,"content":"说谎 (거짓말) (《杜拉拉升职记》电视剧插曲|《针尖上的天使》电影主题曲) - 林宥嘉 (Yoga Lin)\\n 词:施人诚\\n 曲:李双飞\\n 是有过几个不错对象\\n 说起来并不寂寞孤单\\n 可能我浪荡让人家不安\\n 才会结果都阵亡\\n 我没有什么阴影魔障\\n 你千万不要放在心上\\n 我又不脆弱何况那算什么伤\\n 反正爱情不就都这样\\n 我没有说谎我何必说谎\\n 你懂我的我对你从来就不会假装\\n 我哪有说谎\\n 请别以为你有多难忘\\n 笑是真的不是我逞强\\n 我好久没来这间餐厅\\n 没想到已经换了装潢\\n 角落那窗口闻的到玫瑰花香\\n 被你一说是有些印象\\n 我没有说谎我何必说谎\\n 你知道的我缺点之一就是很健忘\\n 我哪有说谎\\n 是很感谢今晚的相伴\\n 但我竟然有些不习惯\\n 我没有说谎我何必说谎\\n 爱一个人没爱到难道就会怎么样\\n 别说我说谎\\n 人生已经如此的艰难\\n 有些事情就不要拆穿\\n 我没有说谎是爱情说谎\\n 它带你来骗我说\\n 渴望的有 可能有希望\\n 我没有说谎\\n 祝你做个幸福的新娘\\n 我的心事请你就遗忘","docid":"12084758858355992521","download_url":"http://soso.music.qq.com/fcgi-bin/fcg_download_lrc.q?song=说谎&singer=林宥嘉&down=1&songid=638596&docid=12084758858355992521","interval":266,"isonly":0,"lyric":" 我哪有<em>说谎<\/em>\\n 请别以为你有多难忘\\n 笑是真的不是我逞强\\n","media_mid":"000W95Fk3lAVxV","msgid":14,"nt":3842743252,"pay":{"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200},"preview":{"trybegin":73557,"tryend":104291,"trysize":0},"pubtime":1256832000,"pure":0,"singer":[{"id":11606,"mid":"001f0VyZ1hmWZ1","name":"林宥嘉","name_hilight":"林宥嘉"}],"size128":4264898,"size320":10652912,"sizeape":27291116,"sizeflac":28067207,"sizeogg":5800490,"songid":638596,"songmid":"000W95Fk3lAVxV","songname":"说谎","songname_hilight":"<em>说谎<\/em>","strMediaMid":"000W95Fk3lAVxV","stream":1,"switch":636675,"t":1,"tag":11,"type":0,"ver":0,"vid":""}]
* totalnum : 246
*/ */
private int curnum; private int curnum;
/**
* 便
*/
private int curpage; private int curpage;
/**
* 便
*/
private int totalnum; private int totalnum;
/**
* List<ListBean>ListBean
*/
private List<ListBean> list; private List<ListBean> list;
// 获取当前页歌词数量的方法,供在歌词分页展示等业务逻辑中获取当前页歌词数量信息,进行相关处理(如判断是否显示翻页按钮等)。
public int getCurnum() { public int getCurnum() {
return curnum; return curnum;
} }
// 设置当前页歌词数量的方法外部传入的当前页歌词数量数值会更新当前LyricBean对象保存的对应信息比如重新分页后更新数量情况。
public void setCurnum(int curnum) { public void setCurnum(int curnum) {
this.curnum = curnum; this.curnum = curnum;
} }
// 获取当前页码的方法,用于在歌词展示界面等场景下获取当前所在的页码信息,方便用户知晓位置以及进行翻页操作等。
public int getCurpage() { public int getCurpage() {
return curpage; return curpage;
} }
// 设置当前页码的方法,传入的页码数值会更新当前对象保存的当前页码信息,比如用户手动翻页后更新当前页码显示等情况使用。
public void setCurpage(int curpage) { public void setCurpage(int curpage) {
this.curpage = curpage; this.curpage = curpage;
} }
// 获取总歌词数量的方法,在涉及歌词整体篇幅展示、分页计算等业务逻辑中获取总数量信息进行相应处理。
public int getTotalnum() { public int getTotalnum() {
return totalnum; return totalnum;
} }
// 设置总歌词数量的方法外部传入的总歌词数量数值会更新当前LyricBean对象保存的对应信息比如歌词有新增或删减后更新总数量。
public void setTotalnum(int totalnum) { public void setTotalnum(int totalnum) {
this.totalnum = totalnum; this.totalnum = totalnum;
} }
// 获取歌词列表信息的方法,用于获取包含具体每段歌词详细信息的列表对象,以便进一步操作每段歌词的具体数据,如展示歌词文本等。
public List<ListBean> getList() { public List<ListBean> getList() {
return list; return list;
} }
// 设置歌词列表信息的方法传入的包含歌词详细信息的列表会更新当前LyricBean对象中保存的歌词列表内容比如获取到新的歌词分段信息后替换。
public void setList(List<ListBean> list) { public void setList(List<ListBean> list) {
this.list = list; this.list = list;
} }
public static class ListBean { public static class ListBean {
/** /**
* albumid : 54250 * 01
* albummid : 004YodY33zsWTT
* albumname : /
* albumname_hilight : /
* alertid : 23
* belongCD : 0
* cdIdx : 6
* chinesesinger : 0
* content : () (|使) - (Yoga Lin)\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n 穿\n \n \n \n \n \n
* docid : 12084758858355992521
* download_url : http://soso.music.qq.com/fcgi-bin/fcg_download_lrc.q?song=说谎&singer=林宥嘉&down=1&songid=638596&docid=12084758858355992521
* interval : 266
* isonly : 0
* lyric : <em></em>\n \n \n
* media_mid : 000W95Fk3lAVxV
* msgid : 14
* nt : 3842743252
* pay : {"payalbum":0,"payalbumprice":0,"paydownload":1,"payinfo":1,"payplay":0,"paytrackmouth":1,"paytrackprice":200}
* preview : {"trybegin":73557,"tryend":104291,"trysize":0}
* pubtime : 1256832000
* pure : 0
* singer : [{"id":11606,"mid":"001f0VyZ1hmWZ1","name":"林宥嘉","name_hilight":"林宥嘉"}]
* size128 : 4264898
* size320 : 10652912
* sizeape : 27291116
* sizeflac : 28067207
* sizeogg : 5800490
* songid : 638596
* songmid : 000W95Fk3lAVxV
* songname :
* songname_hilight : <em></em>
* strMediaMid : 000W95Fk3lAVxV
* stream : 1
* switch : 636675
* t : 1
* tag : 11
* type : 0
* ver : 0
* vid :
*/ */
private int chinesesinger; private int chinesesinger;
/**
*
*/
private String content; private String content;
/**
* 便使
*/
private int interval; private int interval;
/**
* 线
*/
private int pubtime; private int pubtime;
/**
* OGGOGG
*/
private int sizeogg; private int sizeogg;
/**
* ID便便
*/
private int songid; private int songid;
/**
*
*/
private String songmid; private String songmid;
/**
*
*/
private String songname; private String songname;
/**
*
*/
@SerializedName("switch") @SerializedName("switch")
private int switchX; private int switchX;
/**
*
*/
private int t; private int t;
/**
*
*/
private int tag; private int tag;
/**
*
*/
private int type; private int type;
/**
*
*/
private int ver; private int ver;
/**
* MV
*/
private String vid; private String vid;
/**
* List<SingerBean>SingerBean
*/
private List<SingerBean> singer; private List<SingerBean> singer;
// 获取是否是中文歌手标识的方法,在涉及根据歌手语言属性判断歌词相关情况(如筛选中文歌词等)的业务逻辑中获取该标识值进行判断处理。
public int getChinesesinger() { public int getChinesesinger() {
return chinesesinger; return chinesesinger;
} }
// 设置是否是中文歌手标识的方法,外部传入的标识数值会更新当前对象保存的这个和歌手语言属性相关的标识信息,比如歌曲演唱者变更等情况使用。
public void setChinesesinger(int chinesesinger) { public void setChinesesinger(int chinesesinger) {
this.chinesesinger = chinesesinger; this.chinesesinger = chinesesinger;
} }
// 获取歌词具体内容的方法,用于在歌词展示界面等场景下获取并展示具体的歌词文本内容给用户查看。
public String getContent() { public String getContent() {
return content; return content;
} }
// 设置歌词具体内容的方法,传入的歌词文本字符串会更新当前对象保存的歌词内容信息,比如歌词有修改、更新版本后替换文本内容。
public void setContent(String content) { public void setContent(String content) {
this.content = content; this.content = content;
} }
// 获取歌词间隔时长相关信息的方法,在歌词滚动展示等涉及时间间隔处理的业务逻辑中获取该时长数值进行相应操作。
public int getInterval() { public int getInterval() {
return interval; return interval;
} }
// 设置歌词间隔时长相关信息的方法,外部传入的时长数值会更新当前对象保存的这个间隔时长信息,比如调整歌词滚动间隔等情况使用。
public void setInterval(int interval) { public void setInterval(int interval) {
this.interval = interval; this.interval = interval;
} }
// 获取歌曲发布时间的方法,在展示歌词对应歌曲的发布历史等情况时获取该时间数值进行展示等操作。
public int getPubtime() { public int getPubtime() {
return pubtime; return pubtime;
} }
// 设置歌曲发布时间的方法,传入的发布时间数值会更新当前对象保存的歌曲发布时间信息,比如歌曲信息更新后修改发布时间等情况使用。
public void setPubtime(int pubtime) { public void setPubtime(int pubtime) {
this.pubtime = pubtime; this.pubtime = pubtime;
} }
// 获取OGG格式歌曲文件大小的方法在涉及歌曲资源大小统计等和歌词关联的歌曲整体信息处理时获取该文件大小数值进行相关操作。
public int getSizeogg() { public int getSizeogg() {
return sizeogg; return sizeogg;
} }
// 设置OGG格式歌曲文件大小的方法外部传入的文件大小数值会更新当前对象保存的这个文件大小信息比如歌曲文件格式转换后更新大小情况。
public void setSizeogg(int sizeogg) { public void setSizeogg(int sizeogg) {
this.sizeogg = sizeogg; this.sizeogg = sizeogg;
} }
// 获取歌曲唯一标识ID的方法在将歌词准确对应到具体歌曲、根据歌曲ID进行歌词相关业务操作如查询、更新等时获取该ID数值进行处理。
public int getSongid() { public int getSongid() {
return songid; return songid;
} }
// 设置歌曲唯一标识ID的方法传入的歌曲ID数值会更新当前对象保存的歌曲唯一标识信息比如歌曲数据更新后修改ID等情况使用。
public void setSongid(int songid) { public void setSongid(int songid) {
this.songid = songid; this.songid = songid;
} }
// 获取歌曲中间标识字符串的方法,在涉及歌曲相关的特定业务操作(比如和某些接口交互、查询歌曲详细信息等)中,
// 可能会用到这个标识来准确识别歌曲,方便外部代码获取该标识用于相关业务逻辑处理。
public String getSongmid() { public String getSongmid() {
return songmid; return songmid;
} }
// 设置歌曲中间标识字符串的方法外部传入的songmid参数会更新当前对象保存的歌曲中间标识信息
// 例如在从服务端获取到新的歌曲标识数据后,可通过此方法进行设置,以保持数据的准确性和一致性。
public void setSongmid(String songmid) { public void setSongmid(String songmid) {
this.songmid = songmid; this.songmid = songmid;
} }
// 获取歌曲名称的方法,用于在界面上展示给用户看具体是哪首歌曲,或者在一些业务处理中(如歌词关联、播放记录统计等)
// 根据歌曲名称进行相关操作时,获取该名称信息。
public String getSongname() { public String getSongname() {
return songname; return songname;
} }
// 设置歌曲名称的方法传入的songname参数会替换当前对象中保存的歌曲名称比如歌曲重命名或者从外部数据源更新歌曲名称时使用。
public void setSongname(String songname) { public void setSongname(String songname) {
this.songname = songname; this.songname = songname;
} }
// 获取可能是某种切换相关标识的方法,比如涉及播放源切换、歌词展示版本切换等情况时,
// 通过这个标识来判断是否需要切换以及切换的相关状态,具体含义依赖业务场景设定,外部可获取该标识进行相应逻辑判断。
public int getSwitchX() { public int getSwitchX() {
return switchX; return switchX;
} }
// 设置切换相关标识的方法将传入的switchX参数赋值给对应属性更新歌曲或相关业务逻辑中涉及的这个切换相关标识信息
// 例如在发生需要更新切换状态的业务场景时调用此方法来改变标识值。
public void setSwitchX(int switchX) { public void setSwitchX(int switchX) {
this.switchX = switchX; this.switchX = switchX;
} }
// 获取可能是某种类型标识的方法,例如歌曲风格类型、歌词类型等也许会用不同值来区分,具体含义按业务逻辑确定,
// 在业务处理中可根据这个标识来进行相应的不同处理逻辑,外部代码可获取该标识进行相关判断操作。
public int getT() { public int getT() {
return t; return t;
} }
// 设置某种类型标识的方法传入的t参数会更新当前对象对应的属性改变这个类型标识的取值
// 例如在歌曲类型变更或者根据业务规则重新设定类型标识时使用此方法来更新数据。
public void setT(int t) { public void setT(int t) {
this.t = t; this.t = t;
} }
// 获取标签标识的方法,在歌曲打不同标签分类(如热门、经典等)、歌词按标签筛选等业务场景下,
// 通过这个标识来知晓歌曲(或与之关联的歌词等)所属的标签类别,不同值代表不同标签含义,外部可据此进行分类相关操作。
public int getTag() { public int getTag() {
return tag; return tag;
} }
// 设置标签标识的方法外部传入的tag参数会赋值给对应属性以此更新歌曲及相关业务元素的标签分类标识
// 比如后台管理端对歌曲标签进行重新编辑后,通过此方法将新标签同步到对象中。
public void setTag(int tag) { public void setTag(int tag) {
this.tag = tag; this.tag = tag;
} }
// 获取歌曲类型标识的方法,比如区分是原创、翻唱、伴奏等不同类型的歌曲,业务中根据这个标识来进行相应的不同处理逻辑,
// 例如在播放列表展示、推荐算法中会依据歌曲类型进行不同的展示和推荐策略,外部可获取此标识用于相关业务逻辑判断。
public int getType() { public int getType() {
return type; return type;
} }
// 设置歌曲类型标识的方法将传入的type参数赋值给对应属性改变歌曲的类型标识取值更新其类型相关信息
// 比如歌曲的创作属性发生变化后,通过此方法来更新类型标识以反映实际情况。
public void setType(int type) { public void setType(int type) {
this.type = type; this.type = type;
} }
// 获取版本相关标识的方法,比如歌曲的版本号、歌词的版本号等,用于在业务处理中判断歌曲(或歌词等)是否是不同版本,
// 进而进行对应的版本相关操作,例如版本更新提示、不同版本切换等,外部可获取该标识来做相应处理。
public int getVer() { public int getVer() {
return ver; return ver;
} }
// 设置版本相关标识的方法传入的ver参数会更新当前对象的版本标识属性改变歌曲或相关业务元素对应的版本相关信息
// 比如在检测到有新版本发布后,调用此方法将版本标识更新为最新值。
public void setVer(int ver) { public void setVer(int ver) {
this.ver = ver; this.ver = ver;
} }
// 获取可能是歌曲视频相关的标识比如歌曲对应的MV等视频的标识或者用于关联视频资源的标识等的方法
// 虽然主要和视频相关,但在这里作为歌曲整体信息一部分与歌词等相关联,具体含义看业务逻辑,外部可获取该标识做进一步操作。
public String getVid() { public String getVid() {
return vid; return vid;
} }
// 设置歌曲视频相关标识的方法传入的vid参数会更新当前对象保存的这个视频相关标识信息
// 例如在视频资源链接变更或者新关联了视频资源后,通过此方法来更新对应的标识值。
public void setVid(String vid) { public void setVid(String vid) {
this.vid = vid; this.vid = vid;
} }
// 获取参与演唱这首歌曲的歌手信息列表的方法,方便在业务逻辑中获取所有参与演唱的歌手详细情况,
// 比如展示歌手名字、根据歌手信息进行关联推荐等操作,外部可获取该列表来处理歌手相关业务逻辑。
public List<SingerBean> getSinger() { public List<SingerBean> getSinger() {
return singer; return singer;
} }
// 设置参与演唱这首歌曲的歌手信息列表的方法,传入的歌手信息列表参数会替换当前对象保存的歌手列表,
// 例如在歌曲的演唱者信息发生变化(新增、删减歌手等情况)时,通过此方法来更新歌手相关数据。
public void setSinger(List<SingerBean> singer) { public void setSinger(List<SingerBean> singer) {
this.singer = singer; this.singer = singer;
} }
// 内部静态类SingerBean用于详细封装每个歌手对应的各项具体信息方便对歌手个体的信息进行管理和操作。
public static class SingerBean { public static class SingerBean {
/** /**
* id : 11606 * IDID
* mid : 001f0VyZ1hmWZ1 *
* name :
* name_hilight :
*/ */
private int id; private int id;
/**
*
*
*/
private String mid; private String mid;
/**
*
*/
private String name; private String name;
/**
*
* 便
*/
private String name_hilight; private String name_hilight;
// 获取歌手唯一标识ID的方法供业务逻辑中需要根据歌手ID进行相关操作如查询、关联、判断等时使用外部可获取该ID进行相应处理。
public int getId() { public int getId() {
return id; return id;
} }
// 设置歌手唯一标识ID的方法将传入的id参数赋值给当前对象的id属性更新歌手的唯一标识信息
// 例如在从数据源同步歌手最新ID或者修改歌手ID时调用此方法进行更新操作。
public void setId(int id) { public void setId(int id) {
this.id = id; this.id = id;
} }
// 获取歌手中间标识字符串的方法,方便在涉及特定业务环节(如接口调用中识别歌手、与外部系统交互歌手数据等)使用这个标识进行相关操作,
// 外部可获取该标识用于相应的业务逻辑处理。
public String getMid() { public String getMid() {
return mid; return mid;
} }
// 设置歌手中间标识字符串的方法把传入的mid参数赋值给对应属性改变歌手的这个标识信息
// 比如在获取到新的歌手中间标识数据后,通过此方法来更新保存的值。
public void setMid(String mid) { public void setMid(String mid) {
this.mid = mid; this.mid = mid;
} }
// 获取歌手名称的方法,用于在各种界面展示(如播放界面、歌手列表等)、业务逻辑判断(比如根据歌手分类歌曲、歌词关联歌手等)等场景下获取歌手的名字信息,
// 外部可获取该名称进行展示或相关处理。
public String getName() { public String getName() {
return name; return name;
} }
// 设置歌手名称的方法传入的name参数会更新当前对象的name属性改变歌手对应的显示名称
// 例如歌手改名或者从数据源同步最新名称时使用此方法进行更新。
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
// 获取歌手名称高亮显示版本的方法,在需要突出显示歌手名称中关键词的场景下(如搜索结果高亮显示、歌词中突出歌手名等),
// 获取这个高亮处理后的名称进行展示,外部可获取该高亮名称用于相应的展示需求。
public String getName_hilight() { public String getName_hilight() {
return name_hilight; return name_hilight;
} }
// 设置歌手名称高亮显示版本的方法,将传入的高亮名称参数赋值给对应属性,更新歌手名称的高亮显示内容,
// 比如在更新了高亮显示规则或者从数据源获取到新的高亮名称数据后,通过此方法来更新保存的值。
public void setName_hilight(String name_hilight) { public void setName_hilight(String name_hilight) {
this.name_hilight = name_hilight; this.name_hilight = name_hilight;
} }
} }
}
}
}
}

@ -1,12 +1,26 @@
package com.example.musicplayer.event; package com.example.musicplayer.event;
// 该类的文档注释,提供了类的作者、创建时间和类的基本描述信息
/** /**
* <pre> * <pre>
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
*
*
* ID
* </pre> * </pre>
*/ */
// 定义一个名为 AlbumCollectionEvent 的公共类
public class AlbumCollectionEvent { public class AlbumCollectionEvent {
// 目前该类为空,没有任何成员变量和方法,可在后续开发中添加。
// 例如,可能会添加以下成员变量:
// private int albumId; 用于存储收藏的专辑的唯一标识符
// private Date collectionTime; 用于存储用户收藏该专辑的时间
// 可能会添加以下方法:
// public void notifyUI() { } 用于通知用户界面更新收藏状态
// public void syncWithServer() { } 用于将收藏信息同步到服务器
} }

@ -1,5 +1,6 @@
package com.example.musicplayer.event; package com.example.musicplayer.event;
// 导入 DownloadInfo 类,因为 DownloadEvent 类中使用了该类
import com.example.musicplayer.entiy.DownloadInfo; import com.example.musicplayer.entiy.DownloadInfo;
/** /**
@ -7,41 +8,60 @@ import com.example.musicplayer.entiy.DownloadInfo;
* author : * author :
* time : 2019/09/16 * time : 2019/09/16
* desc : * desc :
*
*
*
* DownloadEvent
*
* </pre> * </pre>
*/ */
public class DownloadEvent { public class DownloadEvent {
private int downloadStatus;//下载的状态 // 存储下载的状态,可用于表示下载是否开始、正在进行、完成、失败等不同状态
private int downloadStatus;
// 存储下载信息,如文件大小、下载速度、已下载量等
private DownloadInfo downloadInfo; private DownloadInfo downloadInfo;
// 存储在下载列表或队列中的位置,方便进行位置相关的操作,比如更新 UI 中的下载进度条位置
private int position; private int position;
// 构造函数,仅接收下载状态作为参数
public DownloadEvent(int status){ public DownloadEvent(int status){
// 将传入的状态存储到 downloadStatus 成员变量中
downloadStatus = status; downloadStatus = status;
} }
// 构造函数,接收下载状态和 DownloadInfo 对象作为参数
public DownloadEvent(int status, DownloadInfo downloadInfo){ public DownloadEvent(int status, DownloadInfo downloadInfo){
// 将传入的状态存储到 downloadStatus 成员变量中
downloadStatus = status; downloadStatus = status;
// 将传入的 DownloadInfo 对象存储到 downloadInfo 成员变量中
this.downloadInfo = downloadInfo; this.downloadInfo = downloadInfo;
} }
// 构造函数,接收下载状态和位置作为参数
public DownloadEvent(int status,int position){ public DownloadEvent(int status,int position){
// 将传入的状态存储到 downloadStatus 成员变量中
downloadStatus = status; downloadStatus = status;
// 将传入的位置存储到 position 成员变量中
this.position = position; this.position = position;
} }
// 获取位置的方法
public int getPosition() { public int getPosition() {
return position; return position;
} }
// 设置位置的方法
public void setPosition(int position) { public void setPosition(int position) {
this.position = position; this.position = position;
} }
// 获取下载状态的方法
public int getDownloadStatus() { public int getDownloadStatus() {
return downloadStatus; return downloadStatus;
} }
// 获取下载信息的方法
public DownloadInfo getDownloadInfo() { public DownloadInfo getDownloadInfo() {
return downloadInfo; return downloadInfo;
} }

@ -5,8 +5,16 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class OnlineSongChangeEvent { public class OnlineSongChangeEvent {
// 目前该类为空,可在后续开发中添加成员变量和方法。
} }

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/20 * time : 2019/07/20
* desc : * desc :
*
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class OnlineSongErrorEvent { public class OnlineSongErrorEvent {
// 目前该类为空,可在后续开发中添加成员变量和方法。
} }

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class SongAlbumEvent { public class SongAlbumEvent {
// 目前该类为空,可在后续开发中添加成员变量和方法。
} }

@ -5,15 +5,25 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class SongCollectionEvent { public class SongCollectionEvent {
// 存储歌曲的收藏状态true 表示已收藏false 表示未收藏
private boolean isLove; private boolean isLove;
// 构造函数,接收一个布尔值表示收藏状态
public SongCollectionEvent(boolean isLove){ public SongCollectionEvent(boolean isLove){
// 将传入的收藏状态存储到 isLove 成员变量中
this.isLove = isLove; this.isLove = isLove;
} }
// 获取歌曲的收藏状态的方法
public boolean isLove() { public boolean isLove() {
return isLove; return isLove;
} }

@ -5,8 +5,16 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/09/18 * time : 2019/09/18
* desc : * desc :
*
*
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class SongDownloadedEvent { public class SongDownloadedEvent {
// 该类目前为空,后续可根据具体的需求添加成员变量和方法。
} }

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class SongHistoryEvent { public class SongHistoryEvent {
// 目前该类为空,后续可根据具体需求添加成员变量和方法。
} }

@ -5,19 +5,30 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/09/20 * time : 2019/09/20
* desc : * desc :
*
*
*
*
* "type"
*
* "type" 便
* </pre> * </pre>
*/ */
public class SongListNumEvent { public class SongListNumEvent {
// 存储歌曲列表数量变化的类型,可用于表示数量的增减及变化量
private int type; private int type;
// 构造函数,接收一个整数表示歌曲列表数量变化的类型,并将其存储在 type 变量中
public SongListNumEvent(int type){ public SongListNumEvent(int type){
this.type = type; this.type = type;
} }
// 获取歌曲列表数量变化类型的方法
public int getType() { public int getType() {
return type; return type;
} }
// 设置歌曲列表数量变化类型的方法
public void setType(int type) { public void setType(int type) {
this.type = type; this.type = type;
} }

@ -5,8 +5,16 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class SongLocalEvent { public class SongLocalEvent {
// 目前该类为空,后续可根据具体需求添加成员变量和方法。
} }

@ -5,8 +5,15 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public class SongLocalSizeChangeEvent { public class SongLocalSizeChangeEvent {
// 该类目前为空,可在后续开发中添加成员变量和方法。
} }

@ -5,16 +5,27 @@ package com.example.musicplayer.event;
* author : * author :
* time : 2019/07/19 * time : 2019/07/19
* desc : * desc :
*
*
*
* songStatus
*
*
*
*
* </pre> * </pre>
*/ */
public class SongStatusEvent { public class SongStatusEvent {
// 存储歌曲的播放状态,不同的整数值可以表示不同的播放状态,
// 例如0 表示停止1 表示正在播放2 表示暂停等,具体可根据应用需求定义
private int songStatus; private int songStatus;
// 构造函数,接收一个整数表示歌曲的播放状态,并将其存储在 songStatus 变量中
public SongStatusEvent(int songStatus){ public SongStatusEvent(int songStatus){
this.songStatus = songStatus; this.songStatus = songStatus;
} }
// 获取歌曲播放状态的方法
public int getSongStatus() { public int getSongStatus() {
return songStatus; return songStatus;
} }

@ -25,92 +25,164 @@ import io.reactivex.Observable;
* author : * author :
* time : 2019/07/15 * time : 2019/07/15
* desc : * desc :
*
* NetworkHelperDbHelper PreferencesHelper
*
* NetworkHelperImplDbHelperImpl PreferencesHelperImpl
*
* </pre> * </pre>
*/ */
public class DataModel implements NetworkHelper, DbHelper, PreferencesHelper {
public class DataModel implements NetworkHelper, DbHelper,PreferencesHelper { // 声明一个 NetworkHelperImpl 类型的私有成员变量 mNetworkHelper用于存储网络操作的实现类对象
private NetworkHelperImpl mNetworkHelper; private NetworkHelperImpl mNetworkHelper;
// 声明一个 DbHelperImpl 类型的私有成员变量 mDbHelper用于存储数据库操作的实现类对象
private DbHelperImpl mDbHelper; private DbHelperImpl mDbHelper;
// 声明一个 PreferencesHelperImpl 类型的私有成员变量 mPreferencesHelper用于存储偏好设置操作的实现类对象
private PreferencesHelperImpl mPreferencesHelper; private PreferencesHelperImpl mPreferencesHelper;
public DataModel(NetworkHelperImpl networkHelper,DbHelperImpl dbHelper,PreferencesHelperImpl preferencesHelper){ // 构造函数,用于创建 DataModel 对象
// 参数 networkHelperNetworkHelperImpl 类型的对象,提供网络操作的具体实现
// 参数 dbHelperDbHelperImpl 类型的对象,提供数据库操作的具体实现
// 参数 preferencesHelperPreferencesHelperImpl 类型的对象,提供偏好设置操作的具体实现
public DataModel(NetworkHelperImpl networkHelper, DbHelperImpl dbHelper, PreferencesHelperImpl preferencesHelper) {
// 将传入的网络操作实现类对象存储在 mNetworkHelper 成员变量中
mNetworkHelper = networkHelper; mNetworkHelper = networkHelper;
// 将传入的数据库操作实现类对象存储在 mDbHelper 成员变量中
mDbHelper = dbHelper; mDbHelper = dbHelper;
// 将传入的偏好设置操作实现类对象存储在 mPreferencesHelper 成员变量中
mPreferencesHelper = preferencesHelper; mPreferencesHelper = preferencesHelper;
} }
// 实现 NetworkHelper 接口中的 getAlbumSong 方法,用于获取专辑歌曲信息
// 参数 id专辑歌曲的唯一标识符用于指定要获取的专辑歌曲
// 返回值:一个 Observable<AlbumSong> 对象,用于异步获取专辑歌曲信息
@Override @Override
public Observable<AlbumSong> getAlbumSong(String id) { public Observable<AlbumSong> getAlbumSong(String id) {
// 调用 mNetworkHelper 的 getAlbumSong 方法来执行获取专辑歌曲的操作
return mNetworkHelper.getAlbumSong(id); return mNetworkHelper.getAlbumSong(id);
} }
// 实现 NetworkHelper 接口中的 search 方法,用于搜索歌曲
// 参数 seek搜索关键字用于指定搜索的内容
// 参数 offset搜索结果的偏移量用于分页等操作
// 返回值:一个 Observable<SearchSong> 对象,用于异步获取搜索歌曲的结果
@Override @Override
public Observable<SearchSong> search(String seek, int offset) { public Observable<SearchSong> search(String seek, int offset) {
// 调用 mNetworkHelper 的 search 方法来执行歌曲搜索操作
return mNetworkHelper.search(seek, offset); return mNetworkHelper.search(seek, offset);
} }
// 实现 NetworkHelper 接口中的 searchAlbum 方法,用于搜索专辑
// 参数 seek搜索关键字用于指定搜索的内容
// 参数 offset搜索结果的偏移量用于分页等操作
// 返回值:一个 Observable<Album> 对象,用于异步获取搜索专辑的结果
@Override @Override
public Observable<Album> searchAlbum(String seek, int offset) { public Observable<Album> searchAlbum(String seek, int offset) {
// 调用 mNetworkHelper 的 searchAlbum 方法来执行专辑搜索操作
return mNetworkHelper.searchAlbum(seek, offset); return mNetworkHelper.searchAlbum(seek, offset);
} }
// 实现 NetworkHelper 接口中的 getLrc 方法,用于获取歌词
// 参数 seek搜索关键字用于指定搜索的歌词内容
// 返回值:一个 Observable<SongLrc> 对象,用于异步获取歌词的结果
@Override @Override
public Observable<SongLrc> getLrc(String seek) { public Observable<SongLrc> getLrc(String seek) {
// 调用 mNetworkHelper 的 getLrc 方法来执行获取歌词的操作
return mNetworkHelper.getLrc(seek); return mNetworkHelper.getLrc(seek);
} }
// 实现 NetworkHelper 接口中的 getOnlineSongLrc 方法,用于获取网络歌曲的歌词
// 参数 songId歌曲的唯一标识符用于指定要获取歌词的网络歌曲
// 返回值:一个 Observable<OnlineSongLrc> 对象,用于异步获取网络歌曲歌词的结果
@Override @Override
public Observable<OnlineSongLrc> getOnlineSongLrc(String songId) { public Observable<OnlineSongLrc> getOnlineSongLrc(String songId) {
// 调用 mNetworkHelper 的 getOnlineSongLrc 方法来执行获取网络歌曲歌词的操作
return mNetworkHelper.getOnlineSongLrc(songId); return mNetworkHelper.getOnlineSongLrc(songId);
} }
// 实现 NetworkHelper 接口中的 getSingerImg 方法,用于获取歌手头像
// 参数 singer歌手的名称用于指定要获取头像的歌手
// 返回值:一个 Observable<SingerImg> 对象,用于异步获取歌手头像的结果
@Override @Override
public Observable<SingerImg> getSingerImg(String singer) { public Observable<SingerImg> getSingerImg(String singer) {
// 调用 mNetworkHelper 的 getSingerImg 方法来执行获取歌手头像的操作
return mNetworkHelper.getSingerImg(singer); return mNetworkHelper.getSingerImg(singer);
} }
// 实现 NetworkHelper 接口中的 getSongUrl 方法,用于获取歌曲的播放地址
// 参数 data与播放地址相关的数据可能包含歌曲的相关信息
// 返回值:一个 Observable<SongUrl> 对象,用于异步获取歌曲播放地址的结果
@Override @Override
public Observable<SongUrl> getSongUrl(String data) { public Observable<SongUrl> getSongUrl(String data) {
// 调用 mNetworkHelper 的 getSongUrl 方法来执行获取歌曲播放地址的操作
return mNetworkHelper.getSongUrl(data); return mNetworkHelper.getSongUrl(data);
} }
// 实现 DbHelper 接口中的 insertAllAlbumSong 方法,用于将专辑歌曲列表插入数据库
// 参数 songList包含专辑歌曲信息的列表其中元素类型为 AlbumSong.DataBean.ListBean
@Override @Override
public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) { public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) {
// 调用 mDbHelper 的 insertAllAlbumSong 方法将专辑歌曲列表插入数据库
mDbHelper.insertAllAlbumSong(songList); mDbHelper.insertAllAlbumSong(songList);
} }
// 实现 DbHelper 接口中的 getLocalMp3Info 方法,用于获取本地 MP3 信息
// 返回值:一个 List<LocalSong> 对象,存储本地歌曲信息
@Override @Override
public List<LocalSong> getLocalMp3Info() { public List<LocalSong> getLocalMp3Info() {
// 调用 mDbHelper 的 getLocalMp3Info 方法来获取本地歌曲信息
return mDbHelper.getLocalMp3Info(); return mDbHelper.getLocalMp3Info();
} }
// 实现 DbHelper 接口中的 saveSong 方法,用于保存本地歌曲到数据库
// 参数 localSongs要保存的本地歌曲列表其中元素类型为 LocalSong
// 返回值:一个 boolean 值,表示保存操作是否成功
@Override @Override
public boolean saveSong(List<LocalSong> localSongs) { public boolean saveSong(List<LocalSong> localSongs) {
// 调用 mDbHelper 的 saveSong 方法来保存本地歌曲
return mDbHelper.saveSong(localSongs); return mDbHelper.saveSong(localSongs);
} }
// 实现 DbHelper 接口中的 queryLove 方法,用于查询歌曲是否被收藏
// 参数 songId歌曲的唯一标识符用于指定要查询的歌曲
// 返回值:一个 boolean 值,表示歌曲是否被收藏
@Override @Override
public boolean queryLove(String songId) { public boolean queryLove(String songId) {
// 调用 mDbHelper 的 queryLove 方法来查询歌曲是否被收藏
return mDbHelper.queryLove(songId); return mDbHelper.queryLove(songId);
} }
// 实现 DbHelper 接口中的 saveToLove 方法,用于将歌曲收藏到数据库
// 参数 song要收藏的歌曲对象类型为 Song
// 返回值:一个 boolean 值,表示收藏操作是否成功
@Override @Override
public boolean saveToLove(Song song) { public boolean saveToLove(Song song) {
// 调用 mDbHelper 的 saveToLove 方法来执行收藏歌曲的操作
return mDbHelper.saveToLove(song); return mDbHelper.saveToLove(song);
} }
// 实现 DbHelper 接口中的 deleteFromLove 方法,用于从收藏中删除歌曲
// 参数 songId歌曲的唯一标识符用于指定要删除收藏的歌曲
// 返回值:一个 boolean 值,表示删除操作是否成功
@Override @Override
public boolean deleteFromLove(String songId) { public boolean deleteFromLove(String songId) {
// 调用 mDbHelper 的 deleteFromLove 方法来执行从收藏中删除歌曲的操作
return mDbHelper.deleteFromLove(songId); return mDbHelper.deleteFromLove(songId);
} }
// 实现 PreferencesHelper 接口中的 setPlayMode 方法,用于设置播放模式
// 参数 mode表示播放模式的整数可用于设置不同的播放模式如顺序播放、随机播放等
@Override @Override
public void setPlayMode(int mode) { public void setPlayMode(int mode) {
// 调用 mPreferencesHelper 的 setPlayMode 方法来设置播放模式
mPreferencesHelper.setPlayMode(mode); mPreferencesHelper.setPlayMode(mode);
} }
// 实现 PreferencesHelper 接口中的 getPlayMode 方法,用于获取播放模式
// 返回值:一个整数,表示当前的播放模式
@Override @Override
public int getPlayMode() { public int getPlayMode() {
// 调用 mPreferencesHelper 的 getPlayMode 方法来获取播放模式
return mPreferencesHelper.getPlayMode(); return mPreferencesHelper.getPlayMode();
} }
} }

@ -12,20 +12,72 @@ import java.util.List;
* author : * author :
* time : 2019/07/16 * time : 2019/07/16
* desc : * desc :
*
*
*
*
*
* </pre> * </pre>
*/ */
public interface DbHelper { public interface DbHelper {
/** /**
* *
* @param songList *
* @param songList AlbumSong.DataBean.ListBean
*
* 便使
*/ */
void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList); void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList);
List<LocalSong> getLocalMp3Info(); //得到本地列表
boolean saveSong(List<LocalSong> localSongs);//将本地音乐放到数据库中
boolean queryLove(String songId);//从数据库查找是否为收藏歌曲 /**
boolean saveToLove(Song song);//收藏歌曲 *
boolean deleteFromLove(String songId);//取消收藏歌曲 * LocalSong
*
*
* @return
*/
List<LocalSong> getLocalMp3Info();
/**
*
*
* @param localSongs LocalSong
*
* 便
* @return true false
*/
boolean saveSong(List<LocalSong> localSongs);
/**
*
*
* @param songId
*
*
* @return true false
*/
boolean queryLove(String songId);
/**
*
*
* @param song Song
*
*
* @return true false
*/
boolean saveToLove(Song song);
/**
*
*
* @param songId
*
*
* @return true false
*/
boolean deleteFromLove(String songId);
} }

@ -22,136 +22,205 @@ import java.util.List;
* author : * author :
* time : 2019/07/16 * time : 2019/07/16
* desc : * desc :
*
* DbHelper
* 使 LitePal
*
* </pre> * </pre>
*/ */
public class DbHelperImpl implements DbHelper { public class DbHelperImpl implements DbHelper {
@Override @Override
public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) { public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) {
// 先删除 OnlineSong 表中的所有数据
LitePal.deleteAll(OnlineSong.class); LitePal.deleteAll(OnlineSong.class);
// 遍历歌曲列表,将每首歌曲的信息存储到 OnlineSong 表中
for (int i = 0; i < songList.size(); i++) { for (int i = 0; i < songList.size(); i++) {
AlbumSong.DataBean.ListBean song = songList.get(i); AlbumSong.DataBean.ListBean song = songList.get(i);
OnlineSong onlineSong = new OnlineSong(); OnlineSong onlineSong = new OnlineSong();
// 设置歌曲的 ID从 1 开始递增
onlineSong.setId(i + 1); onlineSong.setId(i + 1);
// 设置歌曲名称
onlineSong.setName(song.getSongname()); onlineSong.setName(song.getSongname());
// 设置歌手,取第一个歌手的名字
onlineSong.setSinger(song.getSinger().get(0).getName()); onlineSong.setSinger(song.getSinger().get(0).getName());
// 设置歌曲的唯一标识符
onlineSong.setSongId(song.getSongmid()); onlineSong.setSongId(song.getSongmid());
// 设置歌曲时长
onlineSong.setDuration(song.getInterval()); onlineSong.setDuration(song.getInterval());
onlineSong.setPic(Api.ALBUM_PIC + song.getAlbummid()+Api.JPG); // 设置歌曲封面图片的 URL
onlineSong.setPic(Api.ALBUM_PIC + song.getAlbummid() + Api.JPG);
// 暂时设置歌曲的 URL 和歌词为 null
onlineSong.setUrl(null); onlineSong.setUrl(null);
onlineSong.setLrc(null); onlineSong.setLrc(null);
// 保存歌曲信息到数据库
onlineSong.save(); onlineSong.save();
} }
} }
@Override @Override
public List<LocalSong> getLocalMp3Info() { public List<LocalSong> getLocalMp3Info() {
// 创建一个用于存储本地歌曲信息的列表
List<LocalSong> mp3InfoList = new ArrayList<>(); List<LocalSong> mp3InfoList = new ArrayList<>();
getFromDownloadFile(mp3InfoList); //从下载列表中读取歌曲文件 // 从下载列表中读取歌曲文件
getFromDownloadFile(mp3InfoList);
// 查询系统媒体库中的音频文件
Cursor cursor = App.getContext().getContentResolver().query( Cursor cursor = App.getContext().getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.Media.DEFAULT_SORT_ORDER); MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
// 遍历查询结果
for (int i = 0; i < cursor.getCount(); i++) { for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToNext(); cursor.moveToNext();
LocalSong mp3Info = new LocalSong(); LocalSong mp3Info = new LocalSong();
// 获取音乐标题
String title = cursor.getString((cursor String title = cursor.getString((cursor
.getColumnIndex(MediaStore.Audio.Media.TITLE)));//音乐标题 .getColumnIndex(MediaStore.Audio.Media.TITLE)));
// 获取艺术家
String artist = cursor.getString(cursor String artist = cursor.getString(cursor
.getColumnIndex(MediaStore.Audio.Media.ARTIST));//艺术家 .getColumnIndex(MediaStore.Audio.Media.ARTIST));
// 获取时长
long duration = cursor.getLong(cursor long duration = cursor.getLong(cursor
.getColumnIndex(MediaStore.Audio.Media.DURATION));//时长 .getColumnIndex(MediaStore.Audio.Media.DURATION));
// 获取文件大小
long size = cursor.getLong(cursor long size = cursor.getLong(cursor
.getColumnIndex(MediaStore.Audio.Media.SIZE)); //文件大小 .getColumnIndex(MediaStore.Audio.Media.SIZE));
// 获取文件路径
String url = cursor.getString(cursor String url = cursor.getString(cursor
.getColumnIndex(MediaStore.Audio.Media.DATA)); //文件路径 .getColumnIndex(MediaStore.Audio.Media.DATA));
// 获取是否为音乐的标记
int isMusic = cursor.getInt(cursor int isMusic = cursor.getInt(cursor
.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));//是否为音乐 .getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));
if (isMusic != 0) {//只把音乐添加到集合当中 // 只添加音乐文件到集合中
if (isMusic!= 0) {
// 对于文件大小大于 800KB 的文件进行处理
if (size > 1000 * 800) { if (size > 1000 * 800) {
// 注释部分是切割标题,分离出歌曲名和歌手 (本地媒体库读取的歌曲信息不规范) // 对于本地媒体库读取的歌曲信息进行规范处理,分离歌曲名和歌手
if (title.contains("-")) { if (title.contains("-")) {
String[] str = title.split("-"); String[] str = title.split("-");
artist = str[0]; artist = str[0];
title = str[1]; title = str[1];
} }
// 设置歌曲名称
mp3Info.setName(title.trim()); mp3Info.setName(title.trim());
// 设置歌手
mp3Info.setSinger(artist); mp3Info.setSinger(artist);
// 设置时长(单位为秒)
mp3Info.setDuration(duration / 1000); mp3Info.setDuration(duration / 1000);
// 设置文件路径
mp3Info.setUrl(url); mp3Info.setUrl(url);
mp3Info.setSongId(i+""); // 设置歌曲的唯一标识符
mp3Info.setSongId(i + "");
// 将歌曲信息添加到列表中
mp3InfoList.add(mp3Info); mp3InfoList.add(mp3Info);
} }
} }
} }
// 关闭游标
cursor.close(); cursor.close();
// 返回存储本地歌曲信息的列表
return mp3InfoList; return mp3InfoList;
} }
@Override @Override
public boolean saveSong(List<LocalSong> localSongs) { public boolean saveSong(List<LocalSong> localSongs) {
// 先删除 LocalSong 表中的所有数据
LitePal.deleteAll(LocalSong.class); LitePal.deleteAll(LocalSong.class);
// 遍历要保存的本地歌曲列表,将歌曲信息存储到数据库中
for (LocalSong localSong : localSongs) { for (LocalSong localSong : localSongs) {
LocalSong song = new LocalSong(); LocalSong song = new LocalSong();
// 设置歌曲名称
song.setName(localSong.getName()); song.setName(localSong.getName());
// 设置歌手
song.setSinger(localSong.getSinger()); song.setSinger(localSong.getSinger());
// 设置文件路径
song.setUrl(localSong.getUrl()); song.setUrl(localSong.getUrl());
// 设置歌曲的唯一标识符
song.setSongId(localSong.getSongId()); song.setSongId(localSong.getSongId());
// 设置时长
song.setDuration(localSong.getDuration()); song.setDuration(localSong.getDuration());
if(!song.save()) return false; // 保存歌曲信息,如果保存失败返回 false
if (!song.save()) return false;
} }
// 保存成功返回 true
return true; return true;
} }
@Override @Override
public boolean queryLove(String songId) { public boolean queryLove(String songId) {
List<Love> love=LitePal.where("songId=?",songId).find(Love.class); // 根据歌曲的唯一标识符查询收藏歌曲列表
return love.size() != 0; List<Love> love = LitePal.where("songId=?", songId).find(Love.class);
// 如果查询结果不为空,表示歌曲已被收藏,返回 true否则返回 false
return love.size()!= 0;
} }
@Override @Override
public boolean saveToLove(Song song) { public boolean saveToLove(Song song) {
Love love =new Love(); // 创建一个新的收藏歌曲对象
Love love = new Love();
// 设置歌曲名称
love.setName(song.getSongName()); love.setName(song.getSongName());
// 设置歌手
love.setSinger(song.getSinger()); love.setSinger(song.getSinger());
// 设置文件路径
love.setUrl(song.getUrl()); love.setUrl(song.getUrl());
// 设置歌曲封面图片的 URL
love.setPic(song.getImgUrl()); love.setPic(song.getImgUrl());
// 设置时长
love.setDuration(song.getDuration()); love.setDuration(song.getDuration());
// 设置歌曲的唯一标识符
love.setSongId(song.getSongId()); love.setSongId(song.getSongId());
// 设置是否为在线歌曲
love.setOnline(song.isOnline()); love.setOnline(song.isOnline());
// 设置歌曲的 QQ 标识符
love.setQqId(song.getQqId()); love.setQqId(song.getQqId());
// 设置歌曲的媒体标识符
love.setMediaId(song.getMediaId()); love.setMediaId(song.getMediaId());
// 设置是否已下载
love.setDownload(song.isDownload()); love.setDownload(song.isDownload());
// 保存收藏歌曲信息,保存成功返回 true否则返回 false
return love.save(); return love.save();
} }
@Override @Override
public boolean deleteFromLove(String songId) { public boolean deleteFromLove(String songId) {
return LitePal.deleteAll(Love.class,"songId=?",songId) !=0; // 根据歌曲的唯一标识符删除收藏歌曲,删除成功返回 true否则返回 false
return LitePal.deleteAll(Love.class, "songId=?", songId)!= 0;
} }
//从下载列表中读取文件 // 从下载列表中读取文件的方法
private void getFromDownloadFile(List<LocalSong> songList){ private void getFromDownloadFile(List<LocalSong> songList) {
// 获取存储歌曲的目录
File file = new File(Api.STORAGE_SONG_FILE); File file = new File(Api.STORAGE_SONG_FILE);
if(!file.exists()) { // 如果目录不存在,则创建目录
if (!file.exists()) {
file.mkdirs(); file.mkdirs();
return; return;
} }
// 获取目录下的所有文件
File[] subFile = file.listFiles(); File[] subFile = file.listFiles();
// 遍历文件列表
for (File value : subFile) { for (File value : subFile) {
String songFileName = value.getName(); String songFileName = value.getName();
// 提取歌曲文件名(不包含后缀)
String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
// 分割文件名,包含歌手、歌曲名、时长、歌曲唯一标识符和文件大小
String[] songValue = songFile.split("-"); String[] songValue = songFile.split("-");
// 获取文件大小
long size = Long.valueOf(songValue[4]); long size = Long.valueOf(songValue[4]);
//如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合 // 如果文件大小不等于实际大小,说明歌曲未下载完成,跳过该歌曲
if(size != value.length()) continue; if (size!= value.length()) continue;
LocalSong song =new LocalSong(); LocalSong song = new LocalSong();
// 设置歌手
song.setSinger(songValue[0]); song.setSinger(songValue[0]);
// 设置歌曲名称
song.setName(songValue[1]); song.setName(songValue[1]);
// 设置时长
song.setDuration(Long.valueOf(songValue[2])); song.setDuration(Long.valueOf(songValue[2]));
// 设置歌曲的唯一标识符
song.setSongId(songValue[3]); song.setSongId(songValue[3]);
// 设置文件路径
song.setUrl(Api.STORAGE_SONG_FILE + songFileName); song.setUrl(Api.STORAGE_SONG_FILE + songFileName);
// 将歌曲信息添加到歌曲列表中
songList.add(song); songList.add(song);
} }
} }

@ -16,16 +16,90 @@ import io.reactivex.Observable;
* author : * author :
* time : 2019/07/15 * time : 2019/07/15
* desc : * desc :
*
*
* RxJava Observable
*
*
* </pre> * </pre>
*/ */
public interface NetworkHelper { public interface NetworkHelper {
Observable<AlbumSong> getAlbumSong(String id); //得到专辑 /**
Observable<SearchSong> search(String seek, int offset); //搜索歌曲 *
Observable<Album> searchAlbum(String seek,int offset);//搜索照片 *
Observable<SongLrc> getLrc(String seek);//获取歌词 * @param id
Observable<OnlineSongLrc> getOnlineSongLrc(String songId);//获取网络歌曲的歌词 *
Observable<SingerImg> getSingerImg(String singer);//获取歌手头像 * Observable<AlbumSong>
Observable<SongUrl> getSongUrl(String data);//获取播放地址 *
} *
*/
Observable<AlbumSong> getAlbumSong(String id);
/**
*
*
* @param seek
* @param offset
*
* Observable<SearchSong>
*
*
*/
Observable<SearchSong> search(String seek, int offset);
/**
*
*
* @param seek
* @param offset
*
* Observable<Album>
*
*
*/
Observable<Album> searchAlbum(String seek,int offset);
/**
*
*
* @param seek
*
* Observable<SongLrc>
*
*
*/
Observable<SongLrc> getLrc(String seek);
/**
*
*
* @param songId
*
* Observable<OnlineSongLrc>
*
*
*/
Observable<OnlineSongLrc> getOnlineSongLrc(String songId);
/**
*
*
* @param singer
*
* Observable<SingerImg>
*
*
*/
Observable<SingerImg> getSingerImg(String singer);
/**
*
*
* @param data
*
* Observable<SongUrl>
*
* 便
*/
Observable<SongUrl> getSongUrl(String data);
}

@ -17,46 +17,60 @@ import io.reactivex.Observable;
* author : * author :
* time : 2019/07/15 * time : 2019/07/15
* desc : * desc :
*
* NetworkHelper
* RetrofitService
* RetrofitService
*
* RxJava Observable
* </pre> * </pre>
*/ */
public class NetworkHelperImpl implements NetworkHelper { public class NetworkHelperImpl implements NetworkHelper {
// 存储 RetrofitService 实例,用于执行网络请求
private RetrofitService mRetrofitService; private RetrofitService mRetrofitService;
// 构造函数,接收 RetrofitService 实例并存储
public NetworkHelperImpl(RetrofitService retrofitService){ public NetworkHelperImpl(RetrofitService retrofitService){
mRetrofitService = retrofitService; mRetrofitService = retrofitService;
} }
// 获取专辑歌曲的方法,调用 RetrofitService 的 getAlbumSong 方法
@Override @Override
public Observable<AlbumSong> getAlbumSong(String id) { public Observable<AlbumSong> getAlbumSong(String id) {
return mRetrofitService.getAlbumSong(id); return mRetrofitService.getAlbumSong(id);
} }
// 搜索歌曲的方法,调用 RetrofitService 的 search 方法
@Override @Override
public Observable<SearchSong> search(String seek, int offset) { public Observable<SearchSong> search(String seek, int offset) {
return mRetrofitService.search(seek, offset); return mRetrofitService.search(seek, offset);
} }
// 搜索专辑的方法,调用 RetrofitService 的 searchAlbum 方法
@Override @Override
public Observable<Album> searchAlbum(String seek, int offset) { public Observable<Album> searchAlbum(String seek, int offset) {
return mRetrofitService.searchAlbum(seek, offset); return mRetrofitService.searchAlbum(seek, offset);
} }
// 获取歌词的方法,调用 RetrofitService 的 getLrc 方法
@Override @Override
public Observable<SongLrc> getLrc(String seek) { public Observable<SongLrc> getLrc(String seek) {
return mRetrofitService.getLrc(seek); return mRetrofitService.getLrc(seek);
} }
// 获取网络歌曲歌词的方法,调用 RetrofitService 的 getOnlineSongLrc 方法
@Override @Override
public Observable<OnlineSongLrc> getOnlineSongLrc(String songId) { public Observable<OnlineSongLrc> getOnlineSongLrc(String songId) {
return mRetrofitService.getOnlineSongLrc(songId); return mRetrofitService.getOnlineSongLrc(songId);
} }
// 获取歌手头像的方法,调用 RetrofitService 的 getSingerImg 方法
@Override @Override
public Observable<SingerImg> getSingerImg(String singer) { public Observable<SingerImg> getSingerImg(String singer) {
return mRetrofitService.getSingerImg(singer); return mRetrofitService.getSingerImg(singer);
} }
// 获取歌曲播放地址的方法,调用 RetrofitService 的 getSongUrl 方法
@Override @Override
public Observable<SongUrl> getSongUrl(String data) { public Observable<SongUrl> getSongUrl(String data) {
return mRetrofitService.getSongUrl(data); return mRetrofitService.getSongUrl(data);

@ -18,81 +18,112 @@ import retrofit2.converter.gson.GsonConverterFactory;
* <pre> * <pre>
* author : * author :
* time : 2019/07/15 * time : 2019/07/15
* desc : Retrofit便 * desc : Retrofit便
*
* Retrofit Retrofit
* Retrofit
*
* 使 OkHttpClient
* 使 Gson JSON
* RxJava2
* </pre> * </pre>
*/ */
public class RetrofitFactory { public class RetrofitFactory {
// 存储 OkHttpClient 实例,用于网络请求
private static OkHttpClient sOkHttpClient; private static OkHttpClient sOkHttpClient;
// 存储通用的 Retrofit 实例
private static Retrofit sRetrofit; private static Retrofit sRetrofit;
// 存储用于歌曲播放地址请求的 Retrofit 实例
private static Retrofit songUrlRetrofit; private static Retrofit songUrlRetrofit;
// 存储用于歌手照片请求的 Retrofit 实例
private static Retrofit sSingerPicRetrofit; private static Retrofit sSingerPicRetrofit;
// 创建网络请求Observable // 创建网络请求 Observable 的方法,使用通用的 Retrofit 实例
public static RetrofitService createRequest() { public static RetrofitService createRequest() {
return getRetrofit().create(RetrofitService.class); return getRetrofit().create(RetrofitService.class);
} }
// 创建用于歌手相关请求的网络请求 Observable 的方法,使用歌手照片的 Retrofit 实例
public static RetrofitService createRequestOfSinger() { public static RetrofitService createRequestOfSinger() {
return getRetrofitOfSinger().create(RetrofitService.class); return getRetrofitOfSinger().create(RetrofitService.class);
} }
public static RetrofitService createRequestOfSongUrl(){ // 创建用于歌曲播放地址请求的网络请求 Observable 的方法,使用歌曲播放地址的 Retrofit 实例
public static RetrofitService createRequestOfSongUrl() {
return getRetrofitOfSongUrl().create(RetrofitService.class); return getRetrofitOfSongUrl().create(RetrofitService.class);
} }
// 配置Retrofit
// 配置通用的 Retrofit 实例
private synchronized static Retrofit getRetrofit() { private synchronized static Retrofit getRetrofit() {
if (sRetrofit == null) { if (sRetrofit == null) {
sRetrofit = new Retrofit.Builder() sRetrofit = new Retrofit.Builder()
.baseUrl(Api.FIDDLER_BASE_QQ_URL) // 对应服务端的host // 设置基础 URL对应服务端的 host
.baseUrl(Api.FIDDLER_BASE_QQ_URL)
// 使用 OkHttpClient 进行网络请求
.client(getOkHttpClient()) .client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) // 这里还结合了Gson // 使用 Gson 进行 JSON 数据的解析和转换,使用了宽松模式
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create()))
// 将 Retrofit 请求转化成 RxJava 的 Observable以便使用 RxJava 处理异步请求结果
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build(); .build();
} }
return sRetrofit; return sRetrofit;
} }
// 获取歌手照片 // 配置用于歌手照片请求的 Retrofit 实例
private synchronized static Retrofit getRetrofitOfSinger() { private synchronized static Retrofit getRetrofitOfSinger() {
if (sSingerPicRetrofit == null) { if (sSingerPicRetrofit == null) {
sSingerPicRetrofit = new Retrofit.Builder() sSingerPicRetrofit = new Retrofit.Builder()
.baseUrl(Api.SINGER_PIC_BASE_URL) // 对应服务端的host // 设置基础 URL对应服务端的 host
.baseUrl(Api.SINGER_PIC_BASE_URL)
// 使用 OkHttpClient 进行网络请求
.client(getOkHttpClient()) .client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create()) // 这里还结合了Gson // 使用 Gson 进行 JSON 数据的解析和转换
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable .addConverterFactory(GsonConverterFactory.create())
// 将 Retrofit 请求转化成 RxJava 的 Observable
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build(); .build();
} }
return sSingerPicRetrofit; return sSingerPicRetrofit;
} }
//得到播放地址 // 配置用于歌曲播放地址请求的 Retrofit 实例
private synchronized static Retrofit getRetrofitOfSongUrl() { private synchronized static Retrofit getRetrofitOfSongUrl() {
if (songUrlRetrofit == null) { if (songUrlRetrofit == null) {
songUrlRetrofit = new Retrofit.Builder() songUrlRetrofit = new Retrofit.Builder()
.baseUrl(Api.FIDDLER_BASE_SONG_URL) // 对应服务端的host // 设置基础 URL对应服务端的 host
.baseUrl(Api.FIDDLER_BASE_SONG_URL)
// 使用 OkHttpClient 进行网络请求
.client(getOkHttpClient()) .client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create()) // 这里还结合了Gson // 使用 Gson 进行 JSON 数据的解析和转换
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable .addConverterFactory(GsonConverterFactory.create())
// 将 Retrofit 请求转化成 RxJava 的 Observable
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build(); .build();
} }
return songUrlRetrofit; return songUrlRetrofit;
} }
//配置OkHttp // 配置 OkHttpClient 实例
private synchronized static OkHttpClient getOkHttpClient() { private synchronized static OkHttpClient getOkHttpClient() {
if (sOkHttpClient == null) { if (sOkHttpClient == null) {
// 创建一个 HttpLoggingInterceptor 用于打印网络请求日志
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> {
//打印retrofit日志 // 打印 retrofit 日志
Log.i("RetrofitLog","retrofitBack = "+message); Log.i("RetrofitLog","retrofitBack = "+message);
}); });
// 设置日志级别为 BODY会打印请求和响应的详细信息
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
sOkHttpClient = new OkHttpClient.Builder() sOkHttpClient = new OkHttpClient.Builder()
// 设置连接超时时间
.connectTimeout(100, TimeUnit.SECONDS) .connectTimeout(100, TimeUnit.SECONDS)
// 设置读取超时时间
.readTimeout(100,TimeUnit.SECONDS) .readTimeout(100,TimeUnit.SECONDS)
// 设置写入超时时间
.writeTimeout(100,TimeUnit.SECONDS) .writeTimeout(100,TimeUnit.SECONDS)
//.addInterceptor(loggingInterceptor) 日志 // 可以添加日志拦截器,这里被注释掉了
//.addInterceptor(loggingInterceptor)
.build(); .build();
} }
return sOkHttpClient; return sOkHttpClient;

@ -22,43 +22,73 @@ import retrofit2.http.Query;
* author : * author :
* time : 2019/07/15 * time : 2019/07/15
* desc : * desc :
*
* 使 Retrofit
*
*
* API 使 RxJava Observable
* </pre> * </pre>
*/ */
public interface RetrofitService { public interface RetrofitService {
/** /**
* https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=2&n=2&w=周杰伦&format=json * https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=2&n=2&w=周杰伦&format=json
*
* @param seek
* @param offset
*
* 使 @GET Api.SEARCH_SONG URL
* @Query "w" "p"
* Observable<SearchSong>
*/ */
@GET(Api.SEARCH_SONG) @GET(Api.SEARCH_SONG)
Observable<SearchSong> search(@Query("w") String seek, @Query("p")int offset); Observable<SearchSong> search(@Query("w") String seek, @Query("p")int offset);
/** /**
* https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=2&w=林宥嘉&format=json&t=8 * https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=2&w=林宥嘉&format=json&t=8
*
* @param seek * @param seek
* @param offset * @param offset
*
* 使 @GET Api.SEARCH_ALBUM URL
* @Query "w" "p"
* Observable<Album>
*/ */
@GET(Api.SEARCH_ALBUM) @GET(Api.SEARCH_ALBUM)
Observable<Album> searchAlbum(@Query("w") String seek, @Query("p")int offset); Observable<Album> searchAlbum(@Query("w") String seek, @Query("p")int offset);
/** /**
* https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?albummid=004YodY33zsWTT&format=json * https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?albummid=004YodY33zsWTT&format=json
* @param id mid *
* @param id mid
*
* 使 @GET Api.ALBUM_DETAIL URL
* @Query "albummid" mid
* Observable<AlbumSong>
*/ */
@GET(Api.ALBUM_DETAIL) @GET(Api.ALBUM_DETAIL)
Observable<AlbumSong> getAlbumSong(@Query("albummid")String id); Observable<AlbumSong> getAlbumSong(@Query("albummid")String id);
/** /**
* songmid{} * songmid{}
* https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data=%7B%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22358840384%22%2C%22 + * https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data=%7B%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22358840384%22%2C%22 +
* songmid%22%3A%5B%22{003wFozn3V3Ra0} + * songmid%22%3A%5B%22{003wFozn3V3Ra0} +
* %22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221443481947%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A%221443481947%22%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D * %22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221443481947%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A%221443481947%22%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D
*
* 使 @GET Api.SONG_URL URL
* @Query "data" true
* Observable<SongUrl>
*/ */
@GET(Api.SONG_URL) @GET(Api.SONG_URL)
Observable<SongUrl> getSongUrl(@Query(value = "data",encoded = true) String data); Observable<SongUrl> getSongUrl(@Query(value = "data",encoded = true) String data);
/** /**
* songmidhttps://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=000wocYU11tSzS&format=json&nobase64=1 * songmid https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=000wocYU11tSzS&format=json&nobase64=1
* headersRefererqq * headers Referer qq
*
* 使 @GET Api.ONLINE_SONG_LRC URL
* @Headers Api.HEADER_REFERER
* @Query "songmid" mid
* Observable<OnlineSongLrc>线
*/ */
@Headers(Api.HEADER_REFERER) @Headers(Api.HEADER_REFERER)
@GET(Api.ONLINE_SONG_LRC) @GET(Api.ONLINE_SONG_LRC)
@ -66,18 +96,29 @@ public interface RetrofitService {
/** /**
* https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=1&w=说谎&format=json&t=7 * https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=1&w=说谎&format=json&t=7
*
* @param seek * @param seek
*
* 使 @GET Api.SONG_LRC URL
* @Query "w"
* Observable<SongLrc>
*/ */
@GET(Api.SONG_LRC) @GET(Api.SONG_LRC)
Observable<SongLrc> getLrc(@Query("w") String seek); Observable<SongLrc> getLrc(@Query("w") String seek);
/** /**
* http://music.163.com/api/search/get/web?s=刘瑞琦&type=100 * http://music.163.com/api/search/get/web?s=刘瑞琦&type=100
*
* @param singer * @param singer
*
* 使 @POST Api.SINGER_PIC URL
* @Headers Api.HEADER_USER_AGENT
* @FormUrlEncoded 使
* @Field "s"
* Observable<SingerImg>
*/ */
@Headers(Api.HEADER_USER_AGENT) @Headers(Api.HEADER_USER_AGENT)
@POST(Api.SINGER_PIC) @POST(Api.SINGER_PIC)
@FormUrlEncoded @FormUrlEncoded
Observable<SingerImg> getSingerImg(@Field("s")String singer); Observable<SingerImg> getSingerImg(@Field("s")String singer);
} }

@ -5,10 +5,18 @@ package com.example.musicplayer.model.prefs;
* author : * author :
* time : 2019/09/09 * time : 2019/09/09
* desc : * desc :
*
*
*
*
*
*
* </pre> * </pre>
*/ */
public interface PreferencesHelper { public interface PreferencesHelper {
void setPlayMode(int mode); //保存播放状态 // 保存播放状态的方法,接收一个整数表示播放模式
int getPlayMode();//得到播放状态 void setPlayMode(int mode);
// 获取播放状态的方法,返回一个整数表示播放模式
int getPlayMode();
} }

@ -11,23 +11,33 @@ import com.example.musicplayer.app.Constant;
* author : * author :
* time : 2019/09/09 * time : 2019/09/09
* desc : * desc :
*
* PreferencesHelper 使 SharedPreferences
* App.getContext() 使 Constant.SHARED_PREFERENCES_NAME SharedPreferences
*
* </pre> * </pre>
*/ */
public class PreferencesHelperImpl implements PreferencesHelper{ public class PreferencesHelperImpl implements PreferencesHelper{
// 存储 SharedPreferences 实例,用于存储播放模式等信息
private SharedPreferences mPreferences; private SharedPreferences mPreferences;
// 构造函数,获取 SharedPreferences 实例
public PreferencesHelperImpl(){ public PreferencesHelperImpl(){
// 获取 SharedPreferences 实例,使用应用上下文和预定义的名称,且为私有模式
mPreferences = App.getContext().getSharedPreferences(Constant.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); mPreferences = App.getContext().getSharedPreferences(Constant.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
} }
// 实现接口中的设置播放模式方法,将播放模式存储到 SharedPreferences 中
@Override @Override
public void setPlayMode(int mode) { public void setPlayMode(int mode) {
// 编辑 SharedPreferences 并存储播放模式,使用 Constant.PREFS_PLAY_MODE 作为键mode 作为值,并使用 apply 方法异步保存
mPreferences.edit().putInt(Constant.PREFS_PLAY_MODE,mode).apply(); mPreferences.edit().putInt(Constant.PREFS_PLAY_MODE,mode).apply();
} }
// 实现接口中的获取播放模式方法,从 SharedPreferences 中获取播放模式
@Override @Override
public int getPlayMode() { public int getPlayMode() {
// 从 SharedPreferences 中获取播放模式,使用 Constant.PREFS_PLAY_MODE 作为键,若不存在则默认返回 0
return mPreferences.getInt(Constant.PREFS_PLAY_MODE,0); return mPreferences.getInt(Constant.PREFS_PLAY_MODE,0);
} }
} }

@ -19,32 +19,53 @@ import static com.example.musicplayer.view.search.AlbumSongFragment.ALBUM_SONG;
/** /**
* Created by on 2018/11/27. * Created by on 2018/11/27.
*
* <pre>
* Presenter BasePresenter IAlbumSongContract.Presenter
*
* 使 RxJava IO 线线
* BaseObserver
* </pre>
*/ */
public class AlbumSongPresenter extends BasePresenter<IAlbumSongContract.View> implements IAlbumSongContract.Presenter { public class AlbumSongPresenter extends BasePresenter<IAlbumSongContract.View> implements IAlbumSongContract.Presenter {
// 用于日志输出的 TAG 常量,方便在日志中识别该类
private final static String TAG = "AlbumSongPresenter"; private final static String TAG = "AlbumSongPresenter";
// 实现 IAlbumSongContract.Presenter 接口中的 getAlbumDetail 方法,用于获取专辑的详细信息
@Override @Override
public void getAlbumDetail(String id, int type) { public void getAlbumDetail(String id, int type) {
// 使用 addRxSubscribe 方法添加 RxJava 的订阅操作
addRxSubscribe( addRxSubscribe(
// 调用 mModel 的 getAlbumSong 方法获取专辑歌曲信息mModel 可能是在 BasePresenter 中声明的对象
mModel.getAlbumSong(id) mModel.getAlbumSong(id)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程,避免阻塞主线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程,方便更新 UI
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对结果进行观察和处理
.subscribeWith(new BaseObserver<AlbumSong>(mView) { .subscribeWith(new BaseObserver<AlbumSong>(mView) {
// 在网络请求开始时调用,用于显示加载状态
@Override @Override
public void onStart() { public void onStart() {
mView.showLoading(); mView.showLoading();
} }
// 当请求成功获取到结果时调用
@Override @Override
public void onNext(AlbumSong albumSong) { public void onNext(AlbumSong albumSong) {
super.onNext(albumSong); super.onNext(albumSong);
// 隐藏加载状态
mView.hideLoading(); mView.hideLoading();
// 如果获取的结果代码为 0表示请求成功
if (albumSong.getCode() == 0) { if (albumSong.getCode() == 0) {
// 根据类型进行不同的处理
if (type == ALBUM_SONG) { if (type == ALBUM_SONG) {
// 调用 insertAllAlbumSong 方法插入所有专辑歌曲
insertAllAlbumSong(albumSong.getData().getList()); insertAllAlbumSong(albumSong.getData().getList());
} else { } else {
// 调用视图的 showAlbumMessage 方法显示专辑信息
mView.showAlbumMessage( mView.showAlbumMessage(
albumSong.getData().getName(), albumSong.getData().getName(),
albumSong.getData().getLan(), albumSong.getData().getLan(),
@ -53,18 +74,25 @@ public class AlbumSongPresenter extends BasePresenter<IAlbumSongContract.View> i
albumSong.getData().getDesc()); albumSong.getData().getDesc());
} }
} else { } else {
// 如果请求不成功,显示专辑歌曲错误
mView.showAlbumSongError(); mView.showAlbumSongError();
} }
} }
// 当请求发生错误时调用
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
// 打印错误栈信息
e.printStackTrace(); e.printStackTrace();
// 输出错误信息到日志
Log.d(TAG, "onError: " + e.toString()); Log.d(TAG, "onError: " + e.toString());
// 隐藏加载状态
mView.hideLoading(); mView.hideLoading();
// 如果是网络异常且类型为 ALBUM_SONG则显示网络错误
if (e instanceof UnknownHostException && type == ALBUM_SONG) { if (e instanceof UnknownHostException && type == ALBUM_SONG) {
mView.showNetError(); mView.showNetError();
} else { } else {
// 否则显示专辑歌曲错误
mView.showAlbumSongError(); mView.showAlbumSongError();
} }
} }
@ -72,9 +100,12 @@ public class AlbumSongPresenter extends BasePresenter<IAlbumSongContract.View> i
); );
} }
// 实现 IAlbumSongContract.Presenter 接口中的 insertAllAlbumSong 方法,用于插入所有专辑歌曲
@Override @Override
public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) { public void insertAllAlbumSong(List<AlbumSong.DataBean.ListBean> songList) {
// 调用 mModel 的 insertAllAlbumSong 方法将歌曲列表插入数据库mModel 可能是在 BasePresenter 中声明的对象
mModel.insertAllAlbumSong(songList); mModel.insertAllAlbumSong(songList);
// 调用视图的 setAlbumSongList 方法更新视图的专辑歌曲列表
mView.setAlbumSongList(songList); mView.setAlbumSongList(songList);
} }
} }

@ -14,26 +14,41 @@ import java.util.List;
/** /**
* Created by on 2018/10/17. * Created by on 2018/10/17.
*
* <pre>
* Presenter BasePresenter ILocalContract.Presenter
* MP3
* mModel 使 EventBus
*
* </pre>
*/ */
public class LocalPresenter extends BasePresenter<ILocalContract.View> implements ILocalContract.Presenter { public class LocalPresenter extends BasePresenter<ILocalContract.View> implements ILocalContract.Presenter {
// 实现 ILocalContract.Presenter 接口中的 getLocalMp3Info 方法,用于获取本地 MP3 信息
@Override @Override
public void getLocalMp3Info() { public void getLocalMp3Info() {
// 调用 mModel 的 getLocalMp3Info 方法获取本地歌曲列表mModel 可能是在 BasePresenter 中声明的对象
List<LocalSong> localSongList = mModel.getLocalMp3Info(); List<LocalSong> localSongList = mModel.getLocalMp3Info();
// 如果获取到的本地歌曲列表大小为 0表示没有本地歌曲
if(localSongList.size() == 0){ if(localSongList.size() == 0){
// 调用视图的 showErrorView 方法显示错误视图
mView.showErrorView(); mView.showErrorView();
}else { }else {
// 否则调用 saveSong 方法保存本地歌曲
saveSong(localSongList); saveSong(localSongList);
} }
} }
// 实现 ILocalContract.Presenter 接口中的 saveSong 方法,用于保存本地歌曲
@Override @Override
public void saveSong(List<LocalSong> localSongs) { public void saveSong(List<LocalSong> localSongs) {
// 调用 mModel 的 saveSong 方法保存本地歌曲mModel 可能是在 BasePresenter 中声明的对象
if(mModel.saveSong(localSongs)) { if(mModel.saveSong(localSongs)) {
// 如果保存成功,使用 EventBus 发送一个 SongListNumEvent 事件,类型为 Constant.LIST_TYPE_LOCAL
EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_LOCAL)); EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_LOCAL));
// 调用视图的 showToast 方法显示成功导入本地音乐的提示
mView.showToast("成功导入本地音乐"); mView.showToast("成功导入本地音乐");
// 调用视图的 showMusicList 方法显示音乐列表
mView.showMusicList(localSongs); mView.showMusicList(localSongs);
} }
} }

@ -21,133 +21,190 @@ import io.reactivex.schedulers.Schedulers;
/** /**
* Created by on 2018/10/26. * Created by on 2018/10/26.
*
* <pre>
* Presenter BasePresenter IPlayContract.Presenter
* ID
* 使 RxJava IO 线线
* RetrofitFactory 使 BaseObserver
* </pre>
*/ */
public class PlayPresenter extends BasePresenter<IPlayContract.View> implements IPlayContract.Presenter { public class PlayPresenter extends BasePresenter<IPlayContract.View> implements IPlayContract.Presenter {
// 实现 IPlayContract.Presenter 接口中的 getSingerImg 方法,用于获取歌手图片
@Override @Override
public void getSingerImg(String singer, String song, long duration) { public void getSingerImg(String singer, String song, long duration) {
addRxSubscribe( addRxSubscribe(
// 通过 RetrofitFactory 创建请求服务并调用 getSingerImg 方法获取歌手图片
RetrofitFactory.createRequestOfSinger().getSingerImg(singer) RetrofitFactory.createRequestOfSinger().getSingerImg(singer)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 当获取到歌手图片时,设置歌手图片到视图
.doOnNext(singerImg -> mView.setSingerImg(singerImg.getResult().getArtists().get(0).getImg1v1Url())) .doOnNext(singerImg -> mView.setSingerImg(singerImg.getResult().getArtists().get(0).getImg1v1Url()))
// 当获取歌手图片出现错误时,显示错误信息
.doOnError(SingerImg -> mView.showToast("获取不到歌手图片")) .doOnError(SingerImg -> mView.showToast("获取不到歌手图片"))
// 再次将后续操作调度到 IO 线程
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.flatMap((Function<SingerImg, ObservableSource<SearchSong>>) singerImg -> RetrofitFactory.createRequest().search(song,1)) // 将获取歌手图片的操作结果转换为搜索歌曲的操作
.flatMap((Function<SingerImg, ObservableSource<SearchSong>>) singerImg -> RetrofitFactory.createRequest().search(song, 1))
// 将后续操作调度到主线程
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索歌曲的结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView) { .subscribeWith(new BaseObserver<SearchSong>(mView) {
// 当搜索歌曲成功时
@Override @Override
public void onNext(SearchSong value) { public void onNext(SearchSong value) {
super.onNext(value); super.onNext(value);
if (value.getCode() == 0) { if (value.getCode() == 0) {
matchLrc(value.getData().getSong().getList(),duration); // 调用 matchLrc 方法匹配歌词
matchLrc(value.getData().getSong().getList(), duration);
} else { } else {
// 当搜索结果异常时,显示获取歌词错误
mView.getLrcError(null); mView.getLrcError(null);
} }
} }
// 当搜索歌曲出现错误时
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
super.onError(e); super.onError(e);
// 显示获取歌词错误
mView.getLrcError(null); mView.getLrcError(null);
} }
}) })
); );
} }
// 实现 IPlayContract.Presenter 接口中的 getLrc 方法,用于获取歌词
@Override @Override
public void getLrc(String songId,int type) { public void getLrc(String songId, int type) {
// 调用 mModel 的 getOnlineSongLrc 方法获取在线歌曲歌词mModel 可能是在 BasePresenter 中声明的对象
mModel.getOnlineSongLrc(songId) mModel.getOnlineSongLrc(songId)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeWith(new BaseObserver<OnlineSongLrc>(mView,false,false){ .subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对获取歌词的结果进行观察和处理
.subscribeWith(new BaseObserver<OnlineSongLrc>(mView, false, false) {
// 当获取歌词成功时
@Override @Override
public void onNext(OnlineSongLrc onlineSongLrc){ public void onNext(OnlineSongLrc onlineSongLrc) {
if(onlineSongLrc.getCode() == 0){ if (onlineSongLrc.getCode() == 0) {
String lrc = onlineSongLrc.getLyric(); String lrc = onlineSongLrc.getLyric();
//如果是本地音乐,就将歌词保存起来 // 如果是本地音乐,保存歌词
if(type == Constant.SONG_LOCAL) mView.saveLrc(lrc); if (type == Constant.SONG_LOCAL) mView.saveLrc(lrc);
// 显示歌词
mView.showLrc(lrc); mView.showLrc(lrc);
}else { } else {
// 当获取歌词失败时,显示获取歌词错误
mView.getLrcError(null); mView.getLrcError(null);
} }
} }
}); });
} }
// 实现 IPlayContract.Presenter 接口中的 getSongId 方法,用于获取歌曲 ID
@Override @Override
public void getSongId(String song, long duration) { public void getSongId(String song, long duration) {
addRxSubscribe( addRxSubscribe(
// 调用 mModel 的 search 方法搜索歌曲
mModel.search(song, 1) mModel.search(song, 1)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView, true, true) { .subscribeWith(new BaseObserver<SearchSong>(mView, true, true) {
// 当搜索成功时
@Override @Override
public void onNext(SearchSong searchSong) { public void onNext(SearchSong searchSong) {
super.onNext(searchSong); super.onNext(searchSong);
if (searchSong.getCode() == 0) { if (searchSong.getCode() == 0) {
matchSong(searchSong.getData().getSong().getList(),duration); // 调用 matchSong 方法匹配歌曲
matchSong(searchSong.getData().getSong().getList(), duration);
} else { } else {
// 当搜索结果异常时,显示获取歌词错误
mView.getLrcError(null); mView.getLrcError(null);
} }
} }
})); });
} }
// 实现 IPlayContract.Presenter 接口中的 setPlayMode 方法,用于设置播放模式
@Override @Override
public void setPlayMode(int mode) { public void setPlayMode(int mode) {
// 调用 mModel 的 setPlayMode 方法设置播放模式mModel 可能是在 BasePresenter 中声明的对象
mModel.setPlayMode(mode); mModel.setPlayMode(mode);
} }
// 实现 IPlayContract.Presenter 接口中的 getPlayMode 方法,用于获取播放模式
@Override @Override
public int getPlayMode() { public int getPlayMode() {
// 调用 mModel 的 getPlayMode 方法获取播放模式mModel 可能是在 BasePresenter 中声明的对象
return mModel.getPlayMode(); return mModel.getPlayMode();
} }
// 实现 IPlayContract.Presenter 接口中的 queryLove 方法,用于查询歌曲是否被收藏
@Override @Override
public void queryLove(String songId) { public void queryLove(String songId) {
// 调用 mView 的 showLove 方法显示歌曲是否被收藏mView 是 IPlayContract.View 类型的视图对象
mView.showLove(mModel.queryLove(songId)); mView.showLove(mModel.queryLove(songId));
} }
// 实现 IPlayContract.Presenter 接口中的 saveToLove 方法,用于将歌曲保存到收藏
@Override @Override
public void saveToLove(Song song) { public void saveToLove(Song song) {
if(mModel.saveToLove(song)){ // 调用 mModel 的 saveToLove 方法保存歌曲到收藏mModel 可能是在 BasePresenter 中声明的对象
if (mModel.saveToLove(song)) {
// 当保存成功时,调用 mView 的 saveToLoveSuccess 方法显示保存成功信息
mView.saveToLoveSuccess(); mView.saveToLoveSuccess();
} }
} }
// 实现 IPlayContract.Presenter 接口中的 deleteFromLove 方法,用于从收藏中删除歌曲
@Override @Override
public void deleteFromLove(String songId) { public void deleteFromLove(String songId) {
if(mModel.deleteFromLove(songId)){ // 调用 mModel 的 deleteFromLove 方法从收藏中删除歌曲mModel 可能是在 BasePresenter 中声明的对象
if (mModel.deleteFromLove(songId)) {
// 当删除成功时,调用 mView 的 sendUpdateCollection 方法更新收藏列表
mView.sendUpdateCollection(); mView.sendUpdateCollection();
} }
} }
//匹配歌词 // 匹配歌词的私有方法
private void matchLrc(List<SearchSong.DataBean.SongBean.ListBean> listBeans,long duration){ private void matchLrc(List<SearchSong.DataBean.SongBean.ListBean> listBeans, long duration) {
boolean isFind = false; boolean isFind = false;
for(SearchSong.DataBean.SongBean.ListBean listBean:listBeans){ // 遍历搜索歌曲结果列表
if(duration == listBean.getInterval()){ for (SearchSong.DataBean.SongBean.ListBean listBean : listBeans) {
// 如果歌曲时长匹配,设置找到标志并设置歌曲 ID
if (duration == listBean.getInterval()) {
isFind = true; isFind = true;
mView.setLocalSongId(listBean.getSongmid()); mView.setLocalSongId(listBean.getSongmid());
} }
} }
//如果找不到歌曲id就传输找不到歌曲的消 // 如果未找到匹配的歌曲,显示找不到歌曲 ID 的错误信
if(!isFind) { if (!isFind) {
mView.getLrcError(Constant.SONG_ID_UNFIND); mView.getLrcError(Constant.SONG_ID_UNFIND);
} }
} }
private void matchSong(List<SearchSong.DataBean.SongBean.ListBean> listBeans,long duration){ // 匹配歌曲的私有方法
private void matchSong(List<SearchSong.DataBean.SongBean.ListBean> listBeans, long duration) {
boolean isFind = false; boolean isFind = false;
for(SearchSong.DataBean.SongBean.ListBean listBean:listBeans){ // 遍历搜索歌曲结果列表
if(duration == listBean.getInterval()){ for (SearchSong.DataBean.SongBean.ListBean listBean : listBeans) {
// 如果歌曲时长匹配,设置找到标志并获取歌曲成功
if (duration == listBean.getInterval()) {
isFind = true; isFind = true;
mView.getSongIdSuccess(listBean.getSongmid()); mView.getSongIdSuccess(listBean.getSongmid());
} }
} }
//如果找不到歌曲id就传输找不到歌曲的消 // 如果未找到匹配的歌曲,显示找不到歌曲 ID 的错误信
if(!isFind) { if (!isFind) {
mView.getLrcError(Constant.SONG_ID_UNFIND); mView.getLrcError(Constant.SONG_ID_UNFIND);
} }
} }

@ -30,22 +30,37 @@ import okhttp3.Response;
/** /**
* Created by on 2018/11/21. * Created by on 2018/11/21.
*
* <pre>
* Presenter BasePresenter ISearchContentContract.Presenter
*
* 使 RxJava IO 线线
* BaseObserver
* </pre>
*/ */
public class SearchContentPresenter extends BasePresenter<ISearchContentContract.View> public class SearchContentPresenter extends BasePresenter<ISearchContentContract.View>
implements ISearchContentContract.Presenter { implements ISearchContentContract.Presenter {
// 用于日志输出的 TAG 常量,方便在日志中识别该类
private static final String TAG = "SearchContentPresenter"; private static final String TAG = "SearchContentPresenter";
// 实现 ISearchContentContract.Presenter 接口中的 search 方法,用于搜索歌曲
@Override @Override
public void search(String seek, int offset) { public void search(String seek, int offset) {
addRxSubscribe( addRxSubscribe(
// 调用 mModel 的 search 方法进行搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.search(seek, offset) mModel.search(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView, true, true) { .subscribeWith(new BaseObserver<SearchSong>(mView, true, true) {
// 当搜索结果成功时
@Override @Override
public void onNext(SearchSong searchSong) { public void onNext(SearchSong searchSong) {
super.onNext(searchSong); super.onNext(searchSong);
if (searchSong.getCode() == 0) { if (searchSong.getCode() == 0) {
// 将搜索结果中的歌曲列表设置到视图中
mView.setSongsList((ArrayList<SearchSong.DataBean.SongBean.ListBean>) mView.setSongsList((ArrayList<SearchSong.DataBean.SongBean.ListBean>)
searchSong.getData().getSong().getList()); searchSong.getData().getSong().getList());
} }
@ -53,12 +68,19 @@ public class SearchContentPresenter extends BasePresenter<ISearchContentContract
})); }));
} }
// 实现 ISearchContentContract.Presenter 接口中的 searchMore 方法,用于搜索更多歌曲
@Override @Override
public void searchMore(String seek, int offset) { public void searchMore(String seek, int offset) {
addRxSubscribe( addRxSubscribe(
// 调用 mModel 的 search 方法进行搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.search(seek, offset) mModel.search(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索结果进行观察和处理
.subscribeWith(new BaseObserver<SearchSong>(mView, false, true) { .subscribeWith(new BaseObserver<SearchSong>(mView, false, true) {
// 当搜索结果成功时
@Override @Override
public void onNext(SearchSong searchSong) { public void onNext(SearchSong searchSong) {
super.onNext(searchSong); super.onNext(searchSong);
@ -66,90 +88,125 @@ public class SearchContentPresenter extends BasePresenter<ISearchContentContract
ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans = ArrayList<SearchSong.DataBean.SongBean.ListBean> songListBeans =
(ArrayList<SearchSong.DataBean.SongBean.ListBean>) searchSong.getData().getSong().getList(); (ArrayList<SearchSong.DataBean.SongBean.ListBean>) searchSong.getData().getSong().getList();
if (songListBeans.size() == 0) { if (songListBeans.size() == 0) {
// 当搜索结果为空时,显示搜索更多错误
mView.searchMoreError(); mView.searchMoreError();
} else { } else {
// 当搜索结果不为空时,显示搜索更多成功并传递结果列表
mView.searchMoreSuccess(songListBeans); mView.searchMoreSuccess(songListBeans);
} }
} else { } else {
// 当搜索结果异常时,显示搜索更多错误
mView.searchMoreError(); mView.searchMoreError();
} }
} }
// 当搜索出现错误时
@Override @Override
public void onError(Throwable e){ public void onError(Throwable e) {
super.onError(e); super.onError(e);
// 显示搜索更多的网络错误
mView.showSearcherMoreNetworkError(); mView.showSearcherMoreNetworkError();
} }
})); }));
} }
// 实现 ISearchContentContract.Presenter 接口中的 searchAlbum 方法,用于搜索专辑
@Override @Override
public void searchAlbum(String seek, int offset) { public void searchAlbum(String seek, int offset) {
addRxSubscribe( addRxSubscribe(
// 调用 mModel 的 searchAlbum 方法进行专辑搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.searchAlbum(seek, offset) mModel.searchAlbum(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索专辑结果进行观察和处理
.subscribeWith(new BaseObserver<Album>(mView, true, true) { .subscribeWith(new BaseObserver<Album>(mView, true, true) {
// 当搜索专辑结果成功时
@Override @Override
public void onNext(Album album) { public void onNext(Album album) {
super.onNext(album); super.onNext(album);
if (album.getCode() == 0) { if (album.getCode() == 0) {
// 当搜索专辑结果正常时,显示搜索专辑成功并传递结果列表
mView.searchAlbumSuccess(album.getData().getAlbum().getList()); mView.searchAlbumSuccess(album.getData().getAlbum().getList());
} else { } else {
// 当搜索专辑结果异常时,显示搜索专辑错误
mView.searchAlbumError(); mView.searchAlbumError();
} }
} }
})); }));
} }
// 实现 ISearchContentContract.Presenter 接口中的 searchAlbumMore 方法,用于搜索更多专辑
@Override @Override
public void searchAlbumMore(String seek, int offset) { public void searchAlbumMore(String seek, int offset) {
addRxSubscribe( addRxSubscribe(
// 调用 mModel 的 searchAlbum 方法进行专辑搜索mModel 可能是在 BasePresenter 中声明的对象
mModel.searchAlbum(seek, offset) mModel.searchAlbum(seek, offset)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对搜索专辑结果进行观察和处理
.subscribeWith(new BaseObserver<Album>(mView, false, true) { .subscribeWith(new BaseObserver<Album>(mView, false, true) {
// 当搜索专辑结果成功时
@Override @Override
public void onNext(Album album) { public void onNext(Album album) {
super.onNext(album); super.onNext(album);
if (album.getCode() == 0) { if (album.getCode() == 0) {
// 当搜索专辑结果正常时,显示搜索更多专辑成功并传递结果列表
mView.searchAlbumMoreSuccess(album.getData().getAlbum().getList()); mView.searchAlbumMoreSuccess(album.getData().getAlbum().getList());
} else { } else {
// 当搜索专辑结果异常时,显示搜索更多错误
mView.searchMoreError(); mView.searchMoreError();
} }
} }
// 当搜索专辑出现错误时
@Override @Override
public void onError(Throwable e){ public void onError(Throwable e) {
super.onError(e); super.onError(e);
// 显示搜索更多的网络错误
mView.showSearcherMoreNetworkError(); mView.showSearcherMoreNetworkError();
} }
})); }));
} }
// 实现 ISearchContentContract.Presenter 中的 getSongUrl 方法,用于获取歌曲播放地址
@Override @Override
public void getSongUrl(Song song) { public void getSongUrl(Song song) {
// 输出日志,显示正在获取歌曲播放地址及相关数据
Log.d(TAG, "getSongUrl: "+Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT); Log.d(TAG, "getSongUrl: "+Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT);
addRxSubscribe( addRxSubscribe(
// 通过 RetrofitFactory 创建请求服务并调用 getSongUrl 方法获取歌曲播放地址
RetrofitFactory.createRequestOfSongUrl().getSongUrl(Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT) RetrofitFactory.createRequestOfSongUrl().getSongUrl(Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT)
.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) // 将网络请求操作调度到 IO 线程
.subscribeOn(Schedulers.io())
// 将结果处理调度到主线程
.observeOn(AndroidSchedulers.mainThread())
// 使用 BaseObserver 对获取歌曲播放地址的结果进行观察和处理
.subscribeWith(new BaseObserver<SongUrl>(mView,false,false){ .subscribeWith(new BaseObserver<SongUrl>(mView,false,false){
// 当获取歌曲播放地址成功时
@Override @Override
public void onNext(SongUrl songUrl){ public void onNext(SongUrl songUrl) {
super.onNext(songUrl); super.onNext(songUrl);
if(songUrl.getCode() == 0){ if (songUrl.getCode() == 0) {
String sip = songUrl.getReq_0().getData().getSip().get(0); String sip = songUrl.getReq_0().getData().getSip().get(0);
String purl = songUrl.getReq_0().getData().getMidurlinfo().get(0).getPurl(); String purl = songUrl.getReq_0().getData().getMidurlinfo().get(0).getPurl();
if(purl.equals("")){ if (purl.equals("")) {
// 当歌曲无版权时,显示提示信息
mView.showToast("该歌曲暂时没有版权,搜搜其它歌曲吧"); mView.showToast("该歌曲暂时没有版权,搜搜其它歌曲吧");
}else { } else {
mView.getSongUrlSuccess(song,sip+purl); // 当获取播放地址成功时,调用视图的 getSongUrlSuccess 方法
mView.getSongUrlSuccess(song, sip + purl);
} }
} else {
}else { // 当获取歌曲播放地址失败时,显示错误信息
mView.showToast(songUrl.getCode()+":获取不到歌曲播放地址"); mView.showToast(songUrl.getCode()+":获取不到歌曲播放地址");
} }
} }
}) })
); );
} }
} }

@ -8,7 +8,6 @@ import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Binder; import android.os.Binder;
import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
@ -46,164 +45,225 @@ import static com.example.musicplayer.app.Constant.TYPE_DOWNLOADING;
* <pre> * <pre>
* author : * author :
* time : 2019/04/08 * time : 2019/04/08
* desc : DownloadTask * desc : DownloadTask
*
*
* 使 DownloadTask DownloadListener
* 使 EventBus UI
* 使 LitePal
* 使 Notification
* </pre> * </pre>
*/ */
public class DownloadService extends Service { public class DownloadService extends Service {
// 日志标签
private static final String TAG = "DownloadService"; private static final String TAG = "DownloadService";
// 存储当前的下载任务
private DownloadTask downloadTask; private DownloadTask downloadTask;
// 存储下载的 URL
private String downloadUrl; private String downloadUrl;
// 自定义的 Binder 对象,用于与客户端通信
private DownloadBinder downloadBinder = new DownloadBinder(); private DownloadBinder downloadBinder = new DownloadBinder();
private LinkedList<DownloadInfo> downloadQueue = new LinkedList<>();//等待队列 // 存储下载等待队列,使用 LinkedList 方便在队列头部和尾部操作
private int position = 0;//下载歌曲在下载歌曲列表的位置 private LinkedList<DownloadInfo> downloadQueue = new LinkedList<>();
// 下载歌曲在下载歌曲列表中的位置
private int position = 0;
// 下载监听器,监听下载进度和状态
private DownloadListener listener = new DownloadListener() { private DownloadListener listener = new DownloadListener() {
// 下载进度更新时调用
@Override @Override
public void onProgress(DownloadInfo downloadInfo) { public void onProgress(DownloadInfo downloadInfo) {
// 更新下载信息的状态为正在下载
downloadInfo.setStatus(Constant.DOWNLOAD_ING); downloadInfo.setStatus(Constant.DOWNLOAD_ING);
EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, downloadInfo)); //通知下载模块 // 发布下载事件,通知下载模块
if(downloadInfo.getProgress()!=100){ EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, downloadInfo));
getNotificationManager().notify(1, getNotification("正在下载: "+downloadInfo.getSongName(), downloadInfo.getProgress())); if (downloadInfo.getProgress()!= 100) {
}else { // 更新通知显示正在下载及进度
if(downloadQueue.isEmpty()) getNotificationManager().notify(1, getNotification("下载成功",-1)); getNotificationManager().notify(1, getNotification("正在下载: " + downloadInfo.getSongName(), downloadInfo.getProgress()));
} else {
if (downloadQueue.isEmpty()) {
// 当下载队列空且下载完成时,更新通知为下载成功
getNotificationManager().notify(1, getNotification("下载成功", -1));
}
} }
} }
// 下载成功时调用
@Override @Override
public void onSuccess() { public void onSuccess() {
downloadTask = null; downloadTask = null;
// 从下载队列中取出已完成的下载信息
DownloadInfo downloadInfo = downloadQueue.poll(); DownloadInfo downloadInfo = downloadQueue.poll();
operateDb(downloadInfo); //操作数据库 // 操作数据库,可能是更新下载信息等操作
start();//下载队列中的其它歌曲 operateDb(downloadInfo);
//下载成功通知前台服务通知关闭,并创建一个下载成功的通知 // 开始下载队列中的下一个歌曲
start();
// 停止前台服务通知,并创建一个下载成功的通知
stopForeground(true); stopForeground(true);
if(downloadQueue.isEmpty()) getNotificationManager().notify(1, getNotification("下载成功",-1)); if (downloadQueue.isEmpty()) {
getNotificationManager().notify(1, getNotification("下载成功", -1));
}
} }
// 下载完成时调用
@Override @Override
public void onDownloaded() { public void onDownloaded() {
downloadTask = null; downloadTask = null;
// 显示下载完成的 Toast 提示
CommonUtil.showToast(DownloadService.this, "已下载"); CommonUtil.showToast(DownloadService.this, "已下载");
} }
// 下载失败时调用
@Override @Override
public void onFailed() { public void onFailed() {
downloadTask = null; downloadTask = null;
// 停止前台服务通知,并创建一个下载失败的通知
//下载失败通知前台服务通知关闭,并创建一个下载失败的通知
stopForeground(true); stopForeground(true);
getNotificationManager().notify(1, getNotification("下载失败",-1)); getNotificationManager().notify(1, getNotification("下载失败", -1));
// 显示下载失败的 Toast 提示
Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_SHORT).show(); Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_SHORT).show();
} }
// 下载暂停时调用
@Override @Override
public void onPaused() { public void onPaused() {
downloadTask = null; downloadTask = null;
DownloadInfo downloadInfo=downloadQueue.poll();//从下载列表中移除该歌曲 // 从下载队列中移除暂停的歌曲
DownloadInfo downloadInfo = downloadQueue.poll();
// 更新数据库中暂停的歌曲状态
updateDbOfPause(downloadInfo.getSongId()); updateDbOfPause(downloadInfo.getSongId());
getNotificationManager().notify(1, getNotification("下载已暂停:"+downloadInfo.getSongName(), -1)); // 更新通知为下载已暂停
start();//下载下载列表中的歌曲 getNotificationManager().notify(1, getNotification("下载已暂停:" + downloadInfo.getSongName(), -1));
// 开始下载队列中的下一个歌曲
start();
// 更新下载信息状态为暂停,并发布暂停事件
downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED); downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED);
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); //下载暂停 EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo));
// 显示下载已暂停的 Toast 提示
Toast.makeText(DownloadService.this, "下载已暂停", Toast.LENGTH_SHORT).show(); Toast.makeText(DownloadService.this, "下载已暂停", Toast.LENGTH_SHORT).show();
} }
// 下载取消时调用
@Override @Override
public void onCanceled() { public void onCanceled() {
downloadTask = null; downloadTask = null;
// 停止前台服务通知
stopForeground(true); stopForeground(true);
// 显示下载已取消的 Toast 提示
Toast.makeText(DownloadService.this, "下载已取消", Toast.LENGTH_SHORT).show(); Toast.makeText(DownloadService.this, "下载已取消", Toast.LENGTH_SHORT).show();
} }
}; };
// 服务绑定方法,返回 Binder 对象
@Nullable @Nullable
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return downloadBinder; return downloadBinder;
} }
// 自定义的 Binder 类,用于与客户端通信
public class DownloadBinder extends Binder { public class DownloadBinder extends Binder {
// 开始下载方法
public void startDownload(DownloadInfo song) { public void startDownload(DownloadInfo song) {
try { try {
postDownloadEvent(song);//通知正在下载界面 // 通知正在下载界面
postDownloadEvent(song);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
if (downloadTask != null) { if (downloadTask!= null) {
// 已存在下载任务时的提示
CommonUtil.showToast(DownloadService.this, "已经加入下载队列"); CommonUtil.showToast(DownloadService.this, "已经加入下载队列");
} else { } else {
// 开始下载的提示
CommonUtil.showToast(DownloadService.this, "开始下载"); CommonUtil.showToast(DownloadService.this, "开始下载");
start(); start();
} }
} }
// 暂停下载方法
public void pauseDownload(String songId) { public void pauseDownload(String songId) {
//暂停的歌曲是否为当前下载的歌曲 // 判断暂停的歌曲是否为当前正在下载的歌曲
if (downloadTask != null &&downloadQueue.peek().getSongId().equals(songId)) { if (downloadTask!= null && downloadQueue.peek().getSongId().equals(songId)) {
// 暂停当前下载任务
downloadTask.pauseDownload(); downloadTask.pauseDownload();
}else {//暂停的歌曲是下载队列的歌曲 } else {
//将该歌曲从下载队列中移除 // 暂停的歌曲在下载队列中
for (int i = 0; i < downloadQueue.size(); i++) { for (int i = 0; i < downloadQueue.size(); i++) {
DownloadInfo downloadInfo = downloadQueue.get(i); DownloadInfo downloadInfo = downloadQueue.get(i);
if (downloadInfo.getSongId().equals(songId)) { if (downloadInfo.getSongId().equals(songId)) {
// 从下载队列中移除该歌曲
downloadQueue.remove(i); downloadQueue.remove(i);
// 更新数据库中该歌曲的暂停状态
updateDbOfPause(downloadInfo.getSongId()); updateDbOfPause(downloadInfo.getSongId());
// 更新下载信息状态为暂停,并发布暂停事件
downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED); downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED);
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); //下载暂停 EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo));
} }
} }
} }
} }
// 取消下载方法
public void cancelDownload(DownloadInfo song) { public void cancelDownload(DownloadInfo song) {
String songId = song.getSongId(); String songId = song.getSongId();
//如果该歌曲正在下载则需要将downloadTask置为null // 如果该歌曲正在下载,取消下载任务
if (downloadTask != null && downloadQueue.peek().getSongId().equals(songId)) { if (downloadTask!= null && downloadQueue.peek().getSongId().equals(songId)) {
downloadTask.cancelDownload(); downloadTask.cancelDownload();
} }
//将该歌曲从下载队列中移除 // 将该歌曲从下载队列中移除
for (int i = 0; i < downloadQueue.size(); i++) { for (int i = 0; i < downloadQueue.size(); i++) {
DownloadInfo downloadInfo = downloadQueue.get(i); DownloadInfo downloadInfo = downloadQueue.get(i);
if (downloadInfo.getSongId().equals(songId)) downloadQueue.remove(i); if (downloadInfo.getSongId().equals(songId)) downloadQueue.remove(i);
} }
// 更新数据库
updateDb(songId); updateDb(songId);
// 删除数据库中的该歌曲信息
deleteDb(songId); deleteDb(songId);
//取消下载需要将文件删除并将通知关闭 // 取消下载需要将文件删除并关闭通知
if (song.getUrl() != null) { if (song.getUrl()!= null) {
checkoutFile(song,song.getUrl()); //实际文件长度 checkoutFile(song, song.getUrl());
} }
//通知正在下载列表 // 发布下载取消事件
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_CANCELED)); EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_CANCELED));
} }
} }
// 开始下载的方法
private void start() { private void start() {
if (downloadTask == null && !downloadQueue.isEmpty()) { if (downloadTask == null &&!downloadQueue.isEmpty()) {
// 从下载队列中取出要下载的歌曲信息
DownloadInfo downloadInfo = downloadQueue.peek(); DownloadInfo downloadInfo = downloadQueue.peek();
// 从数据库中查找该歌曲信息
List<DownloadInfo> songList = List<DownloadInfo> songList =
LitePal.where("songId = ?",downloadInfo.getSongId()).find(DownloadInfo.class); LitePal.where("songId =?", downloadInfo.getSongId()).find(DownloadInfo.class);
DownloadInfo currentDownloadInfo = songList.get(0); DownloadInfo currentDownloadInfo = songList.get(0);
// 更新歌曲状态为准备下载
currentDownloadInfo.setStatus(Constant.DOWNLOAD_READY); currentDownloadInfo.setStatus(Constant.DOWNLOAD_READY);
EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING,currentDownloadInfo)); // 发布下载事件
EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, currentDownloadInfo));
downloadUrl = currentDownloadInfo.getUrl(); downloadUrl = currentDownloadInfo.getUrl();
// 创建新的下载任务并执行
downloadTask = new DownloadTask(listener); downloadTask = new DownloadTask(listener);
downloadTask.execute(currentDownloadInfo); downloadTask.execute(currentDownloadInfo);
getNotificationManager().notify(1, getNotification("正在下载:"+downloadInfo.getSongName(), 0)); // 更新通知为正在下载
getNotificationManager().notify(1, getNotification("正在下载:" + downloadInfo.getSongName(), 0));
} }
} }
// 获取通知管理器
private NotificationManager getNotificationManager() { private NotificationManager getNotificationManager() {
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
} }
private Notification getNotification(String title,int progress) { // 创建通知的方法
private Notification getNotification(String title, int progress) {
// 创建跳转到主界面的意图
Intent intent = new Intent(this, MainActivity.class); Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 创建通知通道
String id = "channel_001"; String id = "channel_001";
String name = "下载通知"; String name = "下载通知";
NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW); NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW);
@ -212,107 +272,132 @@ public class DownloadService extends Service {
builder.setSmallIcon(R.mipmap.icon); builder.setSmallIcon(R.mipmap.icon);
builder.setContentIntent(pi); builder.setContentIntent(pi);
builder.setContentTitle(title); builder.setContentTitle(title);
if(progress>0){ if (progress > 0) {
builder.setContentText(progress +"%"); builder.setContentText(progress + "%");
builder.setProgress(100, progress, false); builder.setProgress(100, progress, false);
} }
return builder.build(); return builder.build();
} else { } else {
// 兼容旧版本的通知创建
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "default"); NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "default");
builder.setSmallIcon(R.mipmap.icon); builder.setSmallIcon(R.mipmap.icon);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon)); builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon));
builder.setContentIntent(pi); builder.setContentIntent(pi);
builder.setContentTitle(title); builder.setContentTitle(title);
if(progress>0){ if (progress > 0) {
builder.setContentText(progress +"%"); builder.setContentText(progress + "%");
builder.setProgress(100, progress, false); builder.setProgress(100, progress, false);
} }
return builder.build(); return builder.build();
} }
} }
// 操作数据库的方法,可能是下载成功后的操作
private void operateDb(DownloadInfo downloadInfo) { private void operateDb(DownloadInfo downloadInfo) {
updateDb(downloadInfo.getSongId()); updateDb(downloadInfo.getSongId());
deleteDb(downloadInfo.getSongId()); deleteDb(downloadInfo.getSongId());
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_SUCCESS));//通知已下载列表 // 发布下载成功事件
EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_DOWNLOAD)); //通知主界面的下载个数需要改变 EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_SUCCESS));
// 发布歌曲列表数量改变事件
EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_DOWNLOAD));
} }
//更新数据库中歌曲列表的位置即下载完成歌曲后的位置都要减去1 // 更新数据库中歌曲列表的位置
private void updateDb(String songId) { private void updateDb(String songId) {
long id = LitePal.select("id").where("songId = ?", songId).find(DownloadInfo.class).get(0).getId(); // 查找该歌曲在数据库中的 ID
List<DownloadInfo> songIdList = LitePal.where("id > ?", id + "").find(DownloadInfo.class); long id = LitePal.select("id").where("songId =?", songId).find(DownloadInfo.class).get(0).getId();
// 查找该歌曲之后的歌曲列表
List<DownloadInfo> songIdList = LitePal.where("id >?", id + "").find(DownloadInfo.class);
for (DownloadInfo song : songIdList) { for (DownloadInfo song : songIdList) {
// 更新位置,将后续歌曲位置减 1
song.setPosition(song.getPosition() - 1); song.setPosition(song.getPosition() - 1);
song.save(); song.save();
} }
} }
//暂停时更新列表歌曲状态 // 暂停时更新数据库中歌曲的状态
private void updateDbOfPause(String songId){ private void updateDbOfPause(String songId) {
List<DownloadInfo> statusList = List<DownloadInfo> statusList =
LitePal.where("songId = ?",songId).find(DownloadInfo.class,true); LitePal.where("songId =?", songId).find(DownloadInfo.class, true);
DownloadInfo downloadInfo = statusList.get(0); DownloadInfo downloadInfo = statusList.get(0);
downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED); downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED);
downloadInfo.save(); downloadInfo.save();
} }
//下载完成时要删除下载歌曲表中的数据以及关联表中的数据 // 下载完成时删除数据库中的数据
private void deleteDb(String songId) { private void deleteDb(String songId) {
LitePal.deleteAll(DownloadInfo.class, "songId=?", songId);//删除已下载歌曲的相关列 // 删除已下载歌曲的相关列
LitePal.deleteAll(DownloadInfo.class, "songId=?", songId);
} }
// 发布下载事件
private void postDownloadEvent(DownloadInfo downloadInfo) { private void postDownloadEvent(DownloadInfo downloadInfo) {
//如果需要下载的表中有该条歌曲,则添加到下载队列后跳过 // 检查数据库中是否已存在该歌曲的下载信息
List<DownloadInfo> downloadInfoList = List<DownloadInfo> downloadInfoList =
LitePal.where("songId = ?",downloadInfo.getSongId()).find(DownloadInfo.class,true); LitePal.where("songId =?", downloadInfo.getSongId()).find(DownloadInfo.class, true);
if (downloadInfoList.size() != 0){ if (downloadInfoList.size()!= 0) {
DownloadInfo historyDownloadInfo = downloadInfoList.get(0); DownloadInfo historyDownloadInfo = downloadInfoList.get(0);
// 更新状态为等待
historyDownloadInfo.setStatus(Constant.DOWNLOAD_WAIT); historyDownloadInfo.setStatus(Constant.DOWNLOAD_WAIT);
historyDownloadInfo.save(); historyDownloadInfo.save();
EventBus.getDefault().post(new DownloadEvent(Constant.DOWNLOAD_PAUSED,historyDownloadInfo)); // 发布暂停事件
EventBus.getDefault().post(new DownloadEvent(Constant.DOWNLOAD_PAUSED, historyDownloadInfo));
// 将歌曲添加到下载队列
downloadQueue.offer(historyDownloadInfo); downloadQueue.offer(historyDownloadInfo);
return; return;
} }
// 设置歌曲在下载列表中的位置
position = LitePal.findAll(DownloadInfo.class).size(); position = LitePal.findAll(DownloadInfo.class).size();
downloadInfo.setPosition(position); downloadInfo.setPosition(position);
downloadInfo.setStatus(Constant.DOWNLOAD_WAIT); //等待 downloadInfo.setStatus(Constant.DOWNLOAD_WAIT);
downloadInfo.save(); downloadInfo.save();
downloadQueue.offer(downloadInfo);//将歌曲放到等待队列中 // 将歌曲添加到等待队列
downloadQueue.offer(downloadInfo);
// 发布添加下载事件
EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_ADD)); EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_ADD));
} }
//获取歌曲实际大小,然后判断是否存在于文件中 // 获取歌曲实际大小,并判断文件是否存在,可能用于取消下载时的清理
public void checkoutFile(DownloadInfo song, String downloadUrl){ public void checkoutFile(DownloadInfo song, String downloadUrl) {
// 创建 OkHttpClient 实例,用于网络请求
OkHttpClient client = new OkHttpClient(); OkHttpClient client = new OkHttpClient();
// 构建请求对象,指定请求的 URL
Request request = new Request.Builder() Request request = new Request.Builder()
.url(downloadUrl) .url(downloadUrl)
.build(); .build();
Call call= client.newCall(request); // 创建一个 Call 对象,用于执行请求
Call call = client.newCall(request);
// 将请求加入请求队列,并设置回调
call.enqueue(new Callback() { call.enqueue(new Callback() {
// 请求失败时调用
@Override @Override
public void onFailure(Call call, IOException e) { public void onFailure(Call call, IOException e) {
// 此处暂时未处理请求失败的情况,可以根据需求添加相应的处理逻辑
} }
// 请求成功时调用
@Override @Override
public void onResponse(Call call, Response response) throws IOException { public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){ if (response.isSuccessful()) {
// 获取响应体的内容长度,即文件的实际大小
long size = response.body().contentLength(); long size = response.body().contentLength();
String fileName = DownloadUtil.getSaveSongFile(song.getSinger(),song.getSongName(),song.getDuration(),song.getSongId(),size); // 根据歌曲信息和文件大小生成保存歌曲的文件名
String fileName = DownloadUtil.getSaveSongFile(song.getSinger(), song.getSongName(), song.getDuration(), song.getSongId(), size);
// 获取存储歌曲文件的目录
File downloadFile = new File(Api.STORAGE_SONG_FILE); File downloadFile = new File(Api.STORAGE_SONG_FILE);
String directory = String.valueOf(downloadFile); String directory = String.valueOf(downloadFile);
// 构建文件对象
File file = new File(fileName, directory); File file = new File(fileName, directory);
if (file.exists()) { if (file.exists()) {
// 如果文件存在,删除文件
file.delete(); file.delete();
} }
// 取消通知
getNotificationManager().cancel(1); getNotificationManager().cancel(1);
// 停止服务的前台运行状态
stopForeground(true); stopForeground(true);
} }
} }
}); });
}
} }

@ -28,164 +28,173 @@ import com.example.musicplayer.app.App;
/** /**
* Created by on 2018/10/26. * Created by on 2018/10/26.
* 使
*/ */
//通用的工具类
public class CommonUtil { public class CommonUtil {
// 存储 Toast 实例,避免重复创建
private static Toast toast; private static Toast toast;
// 控制活动的状态栏显示或隐藏
public static void hideStatusBar(Activity activity, boolean isHide) { public static void hideStatusBar(Activity activity, boolean isHide) {
// 获取活动的根视图
View decorView = activity.getWindow().getDecorView(); View decorView = activity.getWindow().getDecorView();
if (isHide) { if (isHide) {
if (Build.VERSION.SDK_INT >= 22) { if (Build.VERSION.SDK_INT >= 22) {
// 隐藏状态栏并使布局扩展到状态栏区域,状态栏透明
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View.SYSTEM_UI_FLAG_LAYOUT_STABLE); View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
activity.getWindow().setStatusBarColor(Color.TRANSPARENT); activity.getWindow().setStatusBarColor(Color.TRANSPARENT);
} }
} else { } else {
// 显示状态栏并设置状态栏颜色
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
activity.getWindow().setStatusBarColor(App.getContext().getResources().getColor(R.color.actionBarColor)); activity.getWindow().setStatusBarColor(App.getContext().getResources().getColor(R.color.actionBarColor));
} }
} }
// // 显示 Toast 消息
@SuppressLint("ShowToast") @SuppressLint("ShowToast")
public static void showToast(Context context, String message) { public static void showToast(Context context, String message) {
if (toast == null) { if (toast == null) {
// 创建一个新的 Toast 实例
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT); toast = Toast.makeText(context, message, Toast.LENGTH_SHORT);
} else { } else {
// 更新 Toast 的消息内容
toast.setText(message); toast.setText(message);
} }
// 显示 Toast
toast.show(); toast.show();
} }
/** // 获取屏幕的宽度
*
*/
public static int getScreenWidth(Context context) { public static int getScreenWidth(Context context) {
if (null == context) { if (null == context) {
return 0; return 0;
} }
// 获取屏幕的宽度像素值
return context.getResources().getDisplayMetrics().widthPixels; return context.getResources().getDisplayMetrics().widthPixels;
} }
/** // 获取屏幕的高度
*
*/
public static int getScreenHeight(Context context) { public static int getScreenHeight(Context context) {
if (null == context) { if (null == context) {
return 0; return 0;
} }
// 获取屏幕的高度像素值
return context.getResources().getDisplayMetrics().heightPixels; return context.getResources().getDisplayMetrics().heightPixels;
} }
/** // 使 EditText 获取焦点并显示软键盘
* EditText
*/
//弹出软键盘
public static void showKeyboard(EditText editText, Context context) { public static void showKeyboard(EditText editText, Context context) {
//其中editText为dialog中的输入框的 EditText if (editText!= null) {
if (editText != null) { // 使 EditText 可获得焦点
//设置可获得焦点
editText.setFocusable(true); editText.setFocusable(true);
editText.setFocusableInTouchMode(true); editText.setFocusableInTouchMode(true);
//请求获得焦点 // 请求获得焦点
editText.requestFocus(); editText.requestFocus();
//调用系统输入法 // 获取输入法管理器
InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.toggleSoftInput(0,InputMethodManager.HIDE_NOT_ALWAYS); // 显示软键盘
inputManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
} }
} }
/** // 关闭软键盘
*
*
* @param mEditText
* @param context
*/
public static void closeKeybord(EditText mEditText, Context context) { public static void closeKeybord(EditText mEditText, Context context) {
// 清除 EditText 的焦点
mEditText.clearFocus(); mEditText.clearFocus();
// 获取输入法管理器
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
// 隐藏软键盘
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
} }
/** // 使指定字符串在 TextView 中显示不同颜色
* 使
*/
public static void showStringColor(String appointStr, String originalStr, TextView textView) { public static void showStringColor(String appointStr, String originalStr, TextView textView) {
// 将原始字符串中的指定字符串替换为带有颜色标记的 HTML 字符串
originalStr = originalStr.replaceAll(appointStr, "<font color='#FFC66D'>" + appointStr + "</font>"); originalStr = originalStr.replaceAll(appointStr, "<font color='#FFC66D'>" + appointStr + "</font>");
// 使用 Html.fromHtml 将 HTML 字符串转换为 Spanned 并设置到 TextView 中
textView.setText(Html.fromHtml(originalStr)); textView.setText(Html.fromHtml(originalStr));
} }
//获取状态栏高度 // 获取状态栏高度
public static int getStatusHeightPx(Activity act) { public static int getStatusHeightPx(Activity act) {
int height = 0; int height = 0;
// 获取状态栏高度资源的标识符
int resourceId = act.getResources().getIdentifier("status_bar_height", "dimen", "android"); int resourceId = act.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) { if (resourceId > 0) {
// 获取状态栏的高度像素值
height = act.getResources().getDimensionPixelSize(resourceId); height = act.getResources().getDimensionPixelSize(resourceId);
} }
return height; return height;
} }
//高斯模糊
// 生成前景模糊的 Drawable
public static Drawable getForegroundDrawable(Bitmap bitmap) { public static Drawable getForegroundDrawable(Bitmap bitmap) {
/*得到屏幕的宽高比,以便按比例切割图片一部分*/ // 计算屏幕宽高比
final float widthHeightSize = (float) (DisplayUtil.getScreenWidth(App.getContext()) final float widthHeightSize = (float) (DisplayUtil.getScreenWidth(App.getContext())
* 1.0 / DisplayUtil.getScreenHeight(App.getContext()) * 1.0); * 1.0 / DisplayUtil.getScreenHeight(App.getContext()) * 1.0);
// 计算裁剪图片的宽度
int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight()); int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight());
int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0); int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0);
// 裁剪部分图片
/*切割部分图片*/
Bitmap cropBitmap = Bitmap.createBitmap(bitmap, cropBitmapWidthX, 0, cropBitmapWidth, Bitmap cropBitmap = Bitmap.createBitmap(bitmap, cropBitmapWidthX, 0, cropBitmapWidth,
bitmap.getHeight()); bitmap.getHeight());
/*缩小图片*/ // 缩小图片
Bitmap scaleBitmap = Bitmap.createScaledBitmap(cropBitmap, bitmap.getWidth() / 50, bitmap Bitmap scaleBitmap = Bitmap.createScaledBitmap(cropBitmap, bitmap.getWidth() / 50, bitmap
.getHeight() / 50, false); .getHeight() / 50, false);
/*模糊化*/ // 对缩小后的图片进行模糊处理
final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 3, true); final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 3, true);
// 创建模糊后的 Drawable
final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap); final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap);
/*加入灰色遮罩层,避免图片过亮影响其他控件*/ // 可添加灰色遮罩层(未实现)
return foregroundDrawable; return foregroundDrawable;
} }
public static Bitmap getImgBitmap(Context context,String imgUrl){ // 使用 Glide 加载图片并获取其 Bitmap
public static Bitmap getImgBitmap(Context context, String imgUrl) {
SimpleTarget target = new SimpleTarget<Drawable>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) { SimpleTarget target = new SimpleTarget<Drawable>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
@Override @Override
public void onResourceReady(@Nullable Drawable resource, Transition<? super Drawable> transition) { public void onResourceReady(@Nullable Drawable resource, Transition<? super Drawable> transition) {
// 当资源加载完成时,将 Drawable 转换为 Bitmap
Bitmap bitmap = ((BitmapDrawable) resource).getBitmap(); Bitmap bitmap = ((BitmapDrawable) resource).getBitmap();
} }
}; };
// 使用 Glide 加载图片,设置占位符和错误图片
Glide.with(context) Glide.with(context)
.load(imgUrl) .load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome)) .apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome)) .apply(RequestOptions.errorOf(R.drawable.welcome))
.into(target); .into(target);
// 此方法始终返回 null可能需要修改
return null; return null;
} }
public static void setImgWithGlide(Context context,String imgUrl,ImageView view){
// 使用 Glide 加载图片到 ImageView
public static void setImgWithGlide(Context context, String imgUrl, ImageView view) {
// 使用 Glide 加载图片,设置占位符和错误图片
Glide.with(context) Glide.with(context)
.load(imgUrl) .load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome)) .apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.love)) .apply(RequestOptions.errorOf(R.drawable.love))
.into(view); .into(view);
} }
public static void setSingerImg (Context context, String singer, ImageView view) {
if(singer.contains("/")){ // 根据歌手信息加载歌手图片到 ImageView
String[] s=singer.split("/"); public static void setSingerImg(Context context, String singer, ImageView view) {
singer=s[0]; if (singer.contains("/")) {
// 处理歌手名字中包含斜杠的情况
String[] s = singer.split("/");
singer = s[0];
} }
singer=singer.trim(); singer = singer.trim();
// 构建歌手图片的 URL
String imgUrl = Api.STORAGE_IMG_FILE + singer + ".jpg"; String imgUrl = Api.STORAGE_IMG_FILE + singer + ".jpg";
// 使用 Glide 加载图片,设置占位符和错误图片
Glide.with(context) Glide.with(context)
.load(imgUrl) .load(imgUrl)
.apply(RequestOptions.placeholderOf(R.drawable.welcome)) .apply(RequestOptions.placeholderOf(R.drawable.welcome))
.apply(RequestOptions.errorOf(R.drawable.welcome)) .apply(RequestOptions.errorOf(R.drawable.welcome))
.into(view); .into(view);
} }
} }

@ -4,18 +4,18 @@ import android.content.Context;
/** /**
* Created by on 2018/10/27. * Created by on 2018/10/27.
*
*/ */
public class DisplayUtil { public class DisplayUtil {
/*手柄起始角度*/ /* 手柄起始角度,可能用于旋转动画等操作,初始角度为 -30 度 */
public static final float ROTATION_INIT_NEEDLE = -30; public static final float ROTATION_INIT_NEEDLE = -30;
/*截图屏幕宽高*/ /* 基准屏幕的宽度和高度,作为比例计算的基础 */
private static final float BASE_SCREEN_WIDTH = (float) 1080.0; private static final float BASE_SCREEN_WIDTH = (float) 1080.0;
private static final float BASE_SCREEN_HEIGHT = (float) 1920.0; private static final float BASE_SCREEN_HEIGHT = (float) 1920.0;
/*唱针宽高、距离等比例*/ /* 唱针的各种比例相关的常量,可能用于布局调整或动画计算 */
public static final float SCALE_NEEDLE_WIDTH = (float) (276.0 / BASE_SCREEN_WIDTH); public static final float SCALE_NEEDLE_WIDTH = (float) (276.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_MARGIN_LEFT = (float) (500.0 / BASE_SCREEN_WIDTH); public static final float SCALE_NEEDLE_MARGIN_LEFT = (float) (500.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_NEEDLE_PIVOT_X = (float) (43.0 / BASE_SCREEN_WIDTH); public static final float SCALE_NEEDLE_PIVOT_X = (float) (43.0 / BASE_SCREEN_WIDTH);
@ -23,19 +23,19 @@ public class DisplayUtil {
public static final float SCALE_NEEDLE_HEIGHT = (float) (413.0 / BASE_SCREEN_HEIGHT); public static final float SCALE_NEEDLE_HEIGHT = (float) (413.0 / BASE_SCREEN_HEIGHT);
public static final float SCALE_NEEDLE_MARGIN_TOP = (float) (43.0 / BASE_SCREEN_HEIGHT); public static final float SCALE_NEEDLE_MARGIN_TOP = (float) (43.0 / BASE_SCREEN_HEIGHT);
/*唱盘比例*/ /* 唱盘比例,可能用于布局调整,确定唱盘的大小和位置 */
public static final float SCALE_DISC_SIZE = (float) (813.0 / BASE_SCREEN_WIDTH); public static final float SCALE_DISC_SIZE = (float) (813.0 / BASE_SCREEN_WIDTH);
public static final float SCALE_DISC_MARGIN_TOP = (float) (190 / BASE_SCREEN_HEIGHT); public static final float SCALE_DISC_MARGIN_TOP = (float) (190 / BASE_SCREEN_HEIGHT);
/*专辑图片比例*/ /* 专辑图片比例,可能用于布局调整,确定专辑图片的大小 */
public static final float SCALE_MUSIC_PIC_SIZE = (float) (533.0 / BASE_SCREEN_WIDTH); public static final float SCALE_MUSIC_PIC_SIZE = (float) (533.0 / BASE_SCREEN_WIDTH);
/*设备屏幕宽度*/ /* 获取设备屏幕宽度,以像素为单位 */
public static int getScreenWidth(Context context) { public static int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels; return context.getResources().getDisplayMetrics().widthPixels;
} }
/*设备屏幕高度*/ /* 获取设备屏幕高度,以像素为单位 */
public static int getScreenHeight(Context context) { public static int getScreenHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels; return context.getResources().getDisplayMetrics().heightPixels;
} }

@ -18,55 +18,80 @@ import static com.example.musicplayer.app.Api.STORAGE_SONG_FILE;
* desc : * desc :
* </pre> * </pre>
*/ */
public class DownloadUtil { public class DownloadUtil {
/** /**
* *
* @param fileName * @param fileName
* @return * @return
*/ */
public static final List<DownloadSong> getSongFromFile(String fileName){ public static final List<DownloadSong> getSongFromFile(String fileName) {
//将.m4a截取掉得到singer-songName-duration-songId-size // 存储已下载歌曲的列表
List<DownloadSong> res = new ArrayList<>(); List<DownloadSong> res = new ArrayList<>();
File file=new File(fileName); // 创建文件对象
if(!file.exists()){ File file = new File(fileName);
// 如果文件不存在,创建该目录
if (!file.exists()) {
file.mkdirs(); file.mkdirs();
return res; return res;
} }
// 获取目录下的所有文件
File[] subFile = file.listFiles(); File[] subFile = file.listFiles();
for (File value : subFile) { for (File value : subFile) {
// 获取文件名
String songFileName = value.getName(); String songFileName = value.getName();
// 去掉文件后缀名
String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
// 将文件名按 - 分割
String[] songValue = songFile.split("-"); String[] songValue = songFile.split("-");
// 获取文件大小
long size = Long.valueOf(songValue[4]); long size = Long.valueOf(songValue[4]);
//如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合 // 如果文件大小不等于实际文件大小,说明歌曲未下载完成,跳过该文件
if(size != value.length()) continue; if (size!= value.length()) continue;
// 创建 DownloadSong 对象
DownloadSong downloadSong = new DownloadSong(); DownloadSong downloadSong = new DownloadSong();
// 设置歌手信息
downloadSong.setSinger(songValue[0]); downloadSong.setSinger(songValue[0]);
// 设置歌曲名称
downloadSong.setName(songValue[1]); downloadSong.setName(songValue[1]);
// 设置歌曲时长
downloadSong.setDuration(Long.valueOf(songValue[2])); downloadSong.setDuration(Long.valueOf(songValue[2]));
// 设置歌曲 ID
downloadSong.setSongId(songValue[3]); downloadSong.setSongId(songValue[3]);
// 设置歌曲的 URL
downloadSong.setUrl(fileName + songFileName); downloadSong.setUrl(fileName + songFileName);
// 将 DownloadSong 对象添加到结果列表中
res.add(downloadSong); res.add(downloadSong);
} }
return res; return res;
} }
public static final boolean isExistOfDownloadSong(String songId){ /**
//将.m4a截取掉得到singer-songName-duration-songId-size *
File file=new File(STORAGE_SONG_FILE); * @param songId ID
if(!file.exists()){ * @return
*/
public static final boolean isExistOfDownloadSong(String songId) {
// 创建文件对象,使用 STORAGE_SONG_FILE 作为文件根目录
File file = new File(STORAGE_SONG_FILE);
// 如果文件不存在,创建该目录
if (!file.exists()) {
file.mkdirs(); file.mkdirs();
return false; return false;
} }
// 获取目录下的所有文件
File[] subFile = file.listFiles(); File[] subFile = file.listFiles();
for (File value : subFile) { for (File value : subFile) {
// 获取文件名
String songFileName = value.getName(); String songFileName = value.getName();
// 去掉文件后缀名
String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); String songFile = songFileName.substring(0, songFileName.lastIndexOf("."));
// 将文件名按 - 分割
String[] songValue = songFile.split("-"); String[] songValue = songFile.split("-");
//如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合 // 如果歌曲 ID 匹配
if(songValue[3].equals(songId)) { if (songValue[3].equals(songId)) {
// 获取文件大小
long size = Long.valueOf(songValue[4]); long size = Long.valueOf(songValue[4]);
// 判断文件大小是否等于实际文件大小,如果相等表示已下载完成
return size == value.length(); return size == value.length();
} }
} }
@ -74,8 +99,9 @@ public class DownloadUtil {
} }
//组装下载歌曲的文件名 // 组装下载歌曲的文件名
public static final String getSaveSongFile(String singer,String songName,long duration,String songId,long size){ public static final String getSaveSongFile(String singer, String songName, long duration, String songId, long size) {
return singer+"-"+songName+"-"+duration+"-"+songId+"-"+size+".m4a"; // 按照 singer-songName-duration-songId-size.m4a 的格式组装文件名
return singer + "-" + songName + "-" + duration + "-" + songId + "-" + size + ".m4a";
} }
} }

@ -8,22 +8,27 @@ import android.graphics.Bitmap;
public class FastBlurUtil { public class FastBlurUtil {
public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) { public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) {
// 首先,根据 canReuseInBitmap 决定是否复用输入的 Bitmap若可以复用则直接使用否则复制一份
Bitmap bitmap; Bitmap bitmap;
if (canReuseInBitmap) { if (canReuseInBitmap) {
bitmap = sentBitmap; bitmap = sentBitmap;
} else { } else {
// 使用 copy 方法复制一份输入的 Bitmap并保留原始配置和像素信息
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
} }
// 若半径小于 1则不进行模糊处理直接返回 null
if (radius < 1) { if (radius < 1) {
return (null); return (null);
} }
// 获取位图的宽度和高度
int w = bitmap.getWidth(); int w = bitmap.getWidth();
int h = bitmap.getHeight(); int h = bitmap.getHeight();
// 将位图的像素信息存储到 pix 数组中,数组长度为宽度乘以高度
int[] pix = new int[w * h]; int[] pix = new int[w * h];
// 将 bitmap 的像素存储到 pix 数组中,存储格式为 ARGB 格式
bitmap.getPixels(pix, 0, w, 0, 0, w, h); bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1; int wm = w - 1;
@ -31,6 +36,7 @@ public class FastBlurUtil {
int wh = w * h; int wh = w * h;
int div = radius + radius + 1; int div = radius + radius + 1;
// 用于存储模糊处理后的 R、G、B 通道信息
int r[] = new int[wh]; int r[] = new int[wh];
int g[] = new int[wh]; int g[] = new int[wh];
int b[] = new int[wh]; int b[] = new int[wh];
@ -39,13 +45,16 @@ public class FastBlurUtil {
int divsum = (div + 1) >> 1; int divsum = (div + 1) >> 1;
divsum *= divsum; divsum *= divsum;
// 存储模糊处理的除数结果
int dv[] = new int[256 * divsum]; int dv[] = new int[256 * divsum];
// 初始化 dv 数组,存储用于模糊计算的中间结果
for (i = 0; i < 256 * divsum; i++) { for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum); dv[i] = (i / divsum);
} }
yw = yi = 0; yw = yi = 0;
// 用于存储模糊处理的栈信息
int[][] stack = new int[div][3]; int[][] stack = new int[div][3];
int stackpointer; int stackpointer;
int stackstart; int stackstart;
@ -55,163 +64,267 @@ public class FastBlurUtil {
int routsum, goutsum, boutsum; int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum; int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) { // 水平模糊处理
// 外层循环遍历图像的每一行
for (y = 0; y < h; y++) {
// 初始化颜色通道的求和变量,包括用于模糊计算的不同部分
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
// 内层循环遍历以当前像素为中心的水平滑动窗口,窗口大小由 radius 决定
for (i = -radius; i <= radius; i++) { for (i = -radius; i <= radius; i++) {
// 获取当前像素周围的像素,考虑边界情况,使用 Math.min 和 Math.max 确保不越界
p = pix[yi + Math.min(wm, Math.max(i, 0))]; p = pix[yi + Math.min(wm, Math.max(i, 0))];
// 获取栈中存储像素信息的数组
sir = stack[i + radius]; sir = stack[i + radius];
// 提取当前像素的红色通道值,通过位运算提取高 8 位
sir[0] = (p & 0xff0000) >> 16; sir[0] = (p & 0xff0000) >> 16;
// 提取当前像素的绿色通道值,通过位运算提取中间 8 位
sir[1] = (p & 0x00ff00) >> 8; sir[1] = (p & 0x00ff00) >> 8;
// 提取当前像素的蓝色通道值,通过位运算提取低 8 位
sir[2] = (p & 0x0000ff); sir[2] = (p & 0x0000ff);
// 计算像素的权重,距离中心像素越远,权重越低
rbs = r1 - Math.abs(i); rbs = r1 - Math.abs(i);
// 计算红色通道的加权和,乘以权重
rsum += sir[0] * rbs; rsum += sir[0] * rbs;
// 计算绿色通道的加权和,乘以权重
gsum += sir[1] * rbs; gsum += sir[1] * rbs;
// 计算蓝色通道的加权和,乘以权重
bsum += sir[2] * rbs; bsum += sir[2] * rbs;
// 对于窗口中位于当前像素右侧的像素,更新 rinsum、ginsum 和 binsum
if (i > 0) { if (i > 0) {
rinsum += sir[0]; rinsum += sir[0];
ginsum += sir[1]; ginsum += sir[1];
binsum += sir[2]; binsum += sir[2];
} else { } else {
// 对于窗口中位于当前像素左侧的像素,更新 routsum、goutsum 和 boutsum
routsum += sir[0]; routsum += sir[0];
goutsum += sir[1]; goutsum += sir[1];
boutsum += sir[2]; boutsum += sir[2];
} }
} }
// 栈指针初始化为半径
stackpointer = radius; stackpointer = radius;
// 开始内层循环,遍历当前行的每一个像素
for (x = 0; x < w; x++) { for (x = 0; x < w; x++) {
// 此处将继续处理每个像素的模糊计算,对提取的颜色通道值进行处理
r[yi] = dv[rsum]; // 代码在此处未完全展示,后续代码将根据计算的加权和和权重更新像素的颜色通道值
g[yi] = dv[gsum]; // 并更新相关的求和变量,以便下一个像素的计算
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
} }
p = pix[yw + vmin[x]]; }
// 将处理后的 R、G、B 通道值存储在对应的数组中
sir[0] = (p & 0xff0000) >> 16; // 将模糊处理后的红色通道值存储在 r 数组中,使用 dv 数组中的值进行映射
sir[1] = (p & 0x00ff00) >> 8; r[yi] = dv[rsum];
sir[2] = (p & 0x0000ff); // 将模糊处理后的绿色通道值存储在 g 数组中,使用 dv 数组中的值进行映射
g[yi] = dv[gsum];
rinsum += sir[0]; // 将模糊处理后的蓝色通道值存储在 b 数组中,使用 dv 数组中的值进行映射
ginsum += sir[1]; b[yi] = dv[bsum];
binsum += sir[2];
// 更新 rsum减去上一轮计算的 routsum
rsum += rinsum; rsum -= routsum;
gsum += ginsum; // 更新 gsum减去上一轮计算的 goutsum
bsum += binsum; gsum -= goutsum;
// 更新 bsum减去上一轮计算的 boutsum
stackpointer = (stackpointer + 1) % div; bsum -= boutsum;
sir = stack[(stackpointer) % div];
// 计算栈的起始位置,考虑到循环利用栈空间
routsum += sir[0]; stackstart = stackpointer - radius + div;
goutsum += sir[1]; // 获取栈中相应位置的元素
boutsum += sir[2]; sir = stack[stackstart % div];
rinsum -= sir[0]; // 从 routsum 中减去栈中元素的红色通道值
ginsum -= sir[1]; routsum -= sir[0];
binsum -= sir[2]; // 从 goutsum 中减去栈中元素的绿色通道值
goutsum -= sir[1];
// 从 boutsum 中减去栈中元素的蓝色通道值
boutsum -= sir[2];
// 如果当前处于第一行
if (y == 0) {
// 计算当前列的最小有效位置,避免越界
vmin[x] = Math.min(x + radius + 1, wm);
}
// 获取当前像素位置的像素值
p = pix[yw + vmin[x]];
// 提取当前像素的红色通道值
sir[0] = (p & 0xff0000) >> 16;
// 提取当前像素的绿色通道值
sir[1] = (p & 0x00ff00) >> 8;
// 提取当前像素的蓝色通道值
sir[2] = (p & 0x0000ff);
// 更新 rinsum加上当前像素的红色通道值
rinsum += sir[0];
// 更新 ginsum加上当前像素的绿色通道值
ginsum += sir[1];
// 更新 binsum加上当前像素的蓝色通道值
binsum += sir[2];
// 更新 rsum加上 rinsum
rsum += rinsum;
// 更新 gsum加上 ginsum
gsum += ginsum;
// 更新 bsum加上 binsum
bsum += binsum;
// 更新栈指针位置,通过取模实现循环利用栈空间
stackpointer = (stackpointer + 1) % div;
// 获取栈中相应位置的元素
sir = stack[(stackpointer) % div];
// 更新 routsum加上栈中元素的红色通道值
routsum += sir[0];
// 更新 goutsum加上栈中元素的绿色通道值
goutsum += sir[1];
// 更新 boutsum加上栈中元素的蓝色通道值
boutsum += sir[2];
// 从 rinsum 中减去栈中元素的红色通道值
rinsum -= sir[0];
// 从 ginsum 中减去栈中元素的绿色通道值
ginsum -= sir[1];
// 从 binsum 中减去栈中元素的蓝色通道值
binsum -= sir[2];
// 移动到下一个像素位置(同一行的下一个像素)
yi++;
}
// 移动到下一行
yw += w;
yi++; // 垂直模糊处理
} for (x = 0; x < w; x++) {
yw += w; // 初始化颜色通道的求和变量为 0
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
// 初始化垂直偏移量,用于计算垂直方向的滑动窗口位置
yp = -radius * w; yp = -radius * w;
// 遍历垂直方向的滑动窗口,窗口大小由 radius 决定
for (i = -radius; i <= radius; i++) { for (i = -radius; i <= radius; i++) {
// 计算当前像素在垂直方向上的位置,确保不越界
yi = Math.max(0, yp) + x; yi = Math.max(0, yp) + x;
// 获取栈中存储像素信息的数组
sir = stack[i + radius]; sir = stack[i + radius];
// 从 r 数组中获取当前像素的红色通道值
sir[0] = r[yi]; sir[0] = r[yi];
// 从 g 数组中获取当前像素的绿色通道值
sir[1] = g[yi]; sir[1] = g[yi];
// 从 b 数组中获取当前像素的蓝色通道值
sir[2] = b[yi]; sir[2] = b[yi];
// 计算当前像素的权重,距离中心像素越远,权重越低
rbs = r1 - Math.abs(i); rbs = r1 - Math.abs(i);
// 计算红色通道的加权和,乘以权重
rsum += r[yi] * rbs; rsum += r[yi] * rbs;
// 计算绿色通道的加权和,乘以权重
gsum += g[yi] * rbs; gsum += g[yi] * rbs;
// 计算蓝色通道的加权和,乘以权重
bsum += b[yi] * rbs; bsum += b[yi] * rbs;
// 对于窗口中位于当前像素下方的像素,更新 rinsum、ginsum 和 binsum
if (i > 0) { if (i > 0) {
rinsum += sir[0]; rinsum += sir[0];
ginsum += sir[1]; ginsum += sir[1];
binsum += sir[2]; binsum += sir[2];
} else { } else {
// 对于窗口中位于当前像素上方的像素,更新 routsum、goutsum 和 boutsum
routsum += sir[0]; routsum += sir[0];
goutsum += sir[1]; goutsum += sir[1];
boutsum += sir[2]; boutsum += sir[2];
} }
// 向下移动到下一个像素,更新垂直偏移量
if (i < hm) { if (i < hm) {
yp += w; yp += w;
} }
} }
// 初始化 yi 为当前列的位置
yi = x; yi = x;
// 栈指针初始化为半径
stackpointer = radius; stackpointer = radius;
// 遍历当前列的每一个像素
for (y = 0; y < h; y++) { for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] ) // 组合处理后的 R、G、B 通道值,并保留 alpha 通道,更新像素信息
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum];
// 更新 rsum减去 routsum
rsum -= routsum; rsum -= routsum;
// 更新 gsum减去 goutsum
gsum -= goutsum; gsum -= goutsum;
// 更新 bsum减去 boutsum
bsum -= boutsum; bsum -= boutsum;
// 计算栈的起始位置,考虑到循环利用栈空间
stackstart = stackpointer - radius + div; stackstart = stackpointer - radius + div;
// 获取栈中相应位置的元素
sir = stack[stackstart % div]; sir = stack[stackstart % div];
// 从 routsum 中减去栈中元素的红色通道值
routsum -= sir[0]; routsum -= sir[0];
// 从 goutsum 中减去栈中元素的绿色通道值
goutsum -= sir[1]; goutsum -= sir[1];
// 从 boutsum 中减去栈中元素的蓝色通道值
boutsum -= sir[2]; boutsum -= sir[2];
// 如果当前处于第一列
if (x == 0) { if (x == 0) {
// 计算当前行的最小有效位置,避免越界
vmin[y] = Math.min(y + r1, hm) * w; vmin[y] = Math.min(y + r1, hm) * w;
} }
// 获取当前像素位置的像素值
p = x + vmin[y]; p = x + vmin[y];
// 从 r 数组中获取当前像素的红色通道值
sir[0] = r[p]; sir[0] = r[p];
// 从 g 数组中获取当前像素的绿色通道值
sir[1] = g[p]; sir[1] = g[p];
// 从 b 数组中获取当前像素的蓝色通道值
sir[2] = b[p]; sir[2] = b[p];
// 更新 rinsum加上当前像素的红色通道值
rinsum += sir[0]; rinsum += sir[0];
// 更新 ginsum加上当前像素的绿色通道值
ginsum += sir[1]; ginsum += sir[1];
// 更新 binsum加上当前像素的蓝色通道值
binsum += sir[2]; binsum += sir[2];
// 更新 rsum加上 rinsum
rsum += rinsum; rsum += rinsum;
// 更新 gsum加上 ginsum
gsum += ginsum; gsum += ginsum;
bsum += binsum; // 更新 bsum加上 binsum
bsum += bsum;
// 更新栈指针位置,通过取模实现循环利用栈空间
stackpointer = (stackpointer + 1) % div; stackpointer = (stackpointer + 1) % div;
// 获取栈中相应位置的元素
sir = stack[stackpointer]; sir = stack[stackpointer];
// 更新 routsum加上栈中元素的红色通道值
routsum += sir[0]; routsum += sir[0];
// 更新 goutsum加上栈中元素的绿色通道值
goutsum += sir[1]; goutsum += sir[1];
// 更新 boutsum加上栈中元素的蓝色通道值
boutsum += sir[2]; boutsum += sir[2];
// 从 rinsum 中减去栈中元素的红色通道值
rinsum -= sir[0]; rinsum -= sir[0];
// 从 ginsum 中减去栈中元素的绿色通道值
ginsum -= sir[1]; ginsum -= sir[1];
// 从 binsum 中减去栈中元素的蓝色通道值
binsum -= sir[2]; binsum -= sir[2];
// 移动到下一个像素位置(下一列的同一像素)
yi += w; yi += w;
} }
} }
// 将处理后的像素信息重新设置到 Bitmap 中
bitmap.setPixels(pix, 0, w, 0, 0, w, h); bitmap.setPixels(pix, 0, w, 0, 0, w, h);
// 返回模糊处理后的 Bitmap
return (bitmap); return (bitmap);
} }
} }

@ -1,6 +1,5 @@
package com.example.musicplayer.util; package com.example.musicplayer.util;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.util.Log; import android.util.Log;
@ -24,31 +23,31 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
/** /**
* user * user
* Created by on 2018/7/24. * Created by on 2018/7/24.
*/ */
public class FileUtil { public class FileUtil {
private static String TAG = "FileUtil"; private static String TAG = "FileUtil";
/** /**
* person * song
* params: * params:
* p:person * song: Song
*/ */
public static void saveSong(Song song) { public static void saveSong(Song song) {
try { try {
// 获取应用外部存储目录下的 yuanmusic 文件夹路径
File file = new File(App.getContext().getExternalFilesDir("yuanmusic").getAbsolutePath()); File file = new File(App.getContext().getExternalFilesDir("yuanmusic").getAbsolutePath());
if (!file.exists()) { if (!file.exists()) {
// 如果文件夹不存在,创建该文件夹
file.mkdirs(); file.mkdirs();
} }
//写对象流的对象 // 创建用于存储 Song 对象的文件
File userFile = new File(file, "song.txt"); File userFile = new File(file, "song.txt");
// 创建对象输出流,将对象序列化到文件中
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(userFile)); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(userFile));
oos.writeObject(song);//将Person对象p写入到oos oos.writeObject(song); // 将 Song 对象 song 写入到 oos
oos.close(); //关闭文件流 oos.close(); // 关闭文件流
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
} catch (IOException e) { } catch (IOException e) {
@ -57,38 +56,42 @@ public class FileUtil {
} }
/** /**
* Person * Song
*/ */
public static Song getSong() { public static Song getSong() {
try { try {
// 创建对象输入流,从文件中读取序列化的对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(App.getContext().getExternalFilesDir("") + "/yuanmusic/song.txt")); ObjectInputStream ois = new ObjectInputStream(new FileInputStream(App.getContext().getExternalFilesDir("") + "/yuanmusic/song.txt"));
Song song = (Song) ois.readObject();//读出对象 // 读取对象并强制转换为 Song 类型
return song; //返回对象 Song song = (Song) ois.readObject();
return song; // 返回读取的 Song 对象
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
// 当文件不存在时,创建一个新的 Song 对象作为默认值
Song song = new Song(); Song song = new Song();
return song; return song;
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
//保存图片到本地 // 保存图片到本地
public static void saveImgToNative(Context context, Bitmap bitmap, String singer) { public static void saveImgToNative(Context context, Bitmap bitmap, String singer) {
// 创建存储图片的文件目录
File file = new File(Api.STORAGE_IMG_FILE); File file = new File(Api.STORAGE_IMG_FILE);
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); file.mkdirs();
} }
// 创建存储歌手图片的文件
File singerImgFile = new File(file, singer + ".jpg"); File singerImgFile = new File(file, singer + ".jpg");
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
// 创建文件输出流
fos = new FileOutputStream(singerImgFile); fos = new FileOutputStream(singerImgFile);
// 将 Bitmap 以 JPEG 格式压缩并写入文件
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush(); fos.flush();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
@ -98,7 +101,7 @@ public class FileUtil {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
try { try {
if (fos != null) { if (fos!= null) {
fos.close(); fos.close();
} }
} catch (IOException e) { } catch (IOException e) {
@ -107,17 +110,21 @@ public class FileUtil {
} }
} }
//保存歌词到本地 // 保存歌词到本地
public static void saveLrcToNative(String lrc,String songName){ public static void saveLrcToNative(String lrc, String songName) {
//开启线程保存歌词 // 开启线程保存歌词,避免在主线程中进行 I/O 操作
new Thread(() -> { new Thread(() -> {
// 创建存储歌词的文件目录
File file = new File(Api.STORAGE_LRC_FILE); File file = new File(Api.STORAGE_LRC_FILE);
if (!file.exists()) { if (!file.exists()) {
file.mkdirs(); file.mkdirs();
} }
// 创建存储歌词的文件
File lrcFile = new File(file, songName + Constant.LRC); File lrcFile = new File(file, songName + Constant.LRC);
try { try {
// 创建文件写入器
FileWriter fileWriter = new FileWriter(lrcFile); FileWriter fileWriter = new FileWriter(lrcFile);
// 写入歌词
fileWriter.write(lrc); fileWriter.write(lrc);
fileWriter.close(); fileWriter.close();
} catch (IOException e) { } catch (IOException e) {
@ -128,22 +135,22 @@ public class FileUtil {
public static String getLrcFromNative(String songName) { public static String getLrcFromNative(String songName) {
try { try {
FileReader fileReader = new FileReader(Api.STORAGE_LRC_FILE+songName+Constant.LRC); // 创建文件读取器读取歌词文件
FileReader fileReader = new FileReader(Api.STORAGE_LRC_FILE + songName + Constant.LRC);
BufferedReader bufferedReader = new BufferedReader(fileReader); BufferedReader bufferedReader = new BufferedReader(fileReader);
StringBuilder lrc = new StringBuilder(); StringBuilder lrc = new StringBuilder();
while (true){ while (true) {
// 逐行读取歌词
String s = bufferedReader.readLine(); String s = bufferedReader.readLine();
if(s == null) break; if (s == null) break;
lrc.append(s).append("\n"); lrc.append(s).append("\n");
} }
fileReader.close(); fileReader.close();
Log.d(TAG, "getLrcFromNative: "+lrc.toString()); Log.d(TAG, "getLrcFromNative: " + lrc.toString());
return lrc.toString(); return lrc.toString();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
return null; return null;
} }
} }

@ -12,16 +12,17 @@ import java.util.List;
* desc : * desc :
* </pre> * </pre>
*/ */
public class LrcUtil { public class LrcUtil {
/** /**
* *
* *
* @param lrcStr * @param lrcStr
* @return * @return
*/ */
public static List<LrcBean> parseStr2List(String lrcStr) { public static List<LrcBean> parseStr2List(String lrcStr) {
// 存储解析后的歌词对象列表
List<LrcBean> list = new ArrayList<>(); List<LrcBean> list = new ArrayList<>();
// 替换歌词字符串中的特殊字符
String lrcText = lrcStr.replaceAll("&#58;", ":") String lrcText = lrcStr.replaceAll("&#58;", ":")
.replaceAll("&apos;", "'") .replaceAll("&apos;", "'")
.replaceAll("&#10;", "\n") .replaceAll("&#10;", "\n")
@ -30,61 +31,100 @@ public class LrcUtil {
.replaceAll("&#45;", "-") .replaceAll("&#45;", "-")
.replaceAll("&#13;", "\r") .replaceAll("&#13;", "\r")
.replaceAll("&#39;", "'"); .replaceAll("&#39;", "'");
// 按行分割歌词字符串
String[] split = lrcText.split("\n"); String[] split = lrcText.split("\n");
for (int i = 4; i < split.length; i++) { for (int i = 4; i < split.length; i++) {
String lrc = split[i]; String lrc = split[i];
if (lrc.contains(".")) { if (lrc.contains(".")) {
// 提取分钟数
String min = lrc.substring(lrc.indexOf("[") + 1, lrc.indexOf("[") + 3); String min = lrc.substring(lrc.indexOf("[") + 1, lrc.indexOf("[") + 3);
// 提取秒数
String seconds = lrc.substring(lrc.indexOf(":") + 1, lrc.indexOf(":") + 3); String seconds = lrc.substring(lrc.indexOf(":") + 1, lrc.indexOf(":") + 3);
// 提取毫秒数
String mills = lrc.substring(lrc.indexOf(".") + 1, lrc.indexOf(".") + 3); String mills = lrc.substring(lrc.indexOf(".") + 1, lrc.indexOf(".") + 3);
// 计算开始时间(毫秒)
long startTime = Long.valueOf(min) * 60 * 1000 + Long.valueOf(seconds) * 1000 + Long.valueOf(mills) * 10; long startTime = Long.valueOf(min) * 60 * 1000 + Long.valueOf(seconds) * 1000 + Long.valueOf(mills) * 10;
String text = lrc.substring(lrc.indexOf("]") + 1); // 提取歌词文本
if (text.equals("") || text == null) continue; // 从歌词行中提取时间戳后面的文本内容
if (i == 5) { String text = lrc.substring(lrc.indexOf("]") + 1);
// 如果提取的文本内容为空字符串或为 null则跳过当前歌词行的处理继续下一行的处理
if (text.equals("") || text == null) continue;
// 对于第 5 行歌词进行特殊处理
if (i == 5) {
// 查找第一个左括号的位置
int first = text.indexOf("("); int first = text.indexOf("(");
// 查找第二个左括号的位置,从第一个左括号后开始查找
int second = text.indexOf("(", first + 1); int second = text.indexOf("(", first + 1);
// 存储第一个括号前的文本内容
String textFront = null; String textFront = null;
// 存储第一个括号和第二个括号之间的文本内容
String textBehind = null; String textBehind = null;
// 标记是否存在第一个括号和第二个括号之间的文本
boolean isTwo = true; boolean isTwo = true;
// 标记是否存在第一个括号前的文本
boolean isOne = true; boolean isOne = true;
try { try {
// 尝试提取第一个括号前的文本内容
textFront = text.substring(0, first); textFront = text.substring(0, first);
}catch (StringIndexOutOfBoundsException e){ } catch (StringIndexOutOfBoundsException e) {
// 如果出现字符串越界异常,说明第一个括号不存在,标记为 false
isOne = false; isOne = false;
} }
try { try {
// 尝试提取第一个括号和第二个括号之间的文本内容
textBehind = text.substring(first + 1, second); textBehind = text.substring(first + 1, second);
} catch (StringIndexOutOfBoundsException e) { } catch (StringIndexOutOfBoundsException e) {
// 如果出现字符串越界异常,说明第二个括号不存在,标记为 false
isTwo = false; isTwo = false;
} }
LrcBean lrcBean1 = new LrcBean(); // 处理越界异常
lrcBean1.setStart(startTime); isTwo = false;
if(isOne) { // 创建一个新的 LrcBean 实例
LrcBean lrcBean1 = new LrcBean();
// 设置该 LrcBean 的开始时间为之前计算得到的 startTime
lrcBean1.setStart(startTime);
// 如果存在第一个括号前的文本isOne 为 true将其设置为该 LrcBean 的歌词内容
if (isOne) {
lrcBean1.setLrc(textFront); lrcBean1.setLrc(textFront);
} else { // 否则,将整个文本设置为歌词内容
} else {
lrcBean1.setLrc(text); lrcBean1.setLrc(text);
} }
list.add(lrcBean1); // 将创建的 LrcBean 添加到存储歌词对象的列表中
if (isTwo) { list.add(lrcBean1);
// 如果存在第二个括号及其之间的文本isTwo 为 true
if (isTwo) {
// 创建另一个新的 LrcBean 实例
LrcBean lrcBean2 = new LrcBean(); LrcBean lrcBean2 = new LrcBean();
// 设置该 LrcBean 的开始时间为 startTime
lrcBean2.setStart(startTime); lrcBean2.setStart(startTime);
// 将第一个括号和第二个括号之间的文本设置为该 LrcBean 的歌词内容
lrcBean2.setLrc(textBehind); lrcBean2.setLrc(textBehind);
// 将这个 LrcBean 也添加到列表中
list.add(lrcBean2); list.add(lrcBean2);
} }
} else { // 对于其他行(非第 5 行)
} else {
// 创建一个新的 LrcBean 实例
LrcBean lrcBean = new LrcBean(); LrcBean lrcBean = new LrcBean();
// 设置该 LrcBean 的开始时间为 startTime
lrcBean.setStart(startTime); lrcBean.setStart(startTime);
// 将提取的文本设置为该 LrcBean 的歌词内容
lrcBean.setLrc(text); lrcBean.setLrc(text);
// 将该 LrcBean 添加到列表中
list.add(lrcBean); list.add(lrcBean);
} }
if (list.size() > 1) { // 如果存储歌词对象的列表长度大于 1
if (list.size() > 1) {
// 将上一个歌词对象的结束时间设置为当前歌词对象的开始时间
list.get(list.size() - 2).setEnd(startTime); list.get(list.size() - 2).setEnd(startTime);
} }
if (i == split.length - 1) { // 如果当前是最后一行歌词
if (i == split.length - 1) {
// 将最后一个歌词对象的结束时间设置为 startTime 加上 100000 毫秒
list.get(list.size() - 1).setEnd(startTime + 100000); list.get(list.size() - 1).setEnd(startTime + 100000);
} }
} // 完成整个歌词字符串的解析,返回存储歌词对象的列表
} return list;
return list;
} }
} }

@ -14,40 +14,45 @@ import java.security.NoSuchAlgorithmException;
* desc : MD5 * desc : MD5
* </pre> * </pre>
*/ */
public class MD5Util { public class MD5Util {
public static String getFileMD5(File file){ public static String getFileMD5(File file) {
if(file == null||!file.exists()) return ""; // 如果文件为空或不存在,返回空字符串
if (file == null ||!file.exists()) return "";
FileInputStream in = null; FileInputStream in = null;
// 用于存储文件读取的数据
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
// 存储最终的 MD5 结果
StringBuilder res = new StringBuilder(); StringBuilder res = new StringBuilder();
int len; int len;
try { try {
// 获取 MD5 消息摘要对象
MessageDigest messageDigest = MessageDigest.getInstance("MD5"); MessageDigest messageDigest = MessageDigest.getInstance("MD5");
in = new FileInputStream(file); in = new FileInputStream(file);
while ((len=in.read(buffer))!=-1){ // 循环读取文件内容
//计算文件时需要通过分段读取多次调用update来将数据更新给MessageDigest对象 while ((len = in.read(buffer))!= -1) {
messageDigest.update(buffer,0,len); // 更新消息摘要对象,分段读取文件数据
messageDigest.update(buffer, 0, len);
} }
//真正计算文件的MD5值 // 计算最终的 MD5 摘要
byte[] bytes = messageDigest.digest(); byte[] bytes = messageDigest.digest();
//将字节数组转换成16进制的字符串 // 将 MD5 摘要的字节数组转换为 16 进制字符串
for(byte b:bytes){ for (byte b : bytes) {
String temp = Integer.toHexString(b&0xff); String temp = Integer.toHexString(b & 0xff);
if(temp.length()!=2){ if (temp.length()!= 2) {
temp = "0"+temp; temp = "0" + temp;
} }
res.append(temp); res.append(temp);
} }
//返回最终的字符串 // 返回最终的 MD5 字符串
return res.toString(); return res.toString();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
if(null!=in){ if (null!= in) {
try { try {
// 关闭文件输入流
in.close(); in.close();
}catch (Exception e){ } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }

@ -20,33 +20,39 @@ import java.io.FileNotFoundException;
/** /**
* Created by on 2018/10/22. * Created by on 2018/10/22.
*/ */
public class MediaUtil { public class MediaUtil {
// 格式化时间的方法,将毫秒数转换为分钟和秒的字符串表示
public static String formatTime(long time) { public static String formatTime(long time) {
// 计算分钟数
String min = time / 60 + ""; String min = time / 60 + "";
// 计算秒数
String sec = time % 60 + ""; String sec = time % 60 + "";
if(sec.length()<2){ // 如果秒数不足两位,在前面补 0
sec = "0"+sec; if (sec.length() < 2) {
sec = "0" + sec;
} }
// 返回格式化后的时间字符串,格式为 "分钟:秒"
return min + ":" + sec; return min + ":" + sec;
} }
public static String formatSinger(String singer){
if(singer.contains("/")){ // 格式化歌手信息的方法,如果歌手信息包含 "/",取第一个部分
String []s=singer.split("/"); public static String formatSinger(String singer) {
singer=s[0]; if (singer.contains("/")) {
// 按 "/" 分割歌手信息
String[] s = singer.split("/");
singer = s[0];
} }
// 去除前后的空格
return singer.trim(); return singer.trim();
} }
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
public static String formatSize(long size){ // 格式化文件大小的方法,将字节数转换为兆字节表示,保留一位小数
double d = (double) size/1024/1024; public static String formatSize(long size) {
return String.format("%.1f",d); // 将字节数转换为兆字节
double d = (double) size / 1024 / 1024;
// 格式化浮点数,保留一位小数
return String.format("%.1f", d);
} }
} }

@ -9,23 +9,32 @@ import android.content.Context;
* desc : * desc :
* </pre> * </pre>
*/ */
public class ScreenUtil { public class ScreenUtil {
/** /**
* dp px() * dp px()
* @param context
* @param dpValue dp
* @return
*/ */
public static int dip2px(Context context, float dpValue) { public static int dip2px(Context context, float dpValue) {
// 获取设备的屏幕密度
final float scale = context.getResources().getDisplayMetrics().density; final float scale = context.getResources().getDisplayMetrics().density;
// 将 dp 值乘以屏幕密度并四舍五入,转换为像素值
return (int) (dpValue * scale + 0.5f); return (int) (dpValue * scale + 0.5f);
} }
/** /**
* px() dp * px() dp
* @param context
* @param pxValue
* @return dp
*/ */
public static int px2dip(Context context, float pxValue) { public static int px2dip(Context context, float pxValue) {
// 获取设备的屏幕密度
final float scale = context.getResources().getDisplayMetrics().density; final float scale = context.getResources().getDisplayMetrics().density;
// 将像素值除以屏幕密度并四舍五入,转换为 dp 值
return (int) (pxValue / scale + 0.5f); return (int) (pxValue / scale + 0.5f);
} }
} }

@ -12,18 +12,28 @@ import java.util.List;
* desc : * desc :
* </pre> * </pre>
*/ */
public class ServiceUtil { public class ServiceUtil {
/**
*
* @param context
* @param serviceName
* @return true false
*/
public static boolean isServiceRunning(Context context, String serviceName) { public static boolean isServiceRunning(Context context, String serviceName) {
// 获取系统的 ActivityManager 服务
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// 获取正在运行的服务列表,最多 100 个
List<ActivityManager.RunningServiceInfo> infos = am.getRunningServices(100); List<ActivityManager.RunningServiceInfo> infos = am.getRunningServices(100);
// 遍历正在运行的服务列表
for (ActivityManager.RunningServiceInfo info : infos) { for (ActivityManager.RunningServiceInfo info : infos) {
// 获取服务的类名
String name = info.service.getClassName(); String name = info.service.getClassName();
// 如果服务类名与要检查的服务类名相同,返回 true
if (name.equals(serviceName)) { if (name.equals(serviceName)) {
return true; return true;
} }
} }
// 若未找到匹配的服务,返回 false
return false; return false;
} }
} }

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

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

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

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

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

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

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

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

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

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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save