我们对 音乐播放器v1.0 进行改进。
1、把获取数据部分提取出来
增加 Music 信息的数据访问类 MusicDao。我们把 MainActivity 中使用 getData()
放到这里稍加修改即可。
/**
* Music信息的数据访问类
* Data Access Object
*/
public class MusicDao {
public List<Music> getMusicList() {
List<Music> musics = new ArrayList<>();
File musicDir = Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_MUSIC);
Log.d("MUSIC", Environment.getExternalStorageState());
if (musicDir.exists()) {
Log.d("MUSIC", "【信息】文件夹存在");
File[] files = musicDir.listFiles();
if (files != null && files.length > 0) {
Log.d("MUSIC", "【信息】文件夹列表有效");
for (int i = 0; i < files.length; i++) {
if (files[i].isFile()) {
String fileName = files[i].getName();
if (fileName.toLowerCase().endsWith(".mp3")) {
Music music = new Music();
music.setPath(files[i].getAbsolutePath());
music.setTitle(files[i].getName());
musics.add(music);
Log.d("MUSIC", files[i].getAbsolutePath());
} else {
Log.w("MUSIC", "【错误】当前遍历到的不是mp3文件");
}
} else {
Log.w("MUSIC", "【错误】当前遍历到的第" + i + "个不是文件");
}
}
} else {
Log.w("MUSIC", "【错误】文件夹列表无效");
}
} else {
Log.w("MUSIC", "【错误】文件夹不存在");
}
return musics;
}
}
MainActivity 中 onCreate()
中加载数据的时候把getData
改为使用 MusicDao 获取,直接让变量 musics 赋值即可。同时改为在 initView()
之前获取,因为initView()
中就需要用到音乐数据了。以下代码即可:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获取数据
MusicDao dao = new MusicDao();
musics = dao.getMusicList();
//初始化控件
initView();
//添加点击事件
initListener();
}
private void initView() {
listView = findViewById(R.id.listview);
adapter = new MusicAdapter(this, musics);
listView.setAdapter(adapter);
......
}
2、创建不同的包
Activity 可以放在 Activity 或 UI 包中
DAL 表示 Data Access Layer
Entity 里面放实体类
3、Adapter的改进
增加 BaseAdapter
public class BaseAdapter<T> extends android.widget.BaseAdapter {
private Context context;
private List<T> data;
private LayoutInflater inflater;
public BaseAdapter(Context context, List<T> data) {
this.context = context;
this.data = data;
inflater = LayoutInflater.from(this.context);
}
protected final List<T> getData(){
return data;
}
protected final LayoutInflater getLayoutInflater(){
return inflater;
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return data.get(position);
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
MusicAdapter 改为继承 BaseAdapter。因为需要 data 和 inflater,所以之前的 BaseAdapter 中有对应的 getData()
和 getLayoutInflater()
方法方便获取。
public class MusicAdapter extends BaseAdapter<Music> {
public MusicAdapter(Context context, List<Music> data) {
super(context, data);
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(R.layout.item_music, null);
}
Music music = getData().get(position);
TextView tv_music_path = convertView.findViewById(R.id.tv_music_path);
TextView tv_music_title = convertView.findViewById(R.id.tv_music_title);
tv_music_path.setText(music.getPath());
String fileName = music.getTitle();
tv_music_title.setText(fileName.substring(0, fileName.lastIndexOf(".")));
return convertView;
}
}
4、BaseAdapter 还能优化
思考一个问题,当我们创建 MusicAdapter 对象时 Context 传 null
adapter = new MusicAdapter(null,musics);
语法没错误,但是程序运行会报错,因为构造方法中用到了 context,所以会报空指针的错误
因此可以对 BaseAdapter 进行改进,给 Context 增加 get 方法,在为空时抛出异常,为了保持统一,也给 data 和 inflater 也增加 get 方法。
public class BaseAdapter<T> extends android.widget.BaseAdapter {
......
public BaseAdapter(Context context, List<T> data) {
setContext(context);
setData(data);
setInflater();
setInflater();
}
private final void setContext(Context context) {
if (context == null) {
throw new IllegalArgumentException("参数Context不允许为null");
}
this.context = context;
}
private final void setData(List<T> data) {
if (data == null) {
data = new ArrayList<T>();
}
this.data = data;
}
private void setInflater() {
inflater = LayoutInflater.from(context);
}
protected final List<T> getData() {
return data;
}
protected final LayoutInflater getLayoutInflater() {
return inflater;
}
......
}
这样当我们创建 MusicAdapter 对象时 Context 再传 null 时,报错会更明显。
5、对 MusicDao 改造
MusicDao 的作用是获取歌曲信息,在 MainActivity 中使用时:
//获取歌曲数据
MusicDao dao = new MusicDao();
musics = dao.getMusicList();
如果还有其他 Activity 获取数据时也要写同样的代码,如果发现 MusicDao 写的不合理,要改成 MusicDao2,那么所有 Activity 都要改,这说明对 MusicDao 的依赖性太大,要减少对 MusicDao 的依赖,用到的是工厂模式。
首先找出只要是 MusicDao 的共同点,都有getData()
方法,所以先写一个接口 IDao
public interface IDao<T> {
/**
* 获取数据
*
* @return 数据的List集合
*/
List<T> getData();
}
再让 MusicDao 实现这个接口,把 getMusicList()
的内容放到 getData()
中。
创建工厂 MusicDaoFactory
public class MusicDaoFactory {
public static IDao<Music> newInstance() {
return new MusicDao();
}
}
MainActivity 获取数据时可以这样写:
//获取数据
IDao<Music> dao = MusicDaoFactory.newInstance();
musics = dao.getData();
工厂中的接口,就是约定要实现的哪些方法。接口是用来约定规范和标准的,因为实现一个接口就要实现对应的方法。
MusicDao 实现了 IDao 的接口,就是 IDao 类型的数据了。
最后可以把 MusicDao 的 public 去掉,在当前包内访问到即可