IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 音乐播放器 -> 正文阅读

[移动开发]Android 音乐播放器

音乐播放器

https://github.com/JakeWharton/butterknife

界面图镇贴:

在这里插入图片描述

功能分析

播放

  • 点击歌曲播放(recyclerView的Item的点击事件)
  • 点击按钮播放(按钮的点击事件)

暂停

  • 点击按钮暂停(按钮的点击事件)

上一首

  • 点击按钮切换到上一首(按钮的点击事件)

下一首

  • 点击按钮切换到下一首(按钮的点击事件)

布局

页面主布局

采用约束布局。

recyclerView放在上部分,下部分作为当前正在播放歌曲的信息展示和切换、播放、暂停歌曲的按钮。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@mipmap/bg2">

    <ImageView
        android:id="@+id/iv_splitline"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="@color/violet"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="75dp"

        />

    <ImageView
        android:id="@+id/iv_current_icon"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_centerVertical="true"
        android:background="@mipmap/a1"
        android:src="@mipmap/icon_song"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/iv_current_last"
        app:layout_constraintHorizontal_bias="0.036"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/iv_splitline"
        app:layout_constraintVertical_bias="0.448" />

    <TextView
        android:id="@+id/tv_current_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text=""
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/iv_current_icon"
        app:layout_constraintTop_toBottomOf="@+id/iv_splitline"
        app:layout_constraintVertical_bias="0.2" />

    <TextView
        android:id="@+id/tv_current_singer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:text=""
        android:textSize="12sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/iv_current_icon"
        app:layout_constraintTop_toBottomOf="@+id/tv_current_title"
        app:layout_constraintVertical_bias="0.277" />

    <ImageView
        android:id="@+id/iv_current_next"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:layout_centerVertical="true"
        android:src="@mipmap/icon_next"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/iv_splitline"
        android:layout_marginEnd="20dp"/>

    <ImageView
        android:id="@+id/iv_current_play"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginEnd="20dp"
        android:layout_toStartOf="@id/iv_current_next"
        android:src="@mipmap/icon_play"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/iv_current_next"
        app:layout_constraintTop_toBottomOf="@+id/iv_splitline" />

    <ImageView
        android:id="@+id/iv_current_last"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginEnd="20dp"
        android:layout_toStartOf="@id/iv_current_play"
        android:src="@mipmap/icon_last"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/iv_current_play"
        app:layout_constraintTop_toBottomOf="@+id/iv_splitline" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginBottom="0dp"
        app:layout_constraintBottom_toTopOf="@+id/iv_splitline"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        tools:ignore="MissingConstraints" />
</androidx.constraintlayout.widget.ConstraintLayout>

recyclerView中的ite布局

采用 卡片式布局,把每一个item作为一张卡片。

卡片中主要展示:歌曲排序的序列号,歌曲名,歌手名,专辑名,时长。

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="10dp"
    android:layout_marginRight="10dp"
    android:layout_marginTop="10dp"
    app:contentPadding="10dp"
    app:cardCornerRadius="10dp"
    app:cardElevation="1dp"
    app:cardBackgroundColor="@color/pink">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_number"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:text=""
            android:textSize="24sp"
            android:textStyle="bold"
            android:layout_marginStart="10dp"/>

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="20dp"
            android:layout_marginTop="10dp"
            android:layout_toEndOf="@id/tv_number"
            android:singleLine="true"
            android:text=""
            android:textSize="18sp"
            android:textStyle="bold"
            />

        <TextView
            android:id="@+id/tv_singer"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_title"
            android:layout_alignStart="@id/tv_title"
            android:layout_marginTop="5dp"
            android:text=""
            />

        <TextView
            android:id="@+id/tv_line"
            android:layout_width="2dp"
            android:layout_height="18dp"
            android:background="@color/textcolor"
            android:layout_toEndOf="@id/tv_singer"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_alignTop="@id/tv_singer"
            />

        <TextView
            android:id="@+id/tv_album"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignTop="@id/tv_singer"
            android:layout_toEndOf="@id/tv_line"
            android:ellipsize="end"
            android:singleLine="true"
            android:text=""
            android:textColor="@color/textcolor"
            android:textSize="14sp"
            />
        <TextView
            android:id="@+id/tv_duration"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/tv_singer"
            android:layout_alignParentEnd="true"
            android:text=""
            android:textSize="14sp"
            android:textColor="@color/textcolor"
            android:layout_centerVertical="true"/>
    </RelativeLayout>

</androidx.cardview.widget.CardView>

数据载入

初始化布局

private void initView() {
        lastIv = findViewById(R.id.iv_current_last);
        playIv = findViewById(R.id.iv_current_play);
        nextIv = findViewById(R.id.iv_current_next);
        songTv = findViewById(R.id.tv_current_title);
        singerTv = findViewById(R.id.tv_current_singer);
        musicRv = findViewById(R.id.rv);

        lastIv.setOnClickListener(this);
        playIv.setOnClickListener(this);
        nextIv.setOnClickListener(this);
    }

编写音乐实体类

目的:存放之后查询到的音乐的信息。

//音乐实体
    LocalMusicBean musicBean;
public class LocalMusicBean {
    private String id;//歌曲id
    private String song;//歌曲名称
    private String singer;//歌手名称
    private String album;//专辑名称
    private String duration;//歌曲时长
    private String path;//歌曲存储路径

    public LocalMusicBean() {
    }

    public LocalMusicBean(String id, String song, String singer, String album, String duration, String path) {
        this.id = id;
        this.song = song;
        this.singer = singer;
        this.album = album;
        this.duration = duration;
        this.path = path;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getSong() {
        return song;
    }

    public void setSong(String song) {
        this.song = song;
    }

    public String getSinger() {
        return singer;
    }

    public void setSinger(String singer) {
        this.singer = singer;
    }

    public String getAlbum() {
        return album;
    }

    public void setAlbum(String album) {
        this.album = album;
    }

    public String getDuration() {
        return duration;
    }

    public void setDuration(String duration) {
        this.duration = duration;
    }

    public String getPath() {
        return path;
    }

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

设置数据源

查找出手机中的音乐文件,把所有音乐文件存入数据源当中。

//数据源
    List<LocalMusicBean> mData;
 //获取ContentResolver对象
        String[] selectionArgs = new String[]{getString(R.string.music)};
        String selection = MediaStore.Audio.Media.DATA + getString(R.string.like);
        // 媒体库查询语句
         @SuppressLint("Recycle") Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, selection, selectionArgs, MediaStore.Audio.AudioColumns.IS_MUSIC);
            //遍历Cursor
            int id = 0;
                while (cursor.moveToNext()) {
                    String song = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
                    String singer = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
                    String album = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
                    long duration = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION));
                    @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat(getString(R.string.timeformat));
                    String time = dateFormat.format(new Date(duration));
                    //将一行当中的数据封装到对象当中
                    if (!time.equals(getString(R.string.timeiszeero))) {
                        id++;
                        String sid = String.valueOf(id);
                        String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA));
                        musicBean = new LocalMusicBean(sid, song, singer, album, time, path);
                        mData.add(musicBean);
                    }
                }
                //数据源发生变化,提示适配器更新
                musicAdapter.notifyDataSetChanged();

设置适配器

把数据源中的数据加载到布局中显示出来。

//适配器
    private LocalMusicAdapter musicAdapter;
public class LocalMusicAdapter extends RecyclerView.Adapter<LocalMusicAdapter.localMusicViewHolder> {
    //上下文
    Context context;
    //数据源
    List<LocalMusicBean> mDatas;

    //点击事件
    onItemClickListener onItemClickListener;


    public void setOnItemClickListener(LocalMusicAdapter.onItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    //自定义的点击事件的接口,后面通过回调写点击事件
    public interface onItemClickListener{
         void OnItemClick(View view,int position);
    }

    public LocalMusicAdapter(Context context, List<LocalMusicBean> mDatas) {
        this.context = context;
        this.mDatas = mDatas;
    }

    //加载recyclerView的item布局
    @NonNull
    @Override
    public localMusicViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_local_music, parent, false);
        return new localMusicViewHolder(view);
    }

    //item布局初始化并且设置点击事件
    @Override
    public void onBindViewHolder(@NonNull localMusicViewHolder holder, int position) {
        LocalMusicBean musicBean = mDatas.get(position);
        holder.idTv.setText(musicBean.getId());
        holder.songTv.setText(musicBean.getSong());
        holder.singerTv.setText(musicBean.getSinger());
        holder.albumTv.setText(musicBean.getAlbum());
        holder.timeTv.setText(musicBean.getDuration());

        holder.itemView.setOnClickListener(view -> {
            onItemClickListener.OnItemClick(view,position);
        });
    }

    //获得数据源列表的长度,即有多少首歌
    @Override
    public int getItemCount() {
        return mDatas.size();
    }

    //加载item的布局
    static class localMusicViewHolder extends RecyclerView.ViewHolder {

        TextView idTv,songTv,singerTv,albumTv,timeTv;

        public localMusicViewHolder(@NonNull View itemView) {
            super(itemView);
            idTv = itemView.findViewById(R.id.tv_number);
            songTv = itemView.findViewById(R.id.tv_title);
            singerTv = itemView.findViewById(R.id.tv_singer);
            albumTv = itemView.findViewById(R.id.tv_album);
            timeTv = itemView.findViewById(R.id.tv_duration);
        }
    }
}

点击事件

点击歌曲播放对应歌曲,点击上一首按钮,播放上一首歌曲;点击下一首歌曲,播放下一首歌曲。

    //记录当前正在播放的音乐的位置
    private int currentPosition = -1;

    //记录当前播放的音乐播放到哪了
    private int currentPositionInMusic = 0;
    
    MediaPlayer mediaPlayer;
//设置recyclerView的item点击事件
    private void setEventListener() {
        musicAdapter.setOnItemClickListener((view, position) -> {
            currentPosition = position;
            musicBean = mData.get(position);
            playMusicInMusicBean(musicBean);
        });
    }

    //根据歌曲来进行播放
    public void playMusicInMusicBean(LocalMusicBean musicBean) {
        songTv.setText(musicBean.getSong());
        singerTv.setText(musicBean.getSinger());

        stopMusic();

        mediaPlayer.reset();

        try {
            currentPositionInMusic = 0;
            mediaPlayer.setDataSource(musicBean.getPath());
            playMusic();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
    }

    //播放音乐
    private void playMusic(){
        if (currentPositionInMusic == 0){
            //从头开始播放
            if (mediaPlayer != null && !mediaPlayer.isPlaying()){
                try {
                    mediaPlayer.prepare();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mediaPlayer.start();
            }
        }else{
            //从暂停处开始播放
            mediaPlayer.seekTo(currentPositionInMusic);
            mediaPlayer.start();
        }
        playIv.setImageResource(R.mipmap.icon_pause);
    }

    //暂停播放音乐
    private void pauseMusic() {
        if (mediaPlayer != null && mediaPlayer.isPlaying()){
            currentPositionInMusic = mediaPlayer.getCurrentPosition();
            mediaPlayer.pause();
            playIv.setImageResource(R.mipmap.icon_play);
        }
    }

    //停止播放
    private void stopMusic() {
        if (mediaPlayer != null){
            mediaPlayer.pause();
            mediaPlayer.seekTo(0);
            mediaPlayer.stop();
            playIv.setImageResource(R.mipmap.icon_play);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
//        stopMusic();
    }
//按钮的点击事件
    @SuppressLint("NonConstantResourceId")
    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.iv_current_last:
                if (currentPosition == -1){
                    //没有选中要播放的音乐
                    Toast.makeText(this,"请选择要播放的音乐",Toast.LENGTH_LONG).show();
                    return;
                }
                if (currentPosition == 0){
                    Toast.makeText(this,"已经是第一首了 ",Toast.LENGTH_LONG).show();
                }
                else {
                    currentPosition--;
                    currentPositionInMusic = 0;
                    LocalMusicBean lastMusicBean = mData.get(currentPosition);
                    playMusicInMusicBean(lastMusicBean);
                }
                break;
            case R.id.iv_current_play:
                if (currentPosition == -1){
                    //没有选中要播放的音乐
                    Toast.makeText(this,"请选择要播放的音乐",Toast.LENGTH_LONG).show();
                    return;
                }
                if (mediaPlayer.isPlaying()){
                    //处于播放状态,需要暂停音乐
                    pauseMusic();
                }else {
                    playMusic();
                }
                break;
            case R.id.iv_current_next:
                if (currentPosition == -1){
                    Toast.makeText(this,"请选择要播放的音乐 ",Toast.LENGTH_LONG).show();
                    return;
                }else if (currentPosition == mData.size()-1){
                    Toast.makeText(this,"已经是最后一首了",Toast.LENGTH_LONG).show();
                }else {
                    currentPosition++;
                    currentPositionInMusic = 0;
                    LocalMusicBean nextMusicBean = mData.get(currentPosition);
                    playMusicInMusicBean(nextMusicBean);
                }
                break;
            default:
                break;
        }
    }

代码逻辑:先通过recyclerView的点击事件拿到歌曲在列表中的位置和对应歌曲实体,然后再根据歌曲实体拿到歌曲信息加载到布局中。最后根据所拿信息调用MediaPlayer的方法进行播放。

注意:暂停功能中用到了播放位置,在第一次播放时currentPositionInMusic对播放无影响,但是如果按了暂停,currentPositionInMusic发生改变,如果这时候点击事件不是继续播放,且没有对currentPositionInMusic进行重置,那么会进入继续播放逻辑的代码中,但是继续播放逻辑的代码没有对歌曲实体进行加载和准备,所以会报错。因此,在非继续播放事件发生前需要对currentPositionInMusic进行重置

整个项目已经在gitee上开源,如有需要请自取。gitee地址:https://gitee.com/luozhaosong/new-music-app

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-20 15:14:29  更:2021-08-20 15:16:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:19:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码