Android 音乐APP(一)扫描本地音乐(1) |
您所在的位置:网站首页 › 扫描不出本地音乐 › Android 音乐APP(一)扫描本地音乐(1) |
然后当然是要在MainActivity中做处理了。 package com.llw.goodmusic.ui; import android.content.Intent; import android.os.Bundle; import android.view.View; import com.llw.goodmusic.R; import com.llw.goodmusic.basic.BasicActivity; /** 主页面 @author llw */ public class MainActivity extends BasicActivity { @Override public void initData(Bundle savedInstanceState) { } @Override public int getLayoutId() { return R.layout.activity_main; } public void onClick(View view) { startActivity(new Intent(context,LocalMusicActivity.class)); } } 里面的代码也比较的简单,继承BasicActivity。然后重写initData和getLayoutId,再绑定布局中的onclick就可以了。那么它要跳转到LocalMusicActivity。这个Activity现在还没有的,那就创建一个。创建好了之后同样继承BasicActivity,重写里面的两个方法,绑定布局之后,下面来写这个页面的布局。activity_local_music.xml代码如下: 里面有两个布局,一个是用来扫描本地歌曲的,一个是用来显示歌曲的列表,如果扫描不到就提示一下。现在页面的布局有了,下面就是要来写这个页面的业务逻辑。 ⑤ 权限请求之前在AndroidManifest.xml中注册了静态的文件读写权限,而在Android 6.0之后。危险权限需要动态申请才能够使用。所以我在build.gradle中增加了一个权限请求框架,现在就来使用吧。 /** 动态权限请求*/ private void permissionsRequest() { PermissionX.init(this).permissions( //写入文件 Manifest.permission.WRITE_EXTERNAL_STORAGE) .onExplainRequestReason(new ExplainReasonCallbackWithBeforeParam() { @Override public void onExplainReason(ExplainScope scope, List deniedList, boolean beforeRequest) { scope.showRequestReasonDialog(deniedList, “即将申请的权限是程序必须依赖的权限”, “我已明白”); } }) .onForwardToSettings(new ForwardToSettingsCallback() { @Override public void onForwardToSettings(ForwardScope scope, List deniedList) { scope.showForwardToSettingsDialog(deniedList, “您需要去应用程序设置当中手动开启权限”, “我已明白”); } }) .setDialogTintColor(R.color.white, R.color.app_color) .request(new RequestCallback() { @Override public void onResult(boolean allGranted, List grantedList, List deniedList) { if (allGranted) { //通过后的业务逻辑 } else { show(“您拒绝了如下权限:” + deniedList); } } }); } OK,权限申请就是这么简单。 ⑥ 获取音乐数据首先需要些几个工具类,方便APP后面的开发。第一个是日志,这里不用系统自带的日志。在utils包下新建一个BLog类。里面的代码如下: package com.llw.goodmusic.utils; import android.text.TextUtils; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** 日志 @author llw */ public class BLog { private static boolean IS_SHOW_LOG = true; private static final String DEFAULT_MESSAGE = “execute”; private static final String LINE_SEPARATOR = System.getProperty(“line.separator”); private static final int JSON_INDENT = 4; private static final int V = 0x1; private static final int D = 0x2; private static final int I = 0x3; private static final int W = 0x4; private static final int E = 0x5; private static final int A = 0x6; private static final int JSON = 0x7; public static void init(boolean isShowLog) { IS_SHOW_LOG = isShowLog; } public static void v() { printLog(V, null, DEFAULT_MESSAGE); } public static void v(String msg) { printLog(V, null, msg); } public static void v(String tag, String msg) { printLog(V, tag, msg); } public static void d() { printLog(D, null, DEFAULT_MESSAGE); } public static void d(String msg) { printLog(D, null, msg); } public static void d(String tag, String msg) { printLog(D, tag, msg); } public static void i() { printLog(I, null, DEFAULT_MESSAGE); } public static void i(String msg) { printLog(I, null, msg); } public static void i(String tag, String msg) { printLog(I, tag, msg); } public static void w() { printLog(W, null, DEFAULT_MESSAGE); } public static void w(String msg) { printLog(W, null, msg); } public static void w(String tag, String msg) { printLog(W, tag, msg); } public static void e() { printLog(E, null, DEFAULT_MESSAGE); } public static void e(String msg) { printLog(E, null, msg); } public static void e(String tag, String msg) { printLog(E, tag, msg); } public static void a() { printLog(A, null, DEFAULT_MESSAGE); } public static void a(String msg) { printLog(A, null, msg); } public static void a(String tag, String msg) { printLog(A, tag, msg); } public static void json(String jsonFormat) { printLog(JSON, null, jsonFormat); } public static void json(String tag, String jsonFormat) { printLog(JSON, tag, jsonFormat); } private static void printLog(int type, String tagStr, String msg) { if (!IS_SHOW_LOG) { return; } StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); int index = 4; String className = stackTrace[index].getFileName(); String methodName = stackTrace[index].getMethodName(); int lineNumber = stackTrace[index].getLineNumber(); String tag = (tagStr == null ? className : tagStr); methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(“[ (”).append(className).append(“:”).append(lineNumber).append(“)#”).append(methodName).append(" ] "); if (msg != null && type != JSON) { stringBuilder.append(msg); } String logStr = stringBuilder.toString(); switch (type) { case V: Log.v(tag, logStr); break; case D: Log.d(tag, logStr); break; case I: Log.i(tag, logStr); break; case W: Log.w(tag, logStr); break; case E: Log.e(tag, logStr); break; case A: Log.wtf(tag, logStr); break; case JSON: { if (TextUtils.isEmpty(msg)) { Log.d(tag, “Empty or Null json content”); return; } String message = null; try { if (msg.startsWith(“{”)) { JSONObject jsonObject = new JSONObject(msg); message = jsonObject.toString(JSON_INDENT); } else if (msg.startsWith(“[”)) { JSONArray jsonArray = new JSONArray(msg); message = jsonArray.toString(JSON_INDENT); } } catch (JSONException e) { e(tag, e.getCause().getMessage() + “\n” + msg); return; } printLine(tag, true); message = logStr + LINE_SEPARATOR + message; String[] lines = message.split(LINE_SEPARATOR); StringBuilder jsonContent = new StringBuilder(); for (String line : lines) { jsonContent.append("║ ").append(line).append(LINE_SEPARATOR); } Log.d(tag, jsonContent.toString()); printLine(tag, false); } break; default: break; } } private static void printLine(String tag, boolean isTop) { if (isTop) { Log.d(tag, “╔═══════════════════════════════════════════════════════════════════════════════════════”); } else { Log.d(tag, “╚═══════════════════════════════════════════════════════════════════════════════════════”); } } } 为了方便使用,我再加上一个ToastUtils,代码如下: package com.llw.goodmusic.utils; import android.content.Context; import android.widget.Toast; public class ToastUtils { /** 长消息 @param context 上下文参数 @param llw 内容 */ public static void longToast(Context context, CharSequence llw) { Toast.makeText(context.getApplicationContext(), llw, Toast.LENGTH_LONG).show(); } /** 短消息 @param context 上下文参数 @param llw 内容 */ public static void shortToast(Context context, CharSequence llw) { Toast.makeText(context.getApplicationContext(), llw, Toast.LENGTH_SHORT).show(); } } 既然是歌曲信息肯定是需要一个实体bean的。在com.llw.goodmusic下新建一个bean包。在包下新建一个Song类,代码如下: package com.llw.goodmusic.bean; /** 歌曲Bean @author llw */ public class Song { /** 歌手*/ public String singer; /** 歌曲名*/ public String song; /** 专辑名*/ public String album; /** 专辑图片*/ public String album_art; /** 歌曲的地址*/ public String path; /** 歌曲长度*/ public int duration; /** 歌曲的大小*/ public long size; /** 当前歌曲选中*/ public boolean isCheck; public String getSinger() { return singer; } public void setSinger(String singer) { this.singer = singer; } public String getSong() { return song; } public void setSong(String song) { this.song = song; } public String getPath() { return path; } public void setPath(String path) { this.path = path; } public int getDuration() { return duration; } public void setDuration(int duration) { this.duration = duration; } public long getSize() { return size; } public void setSize(long size) { this.size = size; } public String getAlbum() { return album; } public void setAlbum(String album) { this.album = album; } public String getAlbum_art() { return album_art; } public void setAlbum_art(String album_art) { this.album_art = album_art; } public boolean isCheck() { return isCheck; } public void setCheck(boolean check) { isCheck = check; } } 然后还有一个最主要的工具类MusicUtils,代码如下: package com.llw.goodmusic.utils; import android.content.Context; import android.database.Cursor; import android.provider.MediaStore; import com.llw.goodmusic.bean.Song; import java.util.ArrayList; import java.util.List; /** 音乐扫描工具 @author llw */ public class MusicUtils { /** 扫描系统里面的音频文件,返回一个list集合*/ public static List getMusicData(Context context) { List list = new ArrayList(); // 媒体库查询语句(写一个工具类MusicUtils) Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.IS_MUSIC); if (cursor != null) { while (cursor.moveToNext()) { Song song = new Song(); //歌曲名称 song.song = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)); //歌手 song.singer = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)); //专辑名 song.album = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)); //歌曲路径 song.path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)); //歌曲时长 song.duration = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)); //歌曲大小 song.size = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)); if (song.size > 1000 * 800) { // 注释部分是切割标题,分离出歌曲名和歌手 (本地媒体库读取的歌曲信息不规范) if (song.song.contains(“-”)) { String[] str = song.song.split(“-”); song.singer = str[0]; song.song = str[1]; } list.add(song); } } // 释放资源 cursor.close(); } return list; } } 这个扫描请求的工具类是无法扫描到加密的音乐文件的,能扫描到mp3、flac格式的音乐文件,其他的格式我没有试过,因为现在网易云和QQ音乐下载本地歌曲有很多是需要VIP才能下载的,这种音乐下载之后是加密的音乐文件,QQ音乐的下载的加密文件是 .qmc后缀开头的,网易的我就不知道了,因为我没有开网易云音乐的VIP,不过这些加密文件有一个共同点,不允许其他播放器播放,这个就很恶心了,也就是说哪怕你通过文件夹路径扫描到添加到你自己的音乐播放列表里面之后也播放不了。因为加密规则你不知道,你就不能去解密,解密不了自然播放不了。 最终你的项目目录会如下图所示 如果有出入的话可以照这个来改一下,或者可以自己分包也可以。 ⑦ 数据显示做一个列表来显示本地的歌曲列表,列表由item决定,item需要新建一个xml文件,如下图这种。 在layout下面新建一个item_music_rv_list.xml,布局如下: 里面的尺寸都是放在dimen.xml文件里面的,放在values.xml下,和colors.xml同级,这个我也贴一下代码 0dp 0.1dp 0.5dp 1dp 1.5dp 2dp 2.5dp 3dp 3.5dp 4dp 4.5dp 5dp 6dp 7dp 8dp 9dp 10dp 11dp 12dp 13dp 14dp 15dp 16dp 17dp 18dp 19dp 20dp 21dp 22dp 23dp 24dp 25dp 26dp 27dp 28dp 29dp 30dp 31dp 32dp 33dp 34dp 35dp 36dp 37dp 38dp 39dp 40dp 41dp 42dp 43dp 44dp 45dp 46dp 47dp 48dp 49dp 50dp 51dp 52dp 53dp 54dp 55dp 56dp 57dp 58dp 59dp 60dp 61dp 62dp 63dp 64dp 65dp 66dp 67dp 68dp 69dp 70dp 71dp 72dp 73dp 74dp 75dp 76dp 77dp 78dp 79dp 80dp 81dp 82dp 83dp 84dp 85dp 86dp 87dp 88dp 89dp 90dp 91dp 92dp 93dp 94dp 95dp 96dp 97dp 98dp 99dp 100dp 101dp 102dp 103dp 104dp 105dp 106dp 107dp 108dp 109dp 110dp 111dp 112dp 113dp 114dp 115dp 116dp 117dp 118dp 119dp 120dp 121dp 122dp 123dp 124dp 125dp 126dp 127dp 128dp 129dp 130dp 131dp 132dp 133dp 134dp 135dp 136dp 137dp 138dp 139dp 140dp 141dp 142dp 143dp 144dp 145dp 146dp 147dp 148dp 149dp 150dp 151dp 152dp 153dp 154dp 155dp 156dp 157dp 158dp 159dp 160dp 161dp 162dp 163dp 164dp 165dp 166dp 167dp 168dp 169dp 170dp 171dp 172dp 173dp 174dp 175dp 176dp 177dp 178dp 179dp 180dp 181dp 182dp 183dp 184dp 185dp 186dp 187dp 188dp 189dp 190dp 191dp 192dp 193dp 194dp 195dp 196dp 197dp 198dp 199dp 200dp 201dp 202dp 203dp 204dp 205dp 206dp 207dp 208dp 209dp 210dp 211dp 212dp 213dp 214dp 215dp 216dp 217dp 218dp 219dp 220dp 221dp 222dp 223dp 224dp 225dp 226dp 227dp 228dp 229dp 230dp 231dp 232dp 233dp 234dp 235dp 236dp 237dp 238dp 239dp 240dp 241dp 242dp 243dp 244dp 245dp 246dp 247dp 248dp 249dp 250dp 251dp 252dp 253dp 254dp 255dp 256dp 257dp 258dp 259dp 260dp 261dp 262dp 263dp 264dp 265dp 266dp 267dp 268dp 269dp 270dp 271dp 272dp 273dp 274dp 275dp 276dp 277dp 278dp 279dp 280dp 281dp 282dp 283dp 284dp 285dp 286dp 287dp 288dp 289dp 290dp 291dp 292dp 293dp 294dp 295dp 296dp 297dp 298dp 299dp 300dp 301dp 302dp 303dp 304dp 305dp 306dp 307dp 308dp 309dp 310dp 311dp 312dp 313dp 314dp 315dp 316dp 317dp 318dp 319dp 320dp 321dp 322dp 323dp 324dp 325dp 326dp 327dp 328dp 329dp 330dp 331dp 332dp 333dp 334dp 335dp 336dp 337dp 338dp 339dp 340dp 341dp 342dp 343dp 344dp 345dp 346dp 347dp 348dp 349dp 350dp 351dp 352dp 353dp 354dp 355dp 356dp 357dp 358dp 359dp 360dp 365dp 370dp 400dp 410dp 422dp 472dp 500dp 600dp 640dp 720dp 6sp 7sp 8sp 9sp 10sp 11sp 12sp 13sp 14sp 15sp 16sp 17sp 18sp 19sp 20sp 21sp 22sp 23sp 24sp 25sp 26sp 27sp 28sp 29sp 30sp 31sp 32sp 33sp 34sp 35sp 36sp 37sp 38sp 40sp 42sp 48sp item布局有了,还要一个适配器,在com.llw.goodmusic包下新建一个adapter包,然后新建一个MusicListAdapter类。里面的代码如下: package com.llw.goodmusic.adapter; import androidx.annotation.Nullable; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import com.llw.goodmusic.R; import com.llw.goodmusic.bean.Song; import com.llw.goodmusic.utils.DateTimeUtils; import java.util.List; /** 音乐列表适配器 @author llw */ public class MusicListAdapter extends BaseQuickAdapter { public MusicListAdapter(int layoutResId, @Nullable List data) { super(layoutResId, data); } @Override protected void convert(BaseViewHolder helper, Song item) { //给控件赋值 int duration = item.duration; String time = DateTimeUtils.formatTime(duration); //歌曲名称 helper.setText(R.id.tv_song_name, item.getSong().trim()) //歌手 - 专辑 .setText(R.id.tv_singer, item.getSinger() + " - " + item.getAlbum()) //歌曲时间 .setText(R.id.tv_duration_time, time) //歌曲序号,因为getAdapterPosition得到的位置是从0开始,故而加1, //是因为位置和1都是整数类型,直接赋值给TextView会报错,故而拼接了"" .setText(R.id.tv_position, helper.getAdapterPosition() + 1 + “”); //给item添加点击事件,点击之后传递数据到播放页面或者在本页面进行音乐播放 helper.addOnClickListener(R.id.item_music); //点击后改变文字颜色 if (item.isCheck()) { helper.setTextColor(R.id.tv_position, mContext.getColor(R.color.gold_color)) .setTextColor(R.id.tv_song_name, mContext.getColor(R.color.gold_color)) .setTextColor(R.id.tv_singer, mContext.getColor(R.color.gold_color)) .setTextColor(R.id.tv_duration_time, mContext.getColor(R.color.gold_color)); } else { helper.setTextColor(R.id.tv_position, mContext.getColor(R.color.white)) .setTextColor(R.id.tv_song_name, mContext.getColor(R.color.white)) .setTextColor(R.id.tv_singer, mContext.getColor(R.color.white)) .setTextColor(R.id.tv_duration_time, mContext.getColor(R.color.white)); } } /** 刷新数据*/ public void changeState() { notifyDataSetChanged(); } } 上面的代码,除了基本的数据填充之外也没有什么好说,只有一个点击歌曲时更改文字颜色,类似与一般的音乐APP歌曲的效果。当在Activity点击item时,调用changeState方法刷新数据。 好了,一切的准备工作都做完了。看起来这个功能好像没啥东西,但是要想的细节是很多的。下面回到LocalMusicActivity。 定义一些需要的变量 private Toolbar toolbar; /** 歌曲列表*/ private RecyclerView rvMusic; /** 扫描歌曲布局*/ private LinearLayout layScanMusic; /** 歌曲适配器*/ private MusicListAdapter mAdapter; /** 歌曲列表*/ private List mList = new ArrayList(); /** 上一次点击的位置*/ private int oldPosition = -1; /** 初始化控件级页面的业务逻辑*/ private void initView() { ActivityLocalMusicBinding binding = DataBindingUtil.setContentView(context, R.layout.activity_local_music); toolbar = binding.toolbar; rvMusic = binding.rvMusic; layScanMusic = binding.layScanMusic; Back(toolbar); //当进入页面时发现有缓存数据时,则隐藏扫描布局,直接获取本地数据。 if (SPUtils.getBoolean(Constant.LOCAL_MUSIC_DATA, false, context)) { //省去一个点击扫描的步骤 layScanMusic.setVisibility(View.GONE); permissionsRequest(); } } 新建一个方法,在权限通过时调用 方法如下: /** 获取音乐列表*/ private void getMusicList() { //清除列表数据 mList.clear(); //将扫描到的音乐赋值给音乐列表 mList = MusicUtils.getMusicData(this); if (mList != null && mList.size() > 0) { //是否有缓存歌曲 SPUtils.putBoolean(Constant.LOCAL_MUSIC_DATA, true, context); layScanMusic.setVisibility(View.GONE); //显示本地音乐 showLocalMusicData(); } else { show(“兄嘚,你是一无所有啊~”); } 最后愿你有一天,真爱自己,善待自己。 《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取! |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |