效果图

音乐APP 扫描本地音乐

  • 前言
  • 正文
    • ① 新建项目
    • ② 第三方依赖
    • ③ 权限和基础配置
    • ④ 页面设计
    • ⑤ 权限请求
    • ⑥ 获取音乐数据
    • ⑦ 数据显示
  • 结语

前言

  这个项目纯粹的就是心血来潮,打算写一个,写作的方式和天气APP类似,把博客当成开发笔记吧,感兴趣可以跟着看,OK,新建一个项目。

正文

① 新建项目

写之前我就想好名字了,就叫GoodMusic,如下图创建项目

最低版本从6.0开始,然后点击Finish完成创建。

② 第三方依赖

打开项目的build.gradle,新增依赖库

     //新增maven { url "https://jitpack.io" }//新增mavenCentral()

如下图

然后再打开app下面的build.gradle,现在android闭包中指定使用的JDK版本,和开启DataBinding的使用。

然后再dependencies闭包中,增加如下依赖:

 //Google Material控件,以及迁移到AndroidX下一些控件的依赖implementation 'com.google.android.material:material:1.2.0'implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'implementation 'androidx.annotation:annotation:1.1.0'implementation 'androidx.legacy:legacy-support-v4:1.0.0'//RecyclerView最好的适配器,让你的适配器一目了然,告别代码冗余implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'//权限请求框架implementation 'com.permissionx.guolindev:permissionx:1.4.0'

然后Sync同步一下。

③ 权限和基础配置

从文章的标题可以得知,扫描本地音乐就是要打开手机的文件夹,是需要权限的,不光要在AndroidManifest.xml中注册,也要在打开的时候动态申请才行,因为这个是危险权限。
打开AndroidManifest.xml,增加如下代码:

 <!-- 文件读写权限  Android6.0 以后需要动态获取  10.0之后对文件的处理更复杂了 --><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />

当然这还没有玩,因为我想过,我的这个APP不可能只有一个Activity,会有多个,所以就需要写一个管理Activity的类。
下面在com.llw.goodmusic包下新建一个basic包,在该包下新建一个ActivityManager类,类的代码如下所示:

package com.llw.goodmusic.basic;import android.app.Activity;import java.util.ArrayList;
import java.util.List;/*** 管理所有的Activity** @author llw*/
public class ActivityManager {/*** 保存所有创建的Activity*/private List<Activity> allActivities = new ArrayList<>();/*** 添加Activity到管理器** @param activity activity*/public void addActivity(Activity activity) {if (activity != null) {allActivities.add(activity);}}/*** 从管理器移除Activity** @param activity activity*/public void removeActivity(Activity activity) {if (activity != null) {allActivities.remove(activity);}}/*** 关闭所有Activity*/public void finishAll() {for (Activity activity : allActivities) {activity.finish();}}/*** 获取栈顶的Activity** @return*/public Activity getTaskTop() {return allActivities.get(allActivities.size() - 1);}
}

从方法的注释你就明白这个类是干嘛的了。当然光是有一个管理的还不够,还需要一个对工程进行控制的类。在basic包下在建一个BasicApplication,代码如下:

package com.llw.goodmusic.basic;import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;/*** 工程管理** @author llw*/
public class BasicApplication extends Application {private static ActivityManager activityManager;private static BasicApplication application;private static Context context;@Overridepublic void onCreate() {super.onCreate();//声明Activity管理activityManager = new ActivityManager();context = getApplicationContext();application = this;}@Overridepublic void onConfigurationChanged(Configuration newConfig) {super.onConfigurationChanged(newConfig);}public static ActivityManager getActivityManager() {return activityManager;}/*** 内容提供器* @return*/public static Context getContext() {return context;}public static BasicApplication getApplication() {return application;}
}

还差最后一个就是Activity的简单封装,写一个基础的Activity,然后让所有的Activity继承这个,同时实现里面的抽象方法。
这里需要先定义一个接口,由这个基础Activity来实现,在basic下面新建一个UiCallBack接口。里面的代码如下:

package com.llw.goodmusic.basic;import android.os.Bundle;/*** UI回调接口** @author llw*/
public interface UiCallBack {/*** 初始化savedInstanceState** @param savedInstanceState*/void initBeforeView(Bundle savedInstanceState);/*** 初始化数据 相当于onCreate** @param savedInstanceState*/void initData(Bundle savedInstanceState);/*** 绑定布局** @return*/int getLayoutId();
}

里面的方法比较简单就是一个Activity所需要的基本的东西,初始化参数和布局。然后在basic下面新建一个抽象的BasicActivity。里面的代码如下:

package com.llw.goodmusic.basic;import android.app.Activity;
import android.os.Bundle;
import android.view.View;import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;/*** 基础Activity** @author llw*/
public abstract class BasicActivity extends AppCompatActivity implements UiCallBack {/*** 快速点击的时间间隔*/private static final int FAST_CLICK_DELAY_TIME = 500;/*** 最后点击的时间*/private static long lastClickTime;/*** 上下文参数*/protected Activity context;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);initBeforeView(savedInstanceState);this.context = this;//添加继承这个BaseActivity的ActivityBasicApplication.getActivityManager().addActivity(this);//绑定布局idif (getLayoutId() > 0) {setContentView(getLayoutId());}//初始化数据initData(savedInstanceState);}@Overridepublic void initBeforeView(Bundle savedInstanceState) {}/*** 返回** @param toolbar*/protected void Back(Toolbar toolbar) {toolbar.setNavigationOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {context.finish();if (!isFastClick()) {context.finish();}}});}/*** 两次点击间隔不能少于500ms  防止多次点击** @return flag*/protected static boolean isFastClick() {boolean flag = true;long currentClickTime = System.currentTimeMillis();if ((currentClickTime - lastClickTime) >= FAST_CLICK_DELAY_TIME) {flag = false;}lastClickTime = currentClickTime;return flag;}/*** 消息提示** @param llw*/protected void show(CharSequence llw) {Toast.makeText(context, llw, Toast.LENGTH_SHORT).show();}
}

里面除了提供上下文参数以外,还有绑定布局的id,初始化参数等方法,还有一些需要在后面再增加。
现在都搞定了然后在com.llw.goodmusic下创建一个MusicApplication然后继承BasicApplication。代码如下:

package com.llw.goodmusic;import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;import com.llw.goodmusic.basic.ActivityManager;
import com.llw.goodmusic.basic.BasicApplication;/*** 项目管理** @author llw*/
public class MusicApplication extends BasicApplication {/*** 应用实例*/public static MusicApplication musicApplication;private static Context context;private static ActivityManager activityManager;public static Context getMyContext() {return musicApplication == null ? null : musicApplication.getApplicationContext();}private Handler myHandler;public Handler getMyHandler() {return myHandler;}public void setMyHandler(Handler handler) {myHandler = handler;}@Overridepublic void onCreate() {super.onCreate();activityManager = new ActivityManager();context = getApplicationContext();musicApplication = this;}public static ActivityManager getActivityManager() {return activityManager;}@Overridepublic void onConfigurationChanged(Configuration newConfig) {super.onConfigurationChanged(newConfig);}}

还差最后一步配置,那就是在AndroidManifest.xml设置MusicApplication


然后再改一下styles.xml中的样式

里面colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><color name="colorPrimary">#26252B</color><color name="colorPrimaryDark">#26252B</color><color name="colorAccent">#D81B60</color><!--App主要颜色--><color name="app_color">#26252B</color><!--App背景颜色--><color name="app_bg">#333439</color><color name="white">#FFFFFF</color><color name="white_2">#22FFFFFF</color><!--白色透明度22%--><color name="white_4">#44FFFFFF</color><!--白色透明度44%--><color name="white_6">#66FFFFFF</color><!--白色透明度66%--><color name="white_8">#88FFFFFF</color><!--白色透明度88%--><color name="transparent">#00000000</color><!--透明--><color name="gold_color">#FF9D00</color></resources>

④ 页面设计


这个图就是APP的主页面了,深色为主,下面来看这个怎么来写。图标可以去自己下载,也可以在源码中去拿,都行。下面看看activity_main.xml的布局内容

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/app_bg"android:orientation="vertical"tools:context=".ui.MainActivity"><!--标题--><TextViewandroid:id="@+id/tv_title"android:gravity="center"android:layout_width="match_parent"android:layout_gravity="center"android:text="Good Music"android:layout_height="?attr/actionBarSize"android:background="@color/app_color"android:textColor="@color/white"android:textSize="@dimen/sp_18" /><!--主要操作区域--><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="@dimen/dp_12"><!--本地音乐--><LinearLayoutandroid:onClick="onClick"android:id="@+id/lay_local_music"android:layout_width="@dimen/dp_120"android:layout_height="@dimen/dp_120"android:background="@drawable/shape_app_color_radius_5"android:foreground="?android:attr/selectableItemBackground"android:gravity="center"android:orientation="vertical"><ImageViewandroid:layout_width="@dimen/dp_48"android:layout_height="@dimen/dp_48"android:src="@mipmap/icon_local" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="@dimen/dp_8"android:text="本地音乐"android:textColor="@color/white"android:textSize="@dimen/sp_16" /></LinearLayout></LinearLayout></LinearLayout>
</layout>

然后当然是要在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 {@Overridepublic void initData(Bundle savedInstanceState) {}@Overridepublic 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代码如下:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/app_bg"android:orientation="vertical"tools:context=".ui.MainActivity"><!--Toolbar--><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/app_color"app:navigationIcon="@mipmap/icon_return_white"><TextViewandroid:id="@+id/tv_title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="本地音乐"android:textColor="@color/white"android:textSize="@dimen/sp_18" /></androidx.appcompat.widget.Toolbar><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><!--扫描音乐布局--><LinearLayoutandroid:id="@+id/lay_scan_music"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"><ImageViewandroid:layout_width="@dimen/dp_140"android:layout_height="@dimen/dp_140"android:src="@mipmap/icon_empty" /><!--扫描本地音乐--><com.google.android.material.button.MaterialButtonstyle="@style/Widget.MaterialComponents.Button.UnelevatedButton"android:layout_width="@dimen/dp_140"android:layout_height="@dimen/dp_40"android:layout_marginTop="@dimen/dp_16"android:insetTop="@dimen/dp_0"android:insetBottom="@dimen/dp_0"android:onClick="scanLocalMusic"android:text="扫描本地音乐"android:textSize="@dimen/sp_14"android:theme="@style/Theme.MaterialComponents.Light.NoActionBar"app:backgroundTint="@color/transparent"app:cornerRadius="@dimen/dp_20"app:strokeColor="@color/white"app:strokeWidth="@dimen/dp_1" /></LinearLayout><!--列表--><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rv_music"android:layout_width="match_parent"android:layout_height="match_parent" /></RelativeLayout></LinearLayout>
</layout>

里面有两个布局,一个是用来扫描本地歌曲的,一个是用来显示歌曲的列表,如果扫描不到就提示一下。现在页面的布局有了,下面就是要来写这个页面的业务逻辑。

⑤ 权限请求

之前在AndroidManifest.xml中注册了静态的文件读写权限,而在Android 6.0之后。危险权限需要动态申请才能够使用。所以我在build.gradle中增加了一个权限请求框架,现在就来使用吧。

 /*** 动态权限请求*/private void permissionsRequest() {PermissionX.init(this).permissions(//写入文件Manifest.permission.WRITE_EXTERNAL_STORAGE).onExplainRequestReason(new ExplainReasonCallbackWithBeforeParam() {@Overridepublic void onExplainReason(ExplainScope scope, List<String> deniedList, boolean beforeRequest) {scope.showRequestReasonDialog(deniedList, "即将申请的权限是程序必须依赖的权限", "我已明白");}}).onForwardToSettings(new ForwardToSettingsCallback() {@Overridepublic void onForwardToSettings(ForwardScope scope, List<String> deniedList) {scope.showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白");}}).setDialogTintColor(R.color.white, R.color.app_color).request(new RequestCallback() {@Overridepublic void onResult(boolean allGranted, List<String> grantedList, List<String> 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<Song> getMusicData(Context context) {List<Song> list = new ArrayList<Song>();// 媒体库查询语句(写一个工具类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,布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/item_music"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginBottom="@dimen/dp_1"android:background="@color/app_color"android:foreground="?android:attr/selectableItemBackground"android:gravity="center_vertical"android:orientation="horizontal"android:padding="@dimen/dp_10"><TextViewandroid:id="@+id/tv_position"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_margin="2dp"android:text="1"android:textColor="@color/white"android:textSize="@dimen/sp_16" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="@dimen/dp_10"android:orientation="vertical"><TextViewandroid:id="@+id/tv_song_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:maxLines="1"android:text="歌曲名"android:textColor="@color/white"android:textSize="@dimen/sp_18" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="@dimen/dp_4"><TextViewandroid:id="@+id/tv_singer"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:ellipsize="end"android:maxLines="1"android:text="歌手"android:textColor="@color/white"android:textSize="@dimen/sp_14" /><TextViewandroid:id="@+id/tv_duration_time"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="12dp"android:text="时间"android:textColor="@color/white"android:textSize="@dimen/sp_14" /></LinearLayout></LinearLayout>
</LinearLayout>

里面的尺寸都是放在dimen.xml文件里面的,放在values.xml下,和colors.xml同级,这个我也贴一下代码

<?xml version="1.0" encoding="utf-8"?>
<resources><!--尺寸--><dimen name="dp_0">0dp</dimen><dimen name="dp_0_1">0.1dp</dimen><dimen name="dp_0_5">0.5dp</dimen><dimen name="dp_1">1dp</dimen><dimen name="dp_1_5">1.5dp</dimen><dimen name="dp_2">2dp</dimen><dimen name="dp_2_5">2.5dp</dimen><dimen name="dp_3">3dp</dimen><dimen name="dp_3_5">3.5dp</dimen><dimen name="dp_4">4dp</dimen><dimen name="dp_4_5">4.5dp</dimen><dimen name="dp_5">5dp</dimen><dimen name="dp_6">6dp</dimen><dimen name="dp_7">7dp</dimen><dimen name="dp_8">8dp</dimen><dimen name="dp_9">9dp</dimen><dimen name="dp_10">10dp</dimen><dimen name="dp_11">11dp</dimen><dimen name="dp_12">12dp</dimen><dimen name="dp_13">13dp</dimen><dimen name="dp_14">14dp</dimen><dimen name="dp_15">15dp</dimen><dimen name="dp_16">16dp</dimen><dimen name="dp_17">17dp</dimen><dimen name="dp_18">18dp</dimen><dimen name="dp_19">19dp</dimen><dimen name="dp_20">20dp</dimen><dimen name="dp_21">21dp</dimen><dimen name="dp_22">22dp</dimen><dimen name="dp_23">23dp</dimen><dimen name="dp_24">24dp</dimen><dimen name="dp_25">25dp</dimen><dimen name="dp_26">26dp</dimen><dimen name="dp_27">27dp</dimen><dimen name="dp_28">28dp</dimen><dimen name="dp_29">29dp</dimen><dimen name="dp_30">30dp</dimen><dimen name="dp_31">31dp</dimen><dimen name="dp_32">32dp</dimen><dimen name="dp_33">33dp</dimen><dimen name="dp_34">34dp</dimen><dimen name="dp_35">35dp</dimen><dimen name="dp_36">36dp</dimen><dimen name="dp_37">37dp</dimen><dimen name="dp_38">38dp</dimen><dimen name="dp_39">39dp</dimen><dimen name="dp_40">40dp</dimen><dimen name="dp_41">41dp</dimen><dimen name="dp_42">42dp</dimen><dimen name="dp_43">43dp</dimen><dimen name="dp_44">44dp</dimen><dimen name="dp_45">45dp</dimen><dimen name="dp_46">46dp</dimen><dimen name="dp_47">47dp</dimen><dimen name="dp_48">48dp</dimen><dimen name="dp_49">49dp</dimen><dimen name="dp_50">50dp</dimen><dimen name="dp_51">51dp</dimen><dimen name="dp_52">52dp</dimen><dimen name="dp_53">53dp</dimen><dimen name="dp_54">54dp</dimen><dimen name="dp_55">55dp</dimen><dimen name="dp_56">56dp</dimen><dimen name="dp_57">57dp</dimen><dimen name="dp_58">58dp</dimen><dimen name="dp_59">59dp</dimen><dimen name="dp_60">60dp</dimen><dimen name="dp_61">61dp</dimen><dimen name="dp_62">62dp</dimen><dimen name="dp_63">63dp</dimen><dimen name="dp_64">64dp</dimen><dimen name="dp_65">65dp</dimen><dimen name="dp_66">66dp</dimen><dimen name="dp_67">67dp</dimen><dimen name="dp_68">68dp</dimen><dimen name="dp_69">69dp</dimen><dimen name="dp_70">70dp</dimen><dimen name="dp_71">71dp</dimen><dimen name="dp_72">72dp</dimen><dimen name="dp_73">73dp</dimen><dimen name="dp_74">74dp</dimen><dimen name="dp_75">75dp</dimen><dimen name="dp_76">76dp</dimen><dimen name="dp_77">77dp</dimen><dimen name="dp_78">78dp</dimen><dimen name="dp_79">79dp</dimen><dimen name="dp_80">80dp</dimen><dimen name="dp_81">81dp</dimen><dimen name="dp_82">82dp</dimen><dimen name="dp_83">83dp</dimen><dimen name="dp_84">84dp</dimen><dimen name="dp_85">85dp</dimen><dimen name="dp_86">86dp</dimen><dimen name="dp_87">87dp</dimen><dimen name="dp_88">88dp</dimen><dimen name="dp_89">89dp</dimen><dimen name="dp_90">90dp</dimen><dimen name="dp_91">91dp</dimen><dimen name="dp_92">92dp</dimen><dimen name="dp_93">93dp</dimen><dimen name="dp_94">94dp</dimen><dimen name="dp_95">95dp</dimen><dimen name="dp_96">96dp</dimen><dimen name="dp_97">97dp</dimen><dimen name="dp_98">98dp</dimen><dimen name="dp_99">99dp</dimen><dimen name="dp_100">100dp</dimen><dimen name="dp_101">101dp</dimen><dimen name="dp_102">102dp</dimen><dimen name="dp_103">103dp</dimen><dimen name="dp_104">104dp</dimen><dimen name="dp_105">105dp</dimen><dimen name="dp_106">106dp</dimen><dimen name="dp_107">107dp</dimen><dimen name="dp_108">108dp</dimen><dimen name="dp_109">109dp</dimen><dimen name="dp_110">110dp</dimen><dimen name="dp_111">111dp</dimen><dimen name="dp_112">112dp</dimen><dimen name="dp_113">113dp</dimen><dimen name="dp_114">114dp</dimen><dimen name="dp_115">115dp</dimen><dimen name="dp_116">116dp</dimen><dimen name="dp_117">117dp</dimen><dimen name="dp_118">118dp</dimen><dimen name="dp_119">119dp</dimen><dimen name="dp_120">120dp</dimen><dimen name="dp_121">121dp</dimen><dimen name="dp_122">122dp</dimen><dimen name="dp_123">123dp</dimen><dimen name="dp_124">124dp</dimen><dimen name="dp_125">125dp</dimen><dimen name="dp_126">126dp</dimen><dimen name="dp_127">127dp</dimen><dimen name="dp_128">128dp</dimen><dimen name="dp_129">129dp</dimen><dimen name="dp_130">130dp</dimen><dimen name="dp_131">131dp</dimen><dimen name="dp_132">132dp</dimen><dimen name="dp_133">133dp</dimen><dimen name="dp_134">134dp</dimen><dimen name="dp_135">135dp</dimen><dimen name="dp_136">136dp</dimen><dimen name="dp_137">137dp</dimen><dimen name="dp_138">138dp</dimen><dimen name="dp_139">139dp</dimen><dimen name="dp_140">140dp</dimen><dimen name="dp_141">141dp</dimen><dimen name="dp_142">142dp</dimen><dimen name="dp_143">143dp</dimen><dimen name="dp_144">144dp</dimen><dimen name="dp_145">145dp</dimen><dimen name="dp_146">146dp</dimen><dimen name="dp_147">147dp</dimen><dimen name="dp_148">148dp</dimen><dimen name="dp_149">149dp</dimen><dimen name="dp_150">150dp</dimen><dimen name="dp_151">151dp</dimen><dimen name="dp_152">152dp</dimen><dimen name="dp_153">153dp</dimen><dimen name="dp_154">154dp</dimen><dimen name="dp_155">155dp</dimen><dimen name="dp_156">156dp</dimen><dimen name="dp_157">157dp</dimen><dimen name="dp_158">158dp</dimen><dimen name="dp_159">159dp</dimen><dimen name="dp_160">160dp</dimen><dimen name="dp_161">161dp</dimen><dimen name="dp_162">162dp</dimen><dimen name="dp_163">163dp</dimen><dimen name="dp_164">164dp</dimen><dimen name="dp_165">165dp</dimen><dimen name="dp_166">166dp</dimen><dimen name="dp_167">167dp</dimen><dimen name="dp_168">168dp</dimen><dimen name="dp_169">169dp</dimen><dimen name="dp_170">170dp</dimen><dimen name="dp_171">171dp</dimen><dimen name="dp_172">172dp</dimen><dimen name="dp_173">173dp</dimen><dimen name="dp_174">174dp</dimen><dimen name="dp_175">175dp</dimen><dimen name="dp_176">176dp</dimen><dimen name="dp_177">177dp</dimen><dimen name="dp_178">178dp</dimen><dimen name="dp_179">179dp</dimen><dimen name="dp_180">180dp</dimen><dimen name="dp_181">181dp</dimen><dimen name="dp_182">182dp</dimen><dimen name="dp_183">183dp</dimen><dimen name="dp_184">184dp</dimen><dimen name="dp_185">185dp</dimen><dimen name="dp_186">186dp</dimen><dimen name="dp_187">187dp</dimen><dimen name="dp_188">188dp</dimen><dimen name="dp_189">189dp</dimen><dimen name="dp_190">190dp</dimen><dimen name="dp_191">191dp</dimen><dimen name="dp_192">192dp</dimen><dimen name="dp_193">193dp</dimen><dimen name="dp_194">194dp</dimen><dimen name="dp_195">195dp</dimen><dimen name="dp_196">196dp</dimen><dimen name="dp_197">197dp</dimen><dimen name="dp_198">198dp</dimen><dimen name="dp_199">199dp</dimen><dimen name="dp_200">200dp</dimen><dimen name="dp_201">201dp</dimen><dimen name="dp_202">202dp</dimen><dimen name="dp_203">203dp</dimen><dimen name="dp_204">204dp</dimen><dimen name="dp_205">205dp</dimen><dimen name="dp_206">206dp</dimen><dimen name="dp_207">207dp</dimen><dimen name="dp_208">208dp</dimen><dimen name="dp_209">209dp</dimen><dimen name="dp_210">210dp</dimen><dimen name="dp_211">211dp</dimen><dimen name="dp_212">212dp</dimen><dimen name="dp_213">213dp</dimen><dimen name="dp_214">214dp</dimen><dimen name="dp_215">215dp</dimen><dimen name="dp_216">216dp</dimen><dimen name="dp_217">217dp</dimen><dimen name="dp_218">218dp</dimen><dimen name="dp_219">219dp</dimen><dimen name="dp_220">220dp</dimen><dimen name="dp_221">221dp</dimen><dimen name="dp_222">222dp</dimen><dimen name="dp_223">223dp</dimen><dimen name="dp_224">224dp</dimen><dimen name="dp_225">225dp</dimen><dimen name="dp_226">226dp</dimen><dimen name="dp_227">227dp</dimen><dimen name="dp_228">228dp</dimen><dimen name="dp_229">229dp</dimen><dimen name="dp_230">230dp</dimen><dimen name="dp_231">231dp</dimen><dimen name="dp_232">232dp</dimen><dimen name="dp_233">233dp</dimen><dimen name="dp_234">234dp</dimen><dimen name="dp_235">235dp</dimen><dimen name="dp_236">236dp</dimen><dimen name="dp_237">237dp</dimen><dimen name="dp_238">238dp</dimen><dimen name="dp_239">239dp</dimen><dimen name="dp_240">240dp</dimen><dimen name="dp_241">241dp</dimen><dimen name="dp_242">242dp</dimen><dimen name="dp_243">243dp</dimen><dimen name="dp_244">244dp</dimen><dimen name="dp_245">245dp</dimen><dimen name="dp_246">246dp</dimen><dimen name="dp_247">247dp</dimen><dimen name="dp_248">248dp</dimen><dimen name="dp_249">249dp</dimen><dimen name="dp_250">250dp</dimen><dimen name="dp_251">251dp</dimen><dimen name="dp_252">252dp</dimen><dimen name="dp_253">253dp</dimen><dimen name="dp_254">254dp</dimen><dimen name="dp_255">255dp</dimen><dimen name="dp_256">256dp</dimen><dimen name="dp_257">257dp</dimen><dimen name="dp_258">258dp</dimen><dimen name="dp_259">259dp</dimen><dimen name="dp_260">260dp</dimen><dimen name="dp_261">261dp</dimen><dimen name="dp_262">262dp</dimen><dimen name="dp_263">263dp</dimen><dimen name="dp_264">264dp</dimen><dimen name="dp_265">265dp</dimen><dimen name="dp_266">266dp</dimen><dimen name="dp_267">267dp</dimen><dimen name="dp_268">268dp</dimen><dimen name="dp_269">269dp</dimen><dimen name="dp_270">270dp</dimen><dimen name="dp_271">271dp</dimen><dimen name="dp_272">272dp</dimen><dimen name="dp_273">273dp</dimen><dimen name="dp_274">274dp</dimen><dimen name="dp_275">275dp</dimen><dimen name="dp_276">276dp</dimen><dimen name="dp_277">277dp</dimen><dimen name="dp_278">278dp</dimen><dimen name="dp_279">279dp</dimen><dimen name="dp_280">280dp</dimen><dimen name="dp_281">281dp</dimen><dimen name="dp_282">282dp</dimen><dimen name="dp_283">283dp</dimen><dimen name="dp_284">284dp</dimen><dimen name="dp_285">285dp</dimen><dimen name="dp_286">286dp</dimen><dimen name="dp_287">287dp</dimen><dimen name="dp_288">288dp</dimen><dimen name="dp_289">289dp</dimen><dimen name="dp_290">290dp</dimen><dimen name="dp_291">291dp</dimen><dimen name="dp_292">292dp</dimen><dimen name="dp_293">293dp</dimen><dimen name="dp_294">294dp</dimen><dimen name="dp_295">295dp</dimen><dimen name="dp_296">296dp</dimen><dimen name="dp_297">297dp</dimen><dimen name="dp_298">298dp</dimen><dimen name="dp_299">299dp</dimen><dimen name="dp_300">300dp</dimen><dimen name="dp_301">301dp</dimen><dimen name="dp_302">302dp</dimen><dimen name="dp_303">303dp</dimen><dimen name="dp_304">304dp</dimen><dimen name="dp_305">305dp</dimen><dimen name="dp_306">306dp</dimen><dimen name="dp_307">307dp</dimen><dimen name="dp_308">308dp</dimen><dimen name="dp_309">309dp</dimen><dimen name="dp_310">310dp</dimen><dimen name="dp_311">311dp</dimen><dimen name="dp_312">312dp</dimen><dimen name="dp_313">313dp</dimen><dimen name="dp_314">314dp</dimen><dimen name="dp_315">315dp</dimen><dimen name="dp_316">316dp</dimen><dimen name="dp_317">317dp</dimen><dimen name="dp_318">318dp</dimen><dimen name="dp_319">319dp</dimen><dimen name="dp_320">320dp</dimen><dimen name="dp_321">321dp</dimen><dimen name="dp_322">322dp</dimen><dimen name="dp_323">323dp</dimen><dimen name="dp_324">324dp</dimen><dimen name="dp_325">325dp</dimen><dimen name="dp_326">326dp</dimen><dimen name="dp_327">327dp</dimen><dimen name="dp_328">328dp</dimen><dimen name="dp_329">329dp</dimen><dimen name="dp_330">330dp</dimen><dimen name="dp_331">331dp</dimen><dimen name="dp_332">332dp</dimen><dimen name="dp_333">333dp</dimen><dimen name="dp_334">334dp</dimen><dimen name="dp_335">335dp</dimen><dimen name="dp_336">336dp</dimen><dimen name="dp_337">337dp</dimen><dimen name="dp_338">338dp</dimen><dimen name="dp_339">339dp</dimen><dimen name="dp_340">340dp</dimen><dimen name="dp_341">341dp</dimen><dimen name="dp_342">342dp</dimen><dimen name="dp_343">343dp</dimen><dimen name="dp_344">344dp</dimen><dimen name="dp_345">345dp</dimen><dimen name="dp_346">346dp</dimen><dimen name="dp_347">347dp</dimen><dimen name="dp_348">348dp</dimen><dimen name="dp_349">349dp</dimen><dimen name="dp_350">350dp</dimen><dimen name="dp_351">351dp</dimen><dimen name="dp_352">352dp</dimen><dimen name="dp_353">353dp</dimen><dimen name="dp_354">354dp</dimen><dimen name="dp_355">355dp</dimen><dimen name="dp_356">356dp</dimen><dimen name="dp_357">357dp</dimen><dimen name="dp_358">358dp</dimen><dimen name="dp_359">359dp</dimen><dimen name="dp_360">360dp</dimen><dimen name="dp_365">365dp</dimen><dimen name="dp_370">370dp</dimen><dimen name="dp_400">400dp</dimen><dimen name="dp_410">410dp</dimen><dimen name="dp_422">422dp</dimen><dimen name="dp_472">472dp</dimen><dimen name="dp_500">500dp</dimen><dimen name="dp_600">600dp</dimen><dimen name="dp_640">640dp</dimen><dimen name="dp_720">720dp</dimen><!--字体--><dimen name="sp_6">6sp</dimen><dimen name="sp_7">7sp</dimen><dimen name="sp_8">8sp</dimen><dimen name="sp_9">9sp</dimen><dimen name="sp_10">10sp</dimen><dimen name="sp_11">11sp</dimen><dimen name="sp_12">12sp</dimen><dimen name="sp_13">13sp</dimen><dimen name="sp_14">14sp</dimen><dimen name="sp_15">15sp</dimen><dimen name="sp_16">16sp</dimen><dimen name="sp_17">17sp</dimen><dimen name="sp_18">18sp</dimen><dimen name="sp_19">19sp</dimen><dimen name="sp_20">20sp</dimen><dimen name="sp_21">21sp</dimen><dimen name="sp_22">22sp</dimen><dimen name="sp_23">23sp</dimen><dimen name="sp_24">24sp</dimen><dimen name="sp_25">25sp</dimen><dimen name="sp_26">26sp</dimen><dimen name="sp_27">27sp</dimen><dimen name="sp_28">28sp</dimen><dimen name="sp_29">29sp</dimen><dimen name="sp_30">30sp</dimen><dimen name="sp_31">31sp</dimen><dimen name="sp_32">32sp</dimen><dimen name="sp_33">33sp</dimen><dimen name="sp_34">34sp</dimen><dimen name="sp_35">35sp</dimen><dimen name="sp_36">36sp</dimen><dimen name="sp_37">37sp</dimen><dimen name="sp_38">38sp</dimen><dimen name="sp_40">40sp</dimen><dimen name="sp_42">42sp</dimen><dimen name="sp_48">48sp</dimen>
</resources>

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<Song, BaseViewHolder> {public MusicListAdapter(int layoutResId, @Nullable List<Song> data) {super(layoutResId, data);}@Overrideprotected 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<Song> 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("兄嘚,你是一无所有啊~");}}

然后再看showLocalMusicData方法:

 /*** 显示本地音乐数据*/private void showLocalMusicData() {//指定适配器的布局和数据源mAdapter = new MusicListAdapter(R.layout.item_music_rv_list, mList);//线性布局管理器,可以设置横向还是纵向,RecyclerView默认是纵向的,所以不用处理,如果不需要设置方向,代码还可以更加的精简如下rvMusic.setLayoutManager(new LinearLayoutManager(this));//设置适配器rvMusic.setAdapter(mAdapter);//item的点击事件mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {@Overridepublic void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {if (view.getId() == R.id.item_music) {if (oldPosition == -1) {//未点击过 第一次点击oldPosition = position;mList.get(position).setCheck(true);} else {//大于 1次if (oldPosition != position) {mList.get(oldPosition).setCheck(false);mList.get(position).setCheck(true);//重新设置位置,当下一次点击时position又会和oldPosition不一样oldPosition = position;}}mAdapter.changeState();}}});}

  这里通过点击的位置进行控制歌曲的状态。OK,代码就写完了。这里我再说一下业务逻辑,当第一次点击扫描按钮时,会请求文件读写取权限,我只放了写入的权限,因为文件操作的权限是在一个权限组里,通过一个就是通过一组,拿到权限之后获取工具类扫描到的歌曲数据,有数据则显示,而对于点击位置的控制,则是通过一个全局变量来操作,oldPosition初始值为-1,当第一次点击时,加入点击了第三项,那么oldPosition就是为2。这个时候将position对应的数据的check设置为true。然后调用mAdapter.changeState();刷新数据。然后可以点击第三项,这个时候oldPosition是不等于position的。

                         mList.get(oldPosition).setCheck(false);mList.get(position).setCheck(true);//重新设置位置,当下一次点击时position又会和oldPosition不一样oldPosition = position;

所以把oldPosition对应数据取消选中,而把position对应数据进行选中,之后把 oldPosition = position;最后退出判断之后又会刷新数据。
下面来运行一下看一下效果。

结语

  目前才刚开始写,这一篇写音乐扫描和获取,饭要一口一口吃,功能要一个一个来写,感兴趣的也可以看一下。有哪里不明白的评论告诉我,我会及时回复,谢谢你的阅读,做一个有内容的作者。
源码地址:Good Music

Android 音乐APP(一)扫描本地音乐相关推荐

  1. Android MediaPlayer 音乐播放器扫描 本地音乐、上一曲、下一曲切歌、播放本地音乐

    Android MediaPlayer 本地音乐播放器 运行截图 项目请在真机(自己的手机)上测试运行,因为我不喜欢用虚拟机. 为了不浪费您的时间,先看一下运行的效果图, 一进去先进行音乐扫描,然后列 ...

  2. Android 自动扫描歌曲,Android扫描本地音乐文件开发案例分享

    一.前言 本来觉得so easy,真是没想到,还搞了老半天,搞的我大汗淋漓,要拍桌子摔键盘了. 本想实现的功能是: 通过网易云音乐/百度音乐/QQ音乐/酷狗音乐中一个API,通过关键词/歌手/歌词来搜 ...

  3. 高仿网易云音乐一(可扫描本地音乐播放)

    最近闲下来做了个仿网易云音乐的音乐播放器 效果图: 项目下载地址 https://github.com/PangHaHa12138/MusicPlayerdemo 闪屏 扫描本地音乐 播放 仿探探 仿 ...

  4. 有没有java自编歌曲,高仿网易云音乐一(可扫描本地音乐播放)

    最近闲下来做了个仿网易云音乐的音乐播放器 效果图: p1.png p2.png p3.png p4.png p5.png p6.png 项目下载地址 闪屏 1.gif 扫描本地音乐 3.gif 播放 ...

  5. Android音乐播放器制作(一)扫描本地音乐显示在手机上

    思路 首先是扫描本地所有的音频文件,然后全部装进集合当中,接下来就是用ListView展示在屏幕上,大概就是这几个步骤了,接下来细讲 创建一个容器 进行过数据解析的朋友都应该知道JavaBean吧,用 ...

  6. Android 音乐APP(五)音乐通知栏、后台播放音乐

    Android 音乐通知栏 前言 正文 ① 通知栏按钮点击监听 ② 通知栏点击监听 ③ 通知栏业务处理 ④ 运行效果图 结语 前言   这篇文章的标题有些言简意赅了,也突出了这篇文章的核心,那就是通知 ...

  7. 我的音乐(Musicoco)- 本地音乐播放器开发总结

    开源一个功能相对齐全的本地音乐播放器 简述 从五月末就开始利用空余时间开发这款 app ,不知不觉三个月过去了. App 名称:我的音乐,我给取了个别名:Musicoco. Android 手机本地音 ...

  8. 扣丁音乐(四)——本地音乐加载

    本文出自:http://blog.csdn.net/dt235201314/article/details/51341078 一丶本地音乐加载相当于就是listVIew应用 扣丁音乐1.0前部分(gi ...

  9. android音乐播放器扫描本地,简单实现Android本地音乐播放器

    搜索热词 音乐播放需要调用service,在此,只是简单梳理播放流程. public class PlayMusicService extends Service { //绑定服务 调用服务的方法. ...

最新文章

  1. android 原始定位,安卓原生定位
  2. java实现计算机图形学中点画线算法
  3. Boost:是否支持sse4.1指令的测试程序
  4. python 内置标准库socketserver模块的思考
  5. Numpy统计计算、数组比较,看这篇就够了
  6. Windows 记事本的 Bug :-)
  7. 「三分钟系列04」3分钟看懂Python黑魔法之__repr__和__str__
  8. 自己定义android 4.0以上的对话框风格
  9. 法兰克焊接机器人编程入门_Fanuc ARC MATE 焊接机器人操作编程.pdf
  10. Next主题 - 图片设置
  11. 延安大学计算机学院评分,延安大学计算机学院.docx
  12. JQuery将用户输入的数字转换为大写
  13. 【Win10 尝试解决】Win10文件资源管理器无限挂起重启
  14. RITnet: Real-time Semantic Segmentation of the Eye for Gaze Tracking
  15. 人工智能实战2019 第五次作业 焦宇恒 16721088
  16. 电脑上的ofd格式文件如何打开呢_OFD文件怎么打开?如何将ofd转成PDF格式?
  17. 【数据库】数据库的投影查询、运算查询、条件查询、统计查询
  18. (已更新)【增强版短视频去水印源码】去水印微信小程序+去水印软件源码
  19. 在服务器上搭建个人网盘
  20. 机器视觉检测设备能否取代质检员工作

热门文章

  1. 百度搜索结果显示“我喜欢”按钮
  2. android html5播放器,用 HTML5 播放器在 iOS 或 Android 等移动设备上播放视频
  3. #第二章 数理文化的起源2.1文化概述
  4. 图形学笔记(四) 数学变换
  5. IllegalStateException: Errors/BindingResult argument declared without preceding model attribute. Che
  6. 创业企业如何定制商业模式:把握不同行业生命周期,9大要素集中进行创新【转】...
  7. xp没有本地计算机策略,WinXP没有网络适配器怎么添加?本地连接消失了如何恢复?...
  8. 时滞微分方程求解之三ddesd--变时滞
  9. java try catch 输入字符串_java – 没有在try / catch中捕获NumberFormatException
  10. ubuntu 进不去界面解决方案