基于存储卡的音乐播放器0.6

一、知识点
1.标签(TextView)
2.按钮(Button)
3.媒体播放器(MediaPlayer)
4.进度条(ProgressBar)
5.线程(Thread)
6.消息处理器(Handler)
7.列表视图(ListView)
8.应用程序(Application)
9.递归算法(Recursive Algorithm)
10.异步任务(AsyncTask)
11.服务(Service)
12.广播接受者(BroadcastReceiver)
13.布局
14.补间动画(Tween Animation)
15.自定义适配器
16.拖拽条(SeekBar)

二、功能
•数据源来自媒体库音频
•播放存储卡上一首音乐 ,上一首与下一首可切换
•播放|暂停按钮,可切换
•显示要播放的音乐名
•要求窗口有背景图片
•有进度条 播放时长、位置
•播放进度清零
•自动切换到下一首音乐播放
•扫描存储卡上所有mp3文件(递归算法)
•使用应用程序存放数据
•利用应用程序来保存广播频道
•Service类来实现音乐的播放与暂停以及进度的更新
•通过按钮来来选择播放模式
三、创建安卓应用
在这里插入图片描述
在这里插入图片描述
1、将MP3音频文件放到存储卡指定位置
在这里插入图片描述

添加自己喜欢的MP3音频文件到Music目录

•关闭模拟器之后,重启应用程序
2、访问媒体库音频数据
在这里插入图片描述
在这里插入图片描述
打开Navicat premium ,连接SQLite,打开external.db
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3、将图片素材拷贝到Drawable目录
在这里插入图片描述
4、创建按钮背景图片选择器
在这里插入图片描述
(一)、创建播放一个背景图片选择器按钮
在这里插入图片描述
在这里插入图片描述

四)、创建上一首背景图片选择器按钮
在这里插入图片描述
5、在项目清单文件里设置安卓应用的图标
在这里插入图片描述
在这里插入图片描述
6、在res下创建anim文件夹,在里面创建动画资源文件animator.xml
在这里插入图片描述
在这里插入图片描述
7、创建自定义边框配置文件custom_border.xml
在这里插入图片描述
8、主布局
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>


    <ProgressBar
        android:id="@+id/pbScanMusic"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:visibility="gone" />

    <TextView
        android:id="@+id/tvScanMusic"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/scan_music"
        android:textColor="#0000ff"
        android:textSize="25sp"
        android:visibility="gone" />
</LinearLayout>
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@drawable/custom_border"
    android:gravity="center"
    android:orientation="horizontal"
    android:padding="5dp">

    <TextView
        android:id="@+id/tvPlayMode"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/play_mode"
        android:textSize="13sp" />

    <RadioGroup
        android:id="@+id/rgPlayMode"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/tvOrder"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            android:text="@string/order"
            android:textSize="13sp" />

        <RadioButton
            android:id="@+id/tvRandom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/random"
            android:textSize="13sp" />

        <RadioButton
            android:id="@+id/tvLoop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/loop"
            android:textSize="13sp" />
    </RadioGroup>
</LinearLayout>

<ListView
    android:id="@+id/lvMusicName"
    android:layout_width="match_parent"
    android:layout_height="450dp"
    android:layout_marginBottom="16dp"
    android:layout_weight="8" />

<TextView
    android:id="@+id/tvMusicName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="0.5"
    android:textColor="#0000ff"
    android:textSize="16sp" />

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="10dp"
    android:layout_marginTop="10dp"
    android:layout_weight="0.5"
    android:gravity="center"
    android:orientation="horizontal">

    <TextView
        android:id="@+id/tvCurrentPosition"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textColor="#ff0000" />

    <SeekBar
        android:id="@+id/pbMusicProgress"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="6" />

    <TextView
        android:id="@+id/tvDuration"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:textColor="#ff00ff" />
</LinearLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="0.5"
    android:gravity="center"
    android:orientation="horizontal">

    <Button
        android:id="@+id/btnPrevious"
        android:layout_width="60dp"
        android:layout_height="50dp"
        android:background="@drawable/previous_button_selector"
        android:onClick="doPrevious" />

    <Button
        android:id="@+id/btnPlayOrPause"
        android:layout_width="60dp"
        android:layout_height="50dp"
        android:background="@drawable/play_button_selector"
        android:onClick="doPlayOrPause" />

    <Button
        android:id="@+id/btnNext"
        android:layout_width="60dp"
        android:layout_height="50dp"
        android:background="@drawable/next_button_selector"
        android:onClick="doNext" />
</LinearLayout>
9、创建音乐模板 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011151746.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) <?xml version="1.0" encoding="utf-8"?>
<TextView
    android:id="@+id/tvMusicName"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="20sp"/>
10、创建安卓应用SplashScreenActivity.java ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011335554.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/2021011201142060.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) 11、启动界面布局资源文件 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011447843.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) <?xml version="1.0" encoding="utf-8"?>
<ImageView
    android:id="@+id/iv_music_icon"
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:src="@drawable/music"
    android:layout_marginTop="20dp"/>


<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical">
<TextView
    android:id="@+id/tv_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="30dp"
    android:text="@string/title"
    android:textColor="#0000ff"
    android:textSize="25sp" />

<TextView
    android:id="@+id/tv_version"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="20dp"
    android:text="@string/version"
    android:textColor="#ff0000"
    android:textSize="20sp" />

<TextView
    android:id="@+id/tv_author"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="20dp"
    android:text="@string/author"
    android:textColor="#000000"
    android:textSize="20sp" />
</LinearLayout>
12、字符串资源文件String.xml ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011558978.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) 基于存储卡音乐播放器v0.6 播放模式: 顺序 单曲循环 随机 基于媒体库音乐播放器 Version 1.0 19软件一班陈松林 扫描外置存储卡上全部MP3,请耐心等待...... 13、创建ui子包,将MainActivity和SplashScreenActivity拖进ui子包 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011700471.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011715769.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) 14、创建entity子包 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011742788.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011756767.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) 15、在entity子包里面创建音乐实体类Music ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210112011831332.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzQ2MjA0MzAy,size_16,color_FFFFFF,t_70#pic_center) package net.tp.sdcard_musicplayer_v06.entity;

public class Music {

private String musicName; // 音乐名

public String getMusicName() {
    return musicName;
}

public void setMusicName(String musicName) {
    this.musicName = musicName;
}

@Override
public String toString() {
    return "Music{" +
            "musicName='" + musicName + '\'' +
            '}';
}

}
16、创建app子包
在这里插入图片描述
在这里插入图片描述
17、在app子包里面创建音乐播放器应用程序类MusicPlayerApplication
在这里插入图片描述
package net.tp.sdcard_musicplayer_v06.app;

import android.app.Application;
import android.os.Environment;

import net.tp.sdcard_musicplayer_v06.entity.Music;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;

public class MusicPlayerApplication extends Application {

private SimpleDateFormat sdf; // 简单日期格式
private int currentMusicIndex; // 当前音乐索引
private int currentPosition; // 当前播放位置
private int playMode;//播放模式
private int progressChangedByUser;//用户修改的播放进度

@Override
public void onCreate() {
    super.onCreate();
    // 实例化简单日期格式
    sdf = new SimpleDateFormat("mm:ss");

}


public int getCurrentMusicIndex() {
    return currentMusicIndex;
}

public void setCurrentMusicIndex(int currentMusicIndex) {
    this.currentMusicIndex = currentMusicIndex;
}

public int getCurrentPosition() {
    return currentPosition;
}

public void setCurrentPosition(int currentPosition) {
    this.currentPosition = currentPosition;
}
//播放模式
public int getPlayMode() {
    return playMode;
}

public void setPlayMode(int playMode) {
    this.playMode = playMode;
}

public int getProgressChangedByUser() {
    return progressChangedByUser;
}

public void setProgressChangedByUser(int progressChangedByUser) {
    this.progressChangedByUser = progressChangedByUser;
}

/**
 * 获取格式化时间
 *
 * @param time 单位是毫秒
 * @return mm:ss格式的时间
 */
public String getFormatTime(int time) {
    return sdf.format(time);
}

/**
 * 生成指定目录下某种类型的文件列表
 *
 * @param dir
 * @param suffix
 * @param typeFileList
 */
public void makeTypeFileList(File dir, String suffix, List<String> typeFileList) {
    // 获取指定目录下的File数组(File既可以指向目录,也可以指向文件)
    File[] files = dir.listFiles();
    // 遍历File数组
    for (File file : files) {
        // 判断file是否是文件
        if (file.isFile()) { // file是文件
            // 按照后缀来过滤文件
            if (file.getName().endsWith(suffix)) {
                // 将满足条件的文件添加到文件列表
                typeFileList.add(file.getAbsolutePath());
            }
        } else { // file是目录
            // 目录非空,递归调用
            if (file.list() != null) {
                makeTypeFileList(file, suffix, typeFileList);
            }
        }
    }
}

/**
 * 获取音乐列表
 *
 * @return 音乐列表
 */
public List<Music> getMusicList() {
    // 声明音乐列表
    List<Music> musicList = null;

    // 获取外置存储卡根目录
    File sdRootDir = Environment.getExternalStorageDirectory();
    // 创建后缀字符串
    String suffix = ".mp3";
    // 创建音乐文件列表
    List<String> musicFileList = new ArrayList<>();
    // 调用方法,生成指定目录下某种类型文件列表
    makeTypeFileList(sdRootDir, suffix, musicFileList);
    // 判断音乐文件列表里是否有元素
    if (musicFileList.size() > 0) {
        // 实例化音乐列表
        musicList = new ArrayList<>();
        // 遍历音乐文件列表
        for (String musicFile : musicFileList) {
            // 创建音乐实体
            Music music = new Music();
            // 设置实体属性
            music.setMusicName(musicFile);
            // 将音乐实体添加到音乐列表
            musicList.add(music);
        }
    }

    // 返回音乐列表
    return musicList;
}

}
18、在项目清单文件里个音乐播放器应用程序类注册
在这里插入图片描述
在这里插入图片描述
19、创建adapter子包
在这里插入图片描述

20、在adapter子包里面创建音乐适配器
在这里插入图片描述
android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import net.tp.sdcard_musicplayer_v06.R;
import net.tp.sdcard_musicplayer_v06.entity.Music;

import java.util.List;

public class MusicAdapter extends BaseAdapter {
private Context context; // 上下文
private List musicList; // 音乐列表

/**
 * 构造方法
 *
 * @param context
 * @param musicList
 */
public MusicAdapter(Context context, List<Music> musicList) {
    this.context = context;
    this.musicList = musicList;
}

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

@Override
public Object getItem(int position) {
    return musicList.get(position);
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // 声明视图容器
    ViewHolder holder = null;

    // 判断转换视图是否为空
    if (convertView == null) {
        // 将列表项模板映射成转换视图
        convertView = LayoutInflater.from(context).inflate(R.layout.music_name_list_item, null);
        // 创建视图容器对象
        holder = new ViewHolder();
        // 实例化转换视图里的控件
        holder.tvMusicName = convertView.findViewById(R.id.tvMusicName);
        // 将视图容器附加到转换视图
        convertView.setTag(holder);
    } else {
        // 从转换视图里取出视图容器
        holder = (ViewHolder) convertView.getTag();
    }

    // 获取列表项要显示的数据
    Music music = musicList.get(position);
    // 设置列表项控件的属性(去掉路径和扩展名)
    holder.tvMusicName.setText(music.getMusicName().substring(
            music.getMusicName().lastIndexOf("/") + 1, music.getMusicName().lastIndexOf(".")));
    // 返回转换视图
    return convertView;
}

/**
 * 视图容器
 */
private static class ViewHolder {
    TextView tvMusicName;
}

}
21、在app子包常见应用程序常量接口 - AppConstants
package net.tp.sdcard_musicplayer_v06.app;

/**

  • 功能:应用程序常量接口
  • 作者:陈松林
  • 日期:2021年01月07日
    */

public interface AppConstants {
String TAG=“net.tp.sdcard_musicplayer_v06”;//应用程序标记
String INTENT_ACTION_PREVIOUS =TAG +".intent.action.PREVIOUS";//广播频道常量:播放上一首
String INTENT_ACTION_PLAY =TAG +".intent.action.PLAY";//广播频道常量:播放
String INTENT_ACTION_PLAY_OR_PAUSE =TAG +".intent.action.PLAY_OR_PAUSE";//广播频道常量:播放或暂停
String INTENT_ACTION_NEXT =TAG +".intent.action.NEXT";//广播频道常量:播放下一首
String INTENT_ACTION_UPDATE_PROGRESS =TAG +".intent.action.UPDATE_PROGRESS";//广播频道常量:更新播放进度
String INTENT_ACTION_USER_CHANCG_PROGRESS =".intent.action.USER_CHANGE_PROGRESS";//改变进度条
String CONTROL_ICON =“control_icon”;//控制图标名称常量
String DURATION =“duration”;//播放时长名称常量
String CURRENT_POSITION=“current_position”;//当前播放位置名称常量
int PLAY_MODE_ORDER = 0;//播放模式:顺序播放
int PLAY_MODE_RANDOM = 1;// 播放模式:随机播放
int PLAY_MODE_LOOP = 2;//播放模式:单曲循环
}
22、在项目清单文件里授权读外置存储卡
在这里插入图片描述
23、创建service子包
24、在service子包里面创建音乐播放服务类 - MusicPlayService
package net.tp.sdcard_musicplayer_v06.service;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.MediaPlayer;
import android.os.IBinder;

import androidx.annotation.Nullable;

import net.tp.sdcard_musicplayer_v06.R;
import net.tp.sdcard_musicplayer_v06.app.AppConstants;
import net.tp.sdcard_musicplayer_v06.app.MusicPlayerApplication;
import net.tp.sdcard_musicplayer_v06.entity.Music;

import java.io.IOException;
import java.util.List;
import java.util.Random;

public class MusicPlayService extends Service implements AppConstants {

private MediaPlayer mp; // 媒体播放器
private String musicName; // 音乐文件名
private Thread thread; // 更新音乐播放器进度的线程
private boolean isRunning; // 线程循环控制变量
private List<Music> musicList; // 音乐列表(数据源)
private MusicPlayerApplication app; // 音乐播放器应用程序对象
private MusicReceiver receiver;//音乐广播接收者


@Override
public void onCreate() {
    super.onCreate();
    //获取音乐播放器应用程序对象
    app= (MusicPlayerApplication) getApplication();
    //获取音乐列表
    musicList=app.getMusicList();

    //创建媒体播放器
    mp=new MediaPlayer();
    //给媒体播放器注册完成监听器
    mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            //切换到下一首音乐
            nextMusic();
        }
    });

    //设置线程循环控制变量为真
    isRunning=true;
    //创建更新播放进度的线程
    thread=new Thread(new Runnable(){
        @Override
        public void run() {
            while(isRunning){
                //判断音乐是否在播放
                if(mp.isPlaying()){
                    //创意意图
                    Intent intent=new Intent();
                    //设置广播频道:更新播放进度
                    intent.setAction(INTENT_ACTION_UPDATE_PROGRESS);
                    //设置当前播放位置
                    app.setCurrentPosition(mp.getCurrentPosition());
                    //让意图携带播放时长数据
                    intent.putExtra(DURATION,mp.getDuration());
                    //让意图携带控制图标数据(暂停图标)
                    intent.putExtra(CONTROL_ICON, R.drawable.pause_button_selector);
                    //按意图发送广播
                    sendBroadcast(intent);
                    try {
                        //让线程睡眠500毫秒
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    });
    //启动跟新播放进度的线程
    thread.start();

    //创建音乐广播接收器
    receiver=new MusicReceiver();
    //创建意图过滤器
    IntentFilter filter=new IntentFilter();
    //通过意图过滤器添加广播频道
    filter.addAction(INTENT_ACTION_PLAY_OR_PAUSE);
    filter.addAction(INTENT_ACTION_PLAY);
    filter.addAction(INTENT_ACTION_PREVIOUS);
    filter.addAction(INTENT_ACTION_NEXT);
    filter.addAction(INTENT_ACTION_USER_CHANCG_PROGRESS);
    //注册广播接收器
    registerReceiver(receiver,filter);


}



@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //获取当前播放音乐名
    musicName=musicList.get(app.getCurrentMusicIndex()).getMusicName() ;
    try {
        //设置播放源
        mp.setDataSource(musicName);
        //缓冲播放源
        mp. prepare();
        //音乐当前播放位置归雾
        app.setCurrentPosition(0);
        //创建意图
        Intent intent1 = new Intent() ;
        //设置广播频道:更新播放进度
        intent1.setAction(INTENT_ACTION_UPDATE_PROGRESS);
        //让意图携带播放时长
        intent1.putExtra(DURATION,mp.getDuration());
        //按意图发送广播
        sendBroadcast(intent1) ;
    } catch (IOException e) {
        e. printStackTrace();
    }
    //返回非粘性服务
    return Service.START_NOT_STICKY;

}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}






/**
 * 销毁回调方法,释放资源
 */
@Override
public void onDestroy() {
    super.onDestroy();
    // 释放媒体播放器
    if(mp!=null){
        mp.release();
        // 销毁媒体播放器
        mp = null;
    }
    //注销广播接收者
    unregisterReceiver(receiver);
    //设置线程循环控制变量
    isRunning=false;
    //销毁子线程
    thread=null;

}



/**
 * 播放方法(只管播放)
 */
private void play(){
    try {
        // 重置媒体播放器
        mp.reset();
        // 获取当前播放的音乐名
        musicName = musicList.get(app.getCurrentMusicIndex()).getMusicName();
        // 设置播放源
        mp.setDataSource(musicName);
        // 缓冲播放源(从存储卡加载到内存)
        mp.prepare();
        // 定位到暂停时的播放位置
        mp.seekTo(app.getCurrentPosition());
        // 启动音乐的播放
        mp.start();
    } catch (IOException e) {
        e.printStackTrace();
    }

}

/**
 * 上一首音乐
 */
private void previousMusic() {
    // 根据播放模式来更新音乐索引
    switch (app.getPlayMode()) {
        // 顺序播放模式
        case PLAY_MODE_ORDER:
            // 更新音乐索引
            if (app.getCurrentPosition() > 0) {
                app.setCurrentMusicIndex(app.getCurrentMusicIndex() - 1);
            } else {
                app.setCurrentMusicIndex(musicList.size() - 1);
            }
            break;
        // 随机播放模式
        case PLAY_MODE_RANDOM:
            // 随机设置索引
            app.setCurrentMusicIndex(new Random().nextInt(app.getMusicList().size()));
            break;
        // 单曲循环模式
        case PLAY_MODE_LOOP:
            // 音乐索引保持不变
            break;
    }
    // 当前播放位置归零
    app.setCurrentPosition(0);
    // 调用播放方法
    play();
}

/**
 * 下一首音乐
 */
private void nextMusic(){
    // 根据播放模式来更新音乐索引
    switch (app.getPlayMode()) {
        // 顺序播放模式
        case PLAY_MODE_ORDER:
            if (app.getCurrentMusicIndex() < musicList.size() - 1) {
                app.setCurrentMusicIndex(app.getCurrentMusicIndex() + 1);
            } else {
                app.setCurrentMusicIndex(0);
            }
            break;
        // 随机播放模式
        case PLAY_MODE_RANDOM:
            // 随机设置索引
            app.setCurrentMusicIndex(new Random().nextInt(app.getMusicList().size()));
            break;
        // 单曲循环模式
        case PLAY_MODE_LOOP:
            // 音乐索引保持不变
            break;

    }
    // 当前播放位置归零
    app.setCurrentPosition(0);
    // 调用播放方法
    play();
}

/**
 * 暂停方法
 */
private void pause() {
    // 暂停播放
    mp.pause();
    // 保存音乐播放的当前位置
    app.setCurrentPosition(mp.getCurrentPosition());
    //任务:发送广播给主界面,更改图标,更改播放进度
    //创建意图
    Intent intent=new Intent();
    //设置广播频道:更新播放进度
    intent.setAction(INTENT_ACTION_UPDATE_PROGRESS);
    //让意图携带播放时长数据
    intent.putExtra(DURATION,mp.getDuration());
    //让意图携带控制图标数据(播放图)
    intent.putExtra(CONTROL_ICON, R.drawable.play_button_selector);
    //按意图发送广播
    sendBroadcast(intent);

}


/*
 * 音乐广播接收器
 */
private class MusicReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //获取意图动作(广播频道)
        String action=intent.getAction();

        //对广播频道非空判断
        if(action!=null){
            //根据不同频道进行不同操作
            switch(action){
                case INTENT_ACTION_PLAY://主界面单击了列表项
                    //让播放进度归零
                    app.setCurrentPosition(0);
                    //调用播放方法
                    play();
                    break;
                case INTENT_ACTION_PLAY_OR_PAUSE://主界面单击了播放暂停按钮
                    //判断音乐是否在播放
                    if(mp.isPlaying()){
                        pause();
                    }else {
                        play();
                    }
                    break;
                case INTENT_ACTION_PREVIOUS://主界面单击了上一首按钮
                    //切换到上一首音乐
                    previousMusic();
                    break;
                case INTENT_ACTION_NEXT://主界面单击了下一首按钮
                    //切换到下一首音乐
                    nextMusic();
                    break;
                case INTENT_ACTION_USER_CHANCG_PROGRESS:
                    // 根据拖拽条的进度值计算当前播放位置
                    app.setCurrentPosition(app.getProgressChangedByUser() * mp.getDuration() / 100);
                    // 根据音乐当前播放位置开始播放音乐
                    play();
                    break;

            }
        }
    }
}

}
25、在项目清单文件里注册音乐播放服务
在这里插入图片描述
26、主界面类 - MainActivity
在这里插入图片描述
package net.tp.sdcard_musicplayer_v06.ui;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import net.tp.sdcard_musicplayer_v06.R;
import net.tp.sdcard_musicplayer_v06.adapter.MusicAdapter;
import net.tp.sdcard_musicplayer_v06.app.AppConstants;
import net.tp.sdcard_musicplayer_v06.app.MusicPlayerApplication;
import net.tp.sdcard_musicplayer_v06.entity.Music;
import net.tp.sdcard_musicplayer_v06.service.MusicPlayService;

import java.util.List;

public class MainActivity extends AppCompatActivity implements AppConstants {

private String musicName; //音乐文件名
private TextView tvMusicName ;//音乐名标签
private Button btnP1ayOrPause; //播放|暂停按钮
private MusicReceiver receiver; //音乐广播接收器
private TextView tvCurrentPosition; //显示当前播放位的标签
private TextView tvDuration; //显示音乐播放时长的标签
private SeekBar pbMusicProgress; //音乐播放进度条
private ListView lvMusicName; //音乐名列表控件
private List<Music> musicList; //音乐列表(数据源)
private MusicAdapter adapter; //音乐适配器
private MusicPlayerApplication app; //音乐播放器应用程序对象
private ProgressBar pbScanMusic; //扫描存储卡音乐进度条
private TextView tvScanMusic; //扫描存储卡音乐标签(提示用户耐心等待)
private RadioGroup rgPlayMode;//播放模式单选按钮组




@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // 利用布局资源文件设置用户界面
    setContentView(R.layout.activity_main);

    //通过资源标识符获取控件实例
    lvMusicName = findViewById(R.id.lvMusicName);
    tvMusicName = findViewById(R.id.tvMusicName);
    btnP1ayOrPause = findViewById(R.id.btnPlayOrPause);
    tvCurrentPosition = findViewById(R.id.tvCurrentPosition);
    tvDuration = findViewById(R.id.tvDuration);
    pbMusicProgress = findViewById(R.id.pbMusicProgress);
    pbScanMusic = findViewById(R.id.pbScanMusic);
    tvScanMusic = findViewById(R.id.tvScanMusic);
    rgPlayMode=findViewById(R.id.rgPlayMode);
    //获取音乐播放器应用程序对象
    app = (MusicPlayerApplication) getApplication();

    //定义存储读写权限数组
    String[] PERMISSIONS_STORAGE = {
            Manifest. permission.READ_EXTERNAL_STORAGE ,
            Manifest . permission.WRITE_EXTERNAL_STORAGE
    };
    //检查是否有读权限
    final int permission = ActivityCompat. checkSelfPermission( this, PERMISSIONS_STORAGE[0]);
    //如果没有授权,那么就请求读权限
    if (permission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,PERMISSIONS_STORAGE,0);
        return;
    }

    //执行填充音乐列表的异步任务
    new FillMusicListTask().execute();

    //给音乐列表控件注册监听器
    lvMusicName.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            //获取音乐索引
            app. setCurrentMusicIndex(position);
            //播放进度归雾
            app.setCurrentPosition(0);
            //获取音乐名
            musicName = app.getMusicList().get(position).getMusicName();
            //设置音乐名标签内容.去掉路径和扩展名
            tvMusicName . setText("No."+ (app. getCurrentMusicIndex() +1)+" "+musicName. substring(
                    musicName. lastIndexOf( '/') + 1, musicName. lastIndexOf( ".")));
            //创建意图
            Intent intent = new Intent();
            //设置广播频道:播放
            intent.setAction(INTENT_ACTION_PLAY);
            //按意图发送广播
            sendBroadcast(intent);
        }
    });
    // 给播放模式单选按钮组注册监听器
    rgPlayMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(RadioGroup radioGroup, int checkedId) {
            // 判断用户选择何种播放模式
            switch (checkedId) {
                // 顺序播放模式
                case R.id.tvOrder:
                    app.setPlayMode(PLAY_MODE_ORDER);
                    break;
                // 随机播放模式
                case R.id.tvRandom:
                    app.setPlayMode(PLAY_MODE_RANDOM);
                    break;
                // 单曲循环模式
                case R.id.tvLoop:
                    app.setPlayMode(PLAY_MODE_LOOP);
                    break;
            }
        }
    });


    //进度条监听器

    pbMusicProgress.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int position, boolean fromUser) {
            if (fromUser){
                app.setProgressChangedByUser(position);
                //创建意图
                Intent intent = new Intent();
                //设置频道
                intent.setAction(INTENT_ACTION_USER_CHANCG_PROGRESS);
                //发送广播
                sendBroadcast(intent);
            }
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    });
    //创建音乐广播接收器
    receiver = new MusicReceiver();
    //创建意图过滤器
    IntentFilter filter = new IntentFilter();
    //通过意图过滤器添加广播频道
    filter.addAction( INTENT_ACTION_UPDATE_PROGRESS) ;
    //注册音乐广播接收器
    registerReceiver(receiver,filter);
}

/**
 * 填充音乐列表异步任务类
 */
private class FillMusicListTask extends AsyncTask<Void, Integer, Void> {
    /**
     * 耗时工作执行前
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // 显示扫描音乐进度条
        pbScanMusic.setVisibility(View.VISIBLE);
        // 显示扫描音乐标签
        tvScanMusic.setVisibility(View.VISIBLE);
    }

    @Override
    protected Void doInBackground(Void... voids) {
        // 获取音乐列表
        musicList = app.getMusicList();
        // 故意耗时,要不然扫描太快结束
        for (long i = 0; i < 2000000000; i++) {
        }
        return null;
    }

    /**
     * 耗时工作执行后
     *
     * @param aVoid
     */
    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        // 隐藏扫描音乐进度条
        pbScanMusic.setVisibility(View.GONE);
        // 隐藏扫描音乐标签
        tvScanMusic.setVisibility(View.GONE);

        // 判断音乐列表是否有元素
        if (musicList.size() > 0) {
            // 创建音乐适配器
            adapter = new MusicAdapter(MainActivity.this, musicList);
            // 给音乐列表控件设置适配器
            lvMusicName.setAdapter(adapter);
            // 获取当前要播放的音乐名(默认是音乐播放列表的第一首)
            musicName = musicList.get(0).getMusicName();
            // 设置音乐名标签内容,去掉路径和扩展名,添加序号
            tvMusicName.setText("No." + (app.getCurrentMusicIndex() + 1) + " " + musicName.substring(
                    musicName.lastIndexOf("/") + 1, musicName.lastIndexOf(".")));
            //创建意图,用于启动音乐播放服务
            Intent intent=new Intent(MainActivity.this, MusicPlayService.class);
            //按照意图启动服务
            startService(intent);
        } else {
            // 提示用户没有音乐文件
            Toast.makeText(MainActivity.this, "外置存储卡上没有音乐文件!", Toast.LENGTH_SHORT);
        }
    }
}


/*
 * 音乐广播接收器
 * */

private class MusicReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //获取广播频道
        String action = intent.getAction();
        //判断厂播频道是否为空
        if (action != null) {
            //根据不同广播频道执行不同操作
            if (INTENT_ACTION_UPDATE_PROGRESS.equals(action)) {
                //获取播放时长
                int duration = intent.getIntExtra(DURATION, 0);
                //获取播放控制图标
                int controlIcon = intent.getIntExtra(CONTROL_ICON, R.drawable.play_button_selector);
                //计算进度值
                int progress = app.getCurrentPosition() * 100 / duration;
                //获取音乐名
                musicName = app.getMusicList().get(app.getCurrentMusicIndex()).getMusicName();
                //设置正在播放的文件名(去掉扩展名)
                tvMusicName.setText("No." + (app.getCurrentMusicIndex() + 1) + " "
                        + musicName.substring(musicName.lastIndexOf("/") + 1, musicName.lastIndexOf(".")));
                //设置播放进度值标签
                tvCurrentPosition.setText(app.getFormatTime(app.getCurrentPosition()));
                //设播放时长标签
                tvDuration.setText(app.getFormatTime(duration));
                //设置播放进度条的进度值
                pbMusicProgress.setProgress(progress);
                //设置[播放|暂停]按钮显示的图标
                btnP1ayOrPause.setBackgroundResource(controlIcon);

            }
        }
    }
}

/**
 *上一首音乐按钮单击事件处理方法
 * @param view
 */
public void doPrevious(View view) {
    // 创建意图
    Intent intent = new Intent();
    //设置广播频道
    intent.setAction( INTENT_ACTION_PREVIOUS);
    //按意图发送广播
    sendBroadcast( intent) ;
}

/**
 *下一首音乐按钮单击事件处理方法
 * @param view
 */
public void doNext(View view) {
    // 创建意图
    Intent intent = new Intent();
    //设置广播频道
    intent.setAction( INTENT_ACTION_NEXT);
    //按意图发送广播
    sendBroadcast(intent) ;
}


/**
 *播放/暂停按钮单击事件处理方法
 *
 @param view
 */
public void doPlayOrPause(View view) {
    //创建意图
    Intent intent = new Intent() ;
    //设置广播频道
    intent . setAction(INTENT_ACTION_PLAY_OR_PAUSE);
    //按意图发途广播
    sendBroadcast (intent);
}


/**
 *销毁回调方法
 */
@Override
protected void onDestroy() {
    super. onDestroy();
    //停止音乐播放服务
    stopService( new Intent(MainActivity. this, MusicPlayService.class));
    //注销广播接收器
    if (receiver != null) {
        unregisterReceiver( receiver);
    }
}

}
27、启动界面类SplashScreenActivity
在这里插入图片描述
package net.tp.sdcard_musicplayer_v06.ui;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;

import net.tp.sdcard_musicplayer_v06.R;

public class SplashScreenActivity extends AppCompatActivity {
private Animation animation;//动画对象
private ImageView ivMusicIcon;//音乐图标图像控件
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 利用布局资源文件设置用户界面
setContentView(R.layout.activity_splash_screen);

    // 通过资源标识获得控件实例
    ivMusicIcon = findViewById(R.id.iv_music_icon);

    // 加载动画资源文件,创建动画对象
    animation = AnimationUtils.loadAnimation(this, R.anim.animator);
    // 让音乐图标图像控件启动动画
    ivMusicIcon.startAnimation(animation);
    // 给动画对象设置监听器
    animation.setAnimationListener(new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {
        }

        @Override
        public void onAnimationEnd(Animation animation) {
            // 启动主界面
            startActivity(new Intent(SplashScreenActivity.this, MainActivity.class));
            // 关闭启动界面
            finish();
        }

        @Override
        public void onAnimationRepeat(Animation animation) {
        }
    });

}

}
四、运行效果
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值