MP3播放器(安卓应用开发)
如果我们有着自己的MP3音乐库,在Android设备中使用商用媒体播放器进行播放,对于应用的权限和广告等问题,相信感觉不会太好。
好消息是,开发一个自己的安卓版MP3播放器并不是一项太难的工作。
准备工作和开发约定
我们使用的是开发环境是Android Studio,可以在https://developer.android.google.cn/studio下载最新的版本。
创建一个新的项目,并指定类型为空白的Android应用项目,如下图。
创建新Android项目
本文中,将使用Java开发语言和Android 9平台,项目名称定义为AudioPlayer,项目包的名称使用com.caohuayu.audioplayer,其中,com.caohuayu是作者域名的反向定义,如果大家有自己的域名,也可以使用相应的反向域名。新建项目参数设置如下图,项目保存位置(Save location)可以根据自己电脑的实际情况决定。
项目参数
由于应用的功能比较简单,我们只需要在AndroidManifest.xml配置文件中声明所需的存储读写权限,如下面的代码。
XML |
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
实际上,本文中应用功能只需要读取文件的权限,即读取文件并实现基本的播放和循环功能。如果扩展功能以便保存一些数据时就需要使用写入权限,比如保存播放清单、当前播放的曲目,以及播放曲目的进度等。
布局设计
本应用的功能只限于播放设备中指定位置存放的全部MP3文件,可以执行播放/停止操作,以及全部循环、单曲循环和随机播放功能。
应用的功能比较简单,只需要一个界面,我们使用默认的Activity,默认的布局文件为activity_main.xml,定义内容如下。
XML |
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="50dp" >
<Button android:id="@+id/btnSingle"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/single"
android:textAllCaps="false"
android:layout_width="wrap_content"></Button>
<Button android:id="@+id/btnRandom"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/random"
android:textAllCaps="false"
android:layout_width="wrap_content"></Button>
<Button android:id="@+id/btnRepeat"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/repeat"
android:textAllCaps="false"
android:layout_width="wrap_content"></Button>
<Button android:id="@+id/btnPlay"
android:layout_height="50dp"
android:layout_weight="1"
android:text="@string/play"
android:textAllCaps="false"
android:layout_width="wrap_content"></Button>
</LinearLayout>
<TextView android:id="@+id/txtMsg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"></TextView>
<ListView android:id="@+id/lst1"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:listSelector="#CCCCCC"></ListView>
</LinearLayout>
|
在MainActivity.java代码文件中加载此布局,默认的界面如下图。
英文版界面
在中文版的真实的安卓设备中显示如下图。
中文版界面
四个按钮(Button)分别是:
- Single(单曲),单曲循环按钮。
- Random(随机),随机播放按钮。
- Repeat(循环),全部循环按钮。
- Play(播放/停止),播放与停止按钮。
TextView控件的ID为txtMsg,用于显示必要的提示信息。
ListView控件的ID为lst1,用于显示播放列表。
功能实现
主界面的功能实现代码位于MainActivity.java文件,首先来看一些初始代码。
Java |
package com.caohuayu.audioplayer;
import androidx.appcompat.app.AppCompatActivity;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.util.ArrayList;
import java.util.Random;
public class MainActivity extends AppCompatActivity
implements View.OnClickListener, AdapterView.OnItemClickListener,
MediaPlayer.OnCompletionListener{
// 控件对象
private Button btnSingle;
private Button btnRandom;
private Button btnRepeat;
private Button btnPlay;
private ListView lst1;
private TextView txtMsg;
// 完整的文件名数组
private String[] filenames;
// 播放列表显示名称,只显示文件基本名称
private String[] shownames;
// 播放组件
private MediaPlayer player = new MediaPlayer();
// 当前播放索引值
private int currentIndex=-1;
// 循环方法,1单曲,2随机,3全部循环
private int repeatState=3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 对象初始化
btnSingle=(Button)findViewById(R.id.btnSingle);
btnRandom=(Button)findViewById(R.id.btnRandom);
btnRepeat=(Button)findViewById(R.id.btnRepeat);
btnPlay=(Button)findViewById(R.id.btnPlay);
lst1=(ListView)findViewById(R.id.lst1);
txtMsg=(TextView)findViewById(R.id.txtMsg);
txtMsg.setText(R.string.stop);
// 设置事件侦听
btnSingle.setOnClickListener(this);
btnRandom.setOnClickListener(this);
btnRepeat.setOnClickListener(this);
btnPlay.setOnClickListener(this);
lst1.setOnItemClickListener(this);
player.setOnCompletionListener(this);
// 循环状态按钮颜色
setButtonColor(btnSingle,false);
setButtonColor(btnRandom,false);
setButtonColor(btnRepeat,true);
// 载入播放列表
loadPlayList();
}
@Override
protected void onDestroy(){
player.stop();
player.release();
player=null;
filenames=null;
shownames=null;
super.onDestroy();
}
// 其它代码…
}
|
代码中,MainActivity类实现了View.OnClickListener、AdapterView.OnItemClickListener和MediaPlayer.OnCompletionListener事件,分别用于响应按钮的单击操作、列表项的单击操作,以及媒体播放后的响应操作。
MainActivity类中也定义了一些必要的对象和变量,并重写了Activity的onCreate()和onDestroy()方法,分别用于对象的初始化和释放。
在onCreate()方法中,除了一些基本的初始化操作,还调用了一些自定义方法,如:
- setButtonColor()方法,设置按钮的颜色,包括两种状态。第一个参数指定按钮对象,第二个参数设置为true时,设置为当前操作按钮颜色,设置为false时设置为非当前操作按钮操作。
- loadPlayList()方法,用于从指定的位置读取并载入MP3播放列表。
首先来看setButtonColor()方法的实现,如下面的代码。
Java |
// 设置按钮状态
private void setButtonColor(Button btn,boolean isCur){
if(isCur){
btn.setBackgroundColor(getColor(R.color.purple_500));
btn.setTextColor(getColor(R.color.white));
}else{
btn.setBackgroundColor(getColor(R.color.white));
btn.setTextColor(getColor(R.color.black));
}
}
|
接下来是loadPlayList()方法的实现,如下面的代码。
Java |
// 载入列表
private void loadPlayList(){
File dir = new File(getExternalFilesDir(null),"");
//
File[] files = dir.listFiles();
if(files==null){
Toast.makeText(MainActivity.this,
"请在Android\\data\\com.caohuayu.audioplayer\\files文件夹准备.mp3文件",
Toast.LENGTH_LONG).show();
return;
}
ArrayList<File>fileList = new ArrayList<File>();
for(File f:files){
if(f.getName().toLowerCase().endsWith(".mp3"))
fileList.add(f);
}
//生成播放列表和显示列表
int size = fileList.size();
if(size>0){
filenames=new String[size];
shownames=new String[size];
for(int i=0;i<size;i++){
filenames[i] = fileList.get(i).getPath();
String s = fileList.get(i).getName();
shownames[i] = s.substring(0,s.length()-4);
}
// 绑定列表
ArrayAdapter<String> ada=new ArrayAdapter<String>(
MainActivity.this,android.R.layout.simple_list_item_1,shownames);
lst1.setAdapter(ada);
// 准备播放器
try {
currentIndex = 0;
//
player.setDataSource(filenames[currentIndex]);
player.prepare();
Toast.makeText(MainActivity.this,
"音乐已准备",Toast.LENGTH_LONG).show();
}catch (Exception ex){
ex.printStackTrace();
}
}else{
shownames=null;
player.release();;
Toast.makeText(MainActivity.this,
"请在Android\\data\\com.caohuayu.audioplayer\\files文件夹准备.mp3文件",
Toast.LENGTH_LONG).show();
}
}
|
loadPlayList()方法中,我们约定MP3文件保存在安卓设备的Android/data/com.caohuayu.audioplayer/files文件夹中,测试时,需要大家手工将自己喜爱的MP3复制到此文件夹。请注意,在Android/data文件夹中应找到应用的包名,这里是com.caohuayu.audioplayer,如果在创建应用时使用了不同的包名称,应在这里找到相应的文件夹。
在读取文件夹中的MP3文件后,会将完整的文件路径保存到filenames数组中,而基础的文件名(不包括扩展名)会保存在shownames数组中,并显示在播放列表中。
MainActivity类中的另外一个辅助方法是play(),用于开始播放列表中指定索引位置的文件,方法的实现如下。
Java |
// 播放指定索引
private void play(int index) {
try{
currentIndex = index;
player.reset();
player.setDataSource(filenames[currentIndex]);
player.prepare();
player.start();
// 选中播放项
btnPlay.setText(R.string.stop);
txtMsg.setText("Playing : "+shownames[currentIndex]);
}catch(Exception ex){
ex.printStackTrace();
}
}
|
当调用loadPlayList()方法载入播放列表成功后,就可以点击播放按钮开始播放,四个按钮的响应代码使用onClick()方法实现,如下面的代码。
Java |
// 按钮操作响应
@Override
public void onClick(View v) {
int vid = v.getId();
if(vid==R.id.btnPlay){
if(filenames==null || filenames.length==0){
loadPlayList();
}else{
if(player.isPlaying()){
player.pause();
btnPlay.setText(R.string.play);
txtMsg.setText(R.string.stop);
}else{
player.start();
btnPlay.setText(R.string.stop);
txtMsg.setText("Playing : "+shownames[currentIndex]);
}
}
}else if(vid==R.id.btnSingle){
repeatState = 1;
setButtonColor(btnSingle,true);
setButtonColor(btnRandom,false);
setButtonColor(btnRepeat,false);
}else if(vid==R.id.btnRandom){
repeatState = 2;
setButtonColor(btnSingle,false);
setButtonColor(btnRandom,true);
setButtonColor(btnRepeat,false);
}else if(vid==R.id.btnRepeat){
repeatState = 3;
setButtonColor(btnSingle,false);
setButtonColor(btnRandom,false);
setButtonColor(btnRepeat,true);
}
}
|
当一首曲目播放完成后,会调用onCompletion()方法,如下面的代码。
Java |
// 播放器状态
@Override
public void onCompletion(MediaPlayer mp){
if(repeatState==1){
// 单曲
play(currentIndex);
}else if(repeatState==2){
// 随机
play(new Random().nextInt(shownames.length));
}else{
// 循环
int index = currentIndex+1;
if(index>=shownames.length) index=0;
play(index);
}
}
|
此时,会根据repeatState变量判断循环模式,并开始下一首MP3的播放。
最后是点击列表项响应操作,如下面的代码。
Java |
// 列表项点击响应
@Override
public void onItemClick(AdapterView<?> parent,
View view, int position, long id) {
play((int)id);
}
|
相关资源
应用中使用的颜色资源定义在/res/values/colors.xml文件,如下面的代码。
XML |
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
|
相关的文本定义在/res/values/strings.xml,如下面的代码。
XML |
<resources>
<string name="app_name">AudioPlayer</string>
<string name="single">Single</string>
<string name="random">Random</string>
<string name="repeat">Repeat</string>
<string name="play">Play</string>
<string name="stop">Stop</string>
</resources>
|
相应的中文内容定义在/res/values-zh/strings.xml文件,可以在Projects查看模式下创建,文件内容定义如下。
XML |
<resources>
<string name="app_name">AudioPlayer</string>
<string name="single">单曲</string>
<string name="random">随机</string>
<string name="repeat">循环</string>
<string name="play">播放</string>
<string name="stop">停止</string>
</resources>
|
其它
附件中包含了可安装的.apk文件,请注意安装后将mp3文件复制到设备的Android/data/com.caohuayu.audioplayer/files目录中。
扩展资源中包含完整的源代码!