大家好,又见面了,我是你们的朋友全栈君。
这次介绍一个多功能音乐播放器,记得是大二那年寒假写的,实现的主要功能就是音乐播放,带进度条控制,扫描本地音乐,上一曲下一曲,播放类型(单曲循环,顺序播放,随机播放),APP主题换肤,背景图更换等,功能都比较基础,基本上如果你不会的话,跟着我的思路,应该都是能实现的,预计会在以后加入歌词的功能。
在开始前,先放一张最后的效果图吧,我个人喜欢的风格,简约,美观。
目录
1.实现扫描本地音乐
2.音乐的播放与控制
3.关联进度条seekbar,自定义seekbar
4.单曲循环,顺序播放,随机播放的实现
5.设置喜爱音乐
6.播放列表背景图设置与保存
7.实现APP主题换肤的功能
正文
1.实现扫描本地音乐
这里为了将每个系统里面存放的音乐抽象出来,也是为了方便管理,先定义一个音乐类Song,代码如下
public class Song {
/** * 歌手 */
private String singer;
/** * 歌曲名 */
private String song;
/** * 歌曲的地址 */
private String path;
/** * 歌曲长度 */
private int duration;
/** * 歌曲的大小 */
private long size;
public String getSinger() {
return singer;
}
public void setSinger(String singer) {
this.singer = singer;
}
public String getSong() {
return song;
}
public void setSong(String song) {
this.song = song;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public int getDuration() {
return duration;
}
public void setDuration(int duration) {
this.duration = duration;
}
public long getSize() {
return size;
}
public void setSize(long size) {
this.size = size;
}
}
然后我们再写一个工具类,这个工具类实现的功能就是扫描系统中的本地音乐,返回一个List<Song>集合,供我们使用,代码如下
public class MusicUtils {
/**
* 扫描系统里面的音频文件,返回一个list集合
*/
public static List<Song> getMusicData(Context context) {
List<Song> list = new ArrayList<>();
Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
MediaStore.Audio.AudioColumns.IS_MUSIC);
if (cursor != null) {
while (cursor.moveToNext()) {
Song song = new Song();
song.setSong( cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)));
song.setSinger( cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)));
song.setPath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)));
song.setDuration( cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)));
song.setSize( cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)));
if (song.getSize() > 1000 * 800) {//过滤掉短音频
// 分离出歌曲名和歌手
if (song.getSong().contains("-")) {
String[] str = song.getSong().split("-");
song.setSinger( str[0]);
song.setSong( str[1]);
}
list.add(song);
}
}
// 释放资源
cursor.close();
}
return list;
}
//格式化时间
public static String formatTime(int time) {
if (time / 1000 % 60 < 10) {
return time / 1000 / 60 + ":0" + time / 1000 % 60;
} else {
return time / 1000 / 60 + ":" + time / 1000 % 60;
}
}
}
然后,在布局里定义一个Listview,再给Listview写一个适配器,一般继承自BaseAdapter,adapter代码如下
public class MyAdapter extends BaseAdapter {
private Context context;
private List<Song> list;
private int position_flag = 0;
public MyAdapter(MainActivity mainActivity, List<Song> list) {
this.context = mainActivity;
this.list = list;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int i) {
return list.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder = null;
if (view == null) {
holder = new ViewHolder();
// 引入布局
view = View.inflate(context, R.layout.list_item, null);
// 实例化对象
holder.song = (TextView) view.findViewById(R.id.item_mymusic_song);
holder.singer = (TextView) view
.findViewById(R.id.item_mymusic_singer);
holder.duration = (TextView) view
.findViewById(R.id.item_mymusic_duration);
holder.position = (TextView) view
.findViewById(R.id.item_mymusic_postion);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
// 给控件赋值
String string_song = list.get(i).getSong();
if (string_song.length() >= 5
&& string_song.substring(string_song.length() - 4,
string_song.length()).equals(".mp3")) {
holder.song.setText(string_song.substring(0,
string_song.length() - 4).trim());
} else {
holder.song.setText(string_song.trim());
}
holder.singer.setText(list.get(i).getSinger().toString().trim());
// 时间转换为时分秒
int duration = list.get(i).getDuration();
String time = MusicUtils.formatTime(duration);
holder.duration.setText(time);
return view;
}
class ViewHolder {
TextView song;// 歌曲名
TextView singer;// 歌手
TextView duration;// 时长
TextView position;// 序号
}
}
adapter里面的列表项布局代码如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="horizontal">
<TextView
android:id="@+id/item_mymusic_postion"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignBottom="@+id/item_mymusic_singer"
android:layout_gravity="center_vertical"
android:gravity="center"
android:layout_margin="10dp"
android:text="1"
android:textSize="18sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:id="@+id/item_mymusic_song"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginRight="10dp"
android:layout_marginTop="5dp"
android:text="歌曲名"
android:textSize="18sp" />
<TextView
android:id="@+id/item_mymusic_singer"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@+id/item_mymusic_duration"
android:gravity="bottom"
android:text="歌手"
android:ellipsize="end"
android:maxLines="1"
android:layout_marginBottom="5dp"
android:textSize="16sp" />
<TextView
android:id="@+id/item_mymusic_duration"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:gravity="bottom"
android:layout_marginRight="5dp"
android:layout_marginBottom="5dp"
android:text="歌曲时间"
android:textSize="16sp" />
</RelativeLayout>
</LinearLayout>
然后在Listview所在的activity里,调用工具类获取音乐集合,构造适配器,给Listview设置适配器,即可在Listview中显示本地所有的音乐啦,关键代码就三行,如下
List<Song> list = MusicUtils.getMusicData(MainActivity.this);
MyAdapter adapter = new MyAdapter(MainActivity.this, list);
listview.setAdapter(adapter);
好了,到现在为止,你已经实现了,显示手机里所有的音乐,但是还不能播放,怎么播放,接着往下看
2.音乐的播放与控制
实现音乐播放,需要用到的类为MediaPlayer,为了方便,封装一个播放音乐的方法,如下
private void musicplay(int position) {
try {
mplayer.reset();
mplayer.setDataSource(list.get(position).getPath());
mplayer.prepare();
mplayer.start();
} catch (Exception e) {
e.printStackTrace();
}
}
传入的position为,播放的音乐的位置,即序号。
然后给listview设置点击事件
listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
musicplay(currentposition);
}
});
这样我们只是实现了简单的播放,点击Listview对应的条目,即可播放对应的音乐
我们下一步就是实现,音乐播放的控制,即暂停,下一曲,上一曲的实现
首先是暂停,在播放按钮的点击时间中,我们通常的需求是这样的,如果当前音乐正在播放,那么点击,暂停音乐,再点击,即可再次接着上次的继续播放,所以在播放按钮的点击事件中,需要根据不同情况处理,同时为了直观,需要准备两张图片,播放的时候一张,暂停的时候一张,播放按钮的点击事件如下
imageView_play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mplayer.isPlaying()) {
mplayer.pause();
imageview.clearAnimation();
} else {
mplayer.start();
// thread = new Thread(new SeekBarThread());
// thread.start();
imageview.startAnimation(AnimationUtils.loadAnimation(
MainActivity.this, R.anim.imageview_rotate));
}
}
});
由于为了界面体验良好,我这里还设置了,当音乐播放的时候,左侧图片的旋转效果,代码已经在上面的点击事件中,效果图如下
左侧imageview的动画代码如下
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="20000"
android:fromDegrees="0"
android:interpolator="@android:anim/linear_interpolator"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="-1"
android:repeatMode="restart"
android:toDegrees="360" />
扯了点其他的,下面来实现上一曲和下一曲的效果,我们也可以和播放一个,分别写一个对应的方法
上一曲方法代码如下
// 上一曲
private void frontMusic() {
currentposition--;
if (currentposition < 0) {
currentposition = list.size() - 1;
}
musicplay(currentposition);
}
其中,currentposition是记录的当前音乐播放序号,这里有一点需要考虑的是,当前播放音乐的序号为0的时候,进行–操作之后那么会变成负数,所以,这里根据逻辑,处理为播放列表最后一曲,即设置序号为list.size()-1,形成一个环形。
相信你看了上一曲的方法,那么下一曲也很简单了,下一曲方法代码如下
// 下一曲
private void nextMusic() {
currentposition++;
if (currentposition > list.size() - 1) {
currentposition = 0;
}
musicplay(currentposition);
}
同样我们也需要处理播放歌曲到最后一曲的时候,设置为播放列表第一首歌曲。
3.关联进度条seekbar,自定义seekbar
关联进度条的方法也很简单,这里将更新seekbar的方法重新开了一个线程,专门处理更新,代码如下
// 自定义的线程,用于下方seekbar的刷新
class SeekBarThread implements Runnable {
@Override
public void run() {
while (!ischanging && mplayer.isPlaying()) {
// 将SeekBar位置设置到当前播放位置
seekBar.setProgress(mplayer.getCurrentPosition());
try {
// 每500毫秒更新一次位置
Thread.sleep(500);
// 播放进度
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
其中,ischanging用于判断当前的seekbar是否处于滑动状态,然后在音乐播放的地方,也就是刚才封装的musicplay方法中,更改为如下代码
private void musicplay(int position) {
seekBar.setMax(list.get(position).getDuration());
imageview.startAnimation(AnimationUtils.loadAnimation(
MainActivity.this, R.anim.imageview_rotate));
try {
mplayer.reset();
mplayer.setDataSource(list.get(position).getPath());
mplayer.prepare();
mplayer.start();
} catch (Exception e) {
e.printStackTrace();
}
thread = new Thread(new SeekBarThread());
thread.start();
}
当然,不要忘了先设置seekbar的最大刻度值,也就是上面代码中setMax方法。
至此,你的音乐播放就已经和seekbar进度条关联起来了,但是你可能会发现系统默认的进度条很丑,不符合你的审美,那么我们就需要更改seekbar的样式,也就是自定义seekbar。
自定义seekbar,需要在布局中设置progressDrawable和thumb,分别对应进度条的背景和进度条的指示小图标,我这里进度条的背景采用的是drawable,代码如下
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/background">
<shape>
<solid
android:color="#DCDCDC"/>
</shape>
</item>
<item android:id="@+id/secondaryProgress">
<clip>
<shape>
<solid android:color="@color/blue" />
</shape>
</clip>
</item>
<item android:id="@+id/progress">
<clip>
<shape>
<solid android:color="@color/blue" />
</shape>
</clip>
</item>
</layer-list>
而thumb,我这里使用的就是一张图片,可以在我的项目源代码中找到,图片长下面这个样子
当然你也可以采用自己的图片,来实现炫酷的效果哦!
4.单曲循环,顺序播放,随机播放的实现
实现这个效果,首先我哦们定义一个变量,用于记录当前的播放类型是哪种,如下
// 用于判断当前的播放顺序,0->单曲循环,1->顺序播放,2->随机播放
private int play_style = 0;
然后在我们的更改播放类型的按钮点击事件中,更改它的值,点击事件代码如下
imageview_playstyle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
play_style++;
if (play_style > 2) {
play_style = 0;
}
switch (play_style) {
case 0:
imageview_playstyle.setImageResource(R.mipmap.cicle);
Toast.makeText(MainActivity.this, "单曲循环",
Toast.LENGTH_SHORT).show();
break;
case 1:
imageview_playstyle.setImageResource(R.mipmap.ordered);
Toast.makeText(MainActivity.this, "顺序播放",
Toast.LENGTH_SHORT).show();
break;
case 2:
imageview_playstyle.setImageResource(R.mipmap.unordered);
Toast.makeText(MainActivity.this, "随机播放",
Toast.LENGTH_SHORT).show();
break;
}
}
});
逻辑比较简单,应该都能看懂,然后就是怎么根据这个变量来实现对应的效果,核心方法就是MediaPLayer的setOnCompeleteListener,代码如下
// 监听mediaplayer播放完毕时调用
mplayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
// TODO Auto-generated method stub
switch (play_style) {
case 0:
musicplay(currentposition);
break;
case 1:
nextMusic();
break;
case 2:
random_nextMusic();
break;
default:
break;
}
}
});
下一曲的代码上面已经给出了,下面是随机播放下一曲的代码,思想很简单,就是生成一个随机数,再设置为currentpositon,然后调用musicplay方法即可
// 随机播放下一曲
private void random_nextMusic() {
currentposition = currentposition + random.nextInt(list.size() - 1);
currentposition %= list.size();
musicplay(currentposition);
}
5.设置喜爱音乐
喜爱音乐的设置,我这里处理的比较简单, 当长按列表项的时候,弹出对话框,用于设置喜爱音乐,效果如下
然后,用sharepreference记录下喜爱音乐的序号值,当要播放喜爱音乐的时候,直接取到该序号值,然后调用musicplay方法播放序号值对应的音乐即可。主要就是sharepreference的使用,代码很简单,就不贴了
6.播放列表背景图设置与保存
设置播放列表背景也就是调用一下,listview.setBackground即可,但是我们如果不进行保存的话,下次进入APP的时候,背景图可能又恢复为初始的,那么我们就需要保存列表ode背景图,这里也采用sharepreference来保存,首先用Base64将图片转换为String,然后保存起来,下次进入APP的时候,再取出来,用Base64将String转为drawable对象,在设置上去即可。相关代码如下。
// 使用sharedPreferences保存listview背景图片
private void saveDrawable(Drawable drawable) {
SharedPreferences.Editor editor = sharedPreferences.edit();
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
// Bitmap bitmap = BitmapFactory.decodeResource(getResources(), id);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos);
String imageBase64 = new String(Base64.encodeToString(
baos.toByteArray(), Base64.DEFAULT));
editor.putString("listbg", imageBase64);
editor.commit();
}
// 加载用sharedPreferences保存的图片
private Drawable loadDrawable() {
String temp = sharedPreferences.getString("listbg", "");
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(
temp.getBytes(), Base64.DEFAULT));
return Drawable.createFromStream(bais, "");
}
7.实现APP主题换肤的功能
实现主题效果,有很多种方法,我这里采用的是自定义属性的方法,首先我们在values下新建一个文件attrs,内容如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="theme_color" format="color" />
<attr name="popupwindow_bg" format="reference"/>
<attr name="dialogactivity_bg" format="reference"/>
<attr name="btn_submit_bg" format="reference"/>
<attr name="seekbar_progress_bg" format="reference"/>
<attr name="play_image" format="reference" />
<attr name="next_image" format="reference" />
<attr name="front_image" format="reference" />
<attr name="thumb_image" format="reference" />
<attr name="indicate_image" format="reference" />
</resources>
这里每一个attr属性代表了哪些内容需要根据主题不同而更换,比如popupwindow_bg,即弹出窗口的背景色等等,然后在styles文件文件中指定各个主题下,这些值分别对应哪个具体的值,styles中相关代码如下
<style name="Theme_blue">
<item name="theme_color">@color/blue</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg</item>
<item name="play_image">@mipmap/play</item>
<item name="next_image">@mipmap/next</item>
<item name="front_image">@mipmap/front</item>
<item name="thumb_image">@mipmap/seekbar_thumb</item>
<item name="indicate_image">@mipmap/play_small</item>
</style>
<style name="Theme_purple">
<item name="theme_color">@color/purple</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg_purple</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg_purple</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg_purple</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg_purple</item>
<item name="play_image">@mipmap/play_purple</item>
<item name="next_image">@mipmap/next_purple</item>
<item name="front_image">@mipmap/front_purple</item>
<item name="thumb_image">@mipmap/seekbar_thumb_purple</item>
<item name="indicate_image">@mipmap/play_small_purple</item>
</style>
<style name="Theme_green">
<item name="theme_color">@color/green</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg_green</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg_green</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg_green</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg_green</item>
<item name="play_image">@mipmap/play_green</item>
<item name="next_image">@mipmap/next_green</item>
<item name="front_image">@mipmap/front_green</item>
<item name="thumb_image">@mipmap/seekbar_thumb_green</item>
<item name="indicate_image">@mipmap/play_small_green</item>
</style>
<style name="Theme_red">
<item name="theme_color">@color/red</item>
<item name="popupwindow_bg">@drawable/popupwindow_bg_red</item>
<item name="dialogactivity_bg">@drawable/dialogactivity_bg_red</item>
<item name="btn_submit_bg">@drawable/btn_submit_bg_red</item>
<item name="seekbar_progress_bg">@drawable/seekbar_progress_bg_red</item>
<item name="play_image">@mipmap/play_red</item>
<item name="next_image">@mipmap/next_red</item>
<item name="front_image">@mipmap/front_red</item>
<item name="thumb_image">@mipmap/seekbar_thumb_red</item>
<item name="indicate_image">@mipmap/play_small_red</item>
</style>
可以很清楚的看到,我设置了四个主题,每个主题中,我都对attrs中定义的属性进行了具体的赋值,然后怎么使用呢,举个例子,比如我现在需要让popupwindow的背景色随主题改变而更换,那么在popupwindow的布局中,设置其background属性为如下即可
android:background="?attr/popupwindow_bg"
其他属性的使用方法同理,然后我们如何来让用户设置主题呢,可以写一个dialog,也可popupwindow,不过我这里为了学习一下样式为dialog的activity,便采用了这种方式,最后效果如下
看上去就像一个dialog,其实是一个activity,然后在这里根据用户的选择,来设置不同的主题,然后拿到主题的类型之后,在代码中根据这个值去判断应该显示哪个主题,相关代码如下
// 主题设置
string_theme = sharedPreferences.getString("theme_select", "blue");
if (string_theme.equals("blue")) {
setTheme(R.style.Theme_blue);
} else if (string_theme.equals("purple")) {
setTheme(R.style.Theme_purple);
} else if (string_theme.equals("green")) {
setTheme(R.style.Theme_green);
} else {
setTheme(R.style.Theme_red);
}
setContentView(R.layout.activity_main);
记住一定要在setContentView方法之前调用,具体细节各方面由于代码比较散,不方便贴,可以去源码里看我是怎么设置的,最终四个主题下的主界面效果如下
当然这个APP里,还有很多其他的细节,诸如,控制当前播放的列表项为不同颜色,顶部显示歌曲名字的彩色TextView等,这些可以直接去看源码,实现的方法也不难,欢迎访问源码!!
源码下载
由于考虑到大家可能没有积分,我把源码重新传到了百度云,这样大家可以免费下载学习,链接和提取码如下:
链接: https://pan.baidu.com/s/1KNxJvsE6XTIi3JkEBgCNgw 提取码: 4xhi
任何问题,也可以加我vx交流:hqq_0711
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/153231.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...