Android中实现音乐播放器3.0(上)

203 阅读2分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

文章目录

开发步骤

【1】显示软件界面
1)设计布局:包括 activity_main.xml 和 ListView 的模板
a)先设计底部按钮区域
b)设计中间歌曲信息显示区域
c)添加ListView控件
2)读取数据(完成 MusicDao 相关类)
a)先设计实体类 Music
b)设计 IDao 接口
c)创建 MusicDao 实现 IDao 接口,并重写抽象方法
d)创建 MusicDaoFactory,提供静态方法返回 MusicDao 的对象
3)实现 Adapter(完成 MusicAdapter 相关类)
4)显示 ListView
【2】开发 Service
1)创建并注册 Service
2)在 Service 内部完成播放控制相关方法,例如 play() 等
【3】实现基本控制
【4】实现播放信息显示
【5】实现细节,修复 bug

展示音乐列表

布局文件

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ListView
        android:id="@+id/lv_music_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/ll_music_info" />

    <LinearLayout
        android:id="@+id/ll_music_info"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/sk_progress"
        android:padding="10dp">

        <TextView
            android:id="@+id/tv_music_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="请选择播放歌曲" />

    </LinearLayout>

    <SeekBar
        android:id="@+id/sk_progress"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/ll_button_layout" />

    <LinearLayout
        android:id="@+id/ll_button_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:gravity="center">

        <ImageButton
            android:id="@+id/ib_previous"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/ic_media_previous" />

        <ImageButton
            android:id="@+id/ib_play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/ic_media_play" />

        <ImageButton
            android:id="@+id/ib_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@android:drawable/ic_media_next" />

    </LinearLayout>

</RelativeLayout>

item_music.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">

    <TextView
        android:id="@+id/tv_music_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:text="title"
        android:textColor="#222222"
        android:textSize="15dp" />

    <TextView
        android:id="@+id/tv_music_path"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/tv_music_title"
        android:maxLines="1"
        android:text="path......"
        android:textColor="#999999"
        android:textSize="15dp" />
</RelativeLayout>

目录结构
在这里插入图片描述

Music类

public class Music {
    private String title;
    private String path;

    public Music() {
    }

    public Music(String title, String path) {
        this.title = title;
        this.path = path;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String toString() {
        return "Music [Title=" + title + ",Path=" + path;
    }
}

IDao

public interface IDao<T> {
    /**
     * 获取数据
     * @return 数据的List集合
     */
    List<T> getData();
}

MusicDao

public class MusicDao implements IDao<Music> {
    public List<Music> getData() {
        List<Music> musics = new ArrayList<>();
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
            File musicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
            File[] files = musicDir.listFiles();
            if (files != null && files.length > 0) {
                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(fileName.substring(0, fileName.length() - 4));
                            musics.add(music);
                        }
                    }
                }
            }
        }
        return musics;
    }
}

MusicDaoFactory

public class MusicDaoFactory {
    //如果是单例,就用getInstance();
    public static IDao<Music> newInstance(Context context) {
        return new MusicDao();
    }
}

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) {
        setContext(context);
        setData(data);
        setInflater();
    }

    public Context getContext() {
        return context;
    }

    public void setContext(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("参数Context不允许为null");
        }
        this.context = context;
    }

    public List<T> getData() {
        return data;
    }

    public void setData(List<T> data) {
        if (data == null) {
            data = new ArrayList<>();
        }
        this.data = data;
    }

    public LayoutInflater getInflater() {
        return inflater;
    }

    public void setInflater() {
        inflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int i) {
        return null;
    }

    @Override
    public long getItemId(int i) {
        return 0;
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        return view;
    }
}

MusicAdapter

public class MusicAdapter extends BaseAdapter<Music> {
    public MusicAdapter(Context context, List data) {
        super(context, data);
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        Music music = getData().get(i);

        if (view == null) {
            view = getInflater().inflate(R.layout.item_music, null);
        }

        TextView title = view.findViewById(R.id.tv_music_title);
        TextView path = view.findViewById(R.id.tv_music_path);

        title.setText(music.getTitle());
        path.setText(music.getPath());

        return view;
    }
}

AndroidManifest.xml

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

MainActivity

public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private TextView tvMusicInfo;
    private SeekBar skMusicProgress;
    private ImageButton ibPrevious;
    private ImageButton ibPlay;
    private ImageButton ibNext;

    private List<Music> musics;
    private MusicAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        musics = MusicDaoFactory.newInstance(this).getData();
        adapter = new MusicAdapter(this, musics);
        listView.setAdapter(adapter);

        setListeners();
    }

    private void setListeners() {

    }

    private void initView() {
        listView = findViewById(R.id.lv_music_list);
        tvMusicInfo = findViewById(R.id.tv_music_info);
        skMusicProgress = findViewById(R.id.sk_progress);
        ibPrevious = findViewById(R.id.ib_previous);
        ibPlay = findViewById(R.id.ib_play);
        ibNext = findViewById(R.id.ib_next);
    }
}

运行程序
在这里插入图片描述

对比2.0优化

我们在 MusicAdapter 中输出 View 和 TextView,然后上下滑动观察日志

		Log.d("TTT",title.toString());
        Log.d("TTT",path.toString());

在这里插入图片描述

我们已经知道整个 View 是可以重复使用的,所以即使 title 和 path 每次都是新 new出来,但还是重复使用的:

TextView title = view.findViewById(R.id.tv_music_title);
TextView path = view.findViewById(R.id.tv_music_path);

所以我们反复的声明变量是没有意义的。

我们的 MusicAdapter 优化如下

public class MusicAdapter extends BaseAdapter<Music> {
    public MusicAdapter(Context context, List data) {
        super(context, data);
    }

    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        Music music = getData().get(i);

        ViewHolder holder;

        if (view == null) {
            view = getInflater().inflate(R.layout.item_music, null);
            holder = new ViewHolder();
            holder.title = view.findViewById(R.id.tv_music_title);
            holder.path = view.findViewById(R.id.tv_music_path);
            view.setTag(holder);
        } else {
            holder = (ViewHolder) view.getTag();
        }

        holder.title.setText(music.getTitle());
        holder.path.setText(music.getPath());

        return view;
    }

    class ViewHolder {
        TextView title;
        TextView path;
    }
}

尽量使用 ViewHolder 这个类名,不要加private,不要加static

我们之前学到使用 static 有2个理由
1、使用频率太高
2、为了完成特定的设计模式或程序结构
这里一个都不符合,所以不加