代码已经托管到码云上,有兴趣的小伙伴可以下载看看
https://git.oschina.net/joy_yuan/MobilePlayer
一 EventBus 3.0 ---利用eventbus代替广播来获取音乐的数据。
EventBus是一款针对Android优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。
1、下载EventBus的类库
源码:https://github.com/greenrobot/EventBus
在Android Studio里使用EventBus 的话,只需要在build.gradle里加入下面这句,然后sync一下即可。
compile 'org.greenrobot:eventbus:3.0.0'
2、EventBus 的用法
a 、注册EventBus,在需要订阅eventbus的activity中,注册eventbus即可
如在AudioPlayerActivity中的onCreate里注册
EventBus.getDefault().register(this);
b、取消注册。在onDestroy里取消注册eventbus
EventBus.getDefault.unregister(this);
c、订阅事件
在Activity里订阅事件,当发布者发布相关的事件后,即可在此接收到
这里要注意的是,订阅的方法,一定是public的,然后上面用注解说明订阅事件在哪个线程执行,以及优先级priority,,这个优先级类似有序广播的优先级。
/** * 订阅eventbus */ @Subscribe(threadMode=ThreadMode.MAIN,sticky = false,priority = 99) public void showData(MediaItem item) { showViewData(); checkPlayMode(); }
d、发布事件.
在AudioPlayerService里的准备播放音乐时,发布事件,将要播放的音乐的对象传过去,那么activity里订阅了该信息的即可接受到
/** * 准备好播放时回调 */ class MyOnPreparedListener implements MediaPlayer.OnPreparedListener { @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) @Override public void onPrepared(MediaPlayer mp) { startAudio(); //在这里发送广播,通知activity,播放的进度、音乐名称、歌唱家等信息 // notifyChange(OPENAUDIOPLAYER); //EventBus发布信息 EventBus.getDefault().post(item); } }
二、显示歌词
1、布局修改。
先在音乐播放页面的布局中,添加一个textview控件来显示歌词区域
activity_audioplayer.xml, 这里textview用的是自定义的类,下面会讲解
当前的歌词部分的效果图如下,因为还没有具体的显示的歌词,只是做了个测试的歌词,实际的歌词也是照着这个做的。
2、自定义歌词的实体类
其实根据现有的歌词文件,分析下其构成,可以看到,没行歌词前都有个时间,表示这句歌词在哪个时间点会唱,然后时间点后面是歌词内容
[00:08.17]歌曲名:北京北京 [00:15.00]演唱:汪峰 [00:23.84]www.666cc.com [00:31.16]当我走在这里的每一条街道 [00:37.32]我的心似乎从来都不能平静 [00:45.23]就让花朵妒忌红名和电气致意 [00:51.66]我似乎听到了他这不慢的心跳 [00:59.74]我在这里欢笑我在这里哭泣 [01:06.93]我在这里活着也在这死去 [01:14.09]我在这里祈祷 我在这里迷惘 [01:21.25]我在这里寻找 在这里寻求 [04:11.76][04:04.59][02:31.70][01:27.19]北京 北京
据此我们可以定义歌词类Lyrc.java,其又3个属性,
String content; 歌词内容。
String long timePosition; 歌词显示的时间段
String long sleepTime ; 每句歌词都有一个高亮的时间,这个就是代表此
package com.yuanlp.mobileplayer.bean; /** * Created by 原立鹏 on 2017/7/30. * * 歌词类 * 例如一句歌词 * [02:21.35]我在这里寻找 */ public class Lyrc { //一句歌词由时间点+歌词内容组成 /** * 歌词内容 */ private String content; /** * 时间点 */ private long timePosition; /** * 高亮显示时间 */ private long sleepTime; public long getSleepTime() { return sleepTime; } public void setSleepTime(long sleepTime) { this.sleepTime = sleepTime; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public long getTimePosition() { return timePosition; } public void setTimePosition(long timePosition) { this.timePosition = timePosition; } @Override public String toString() { return "Lyrc{" + "content='" + content + '\'' + ", timePosition=" + timePosition + ", sleepTime=" + sleepTime + '}'; } }
3、自定的歌词显示view,继承自textview。
在这里先定义了一个假的歌词,不是真正的歌曲的歌词,只是用来示例
先定义一个ArrayList
然后通过for循环,往list里插入数据。
for (int i=0;i<1000;i++){ Lyrc lyrc=new Lyrc(); lyrc.setTimePosition(i*1000); lyrc.setContent("aaaaaa"+i); lyrc.setSleepTime(1500+i); lyrcs.add(lyrc); }
数据完成后,开始绘制歌词,在此先定义2个画笔,主要是绘制高亮的歌词的一个画笔,一个绘制其他歌词的画笔,除了颜色不同外,其他都一样。
//创建画笔----当前高亮的画笔 paint=new Paint(); paint.setColor(Color.GREEN); //高亮颜色 paint.setTextSize(20); paint.setAntiAlias(true); //抗锯齿 paint.setTextAlign(Paint.Align.CENTER); //对齐方式,居中显示 //白色画笔 whitepaint=new Paint(); whitepaint.setColor(Color.WHITE); whitepaint.setTextAlign(Paint.Align.CENTER); whitepaint.setTextSize(20); whitepaint.setAntiAlias(true);
在绘制歌词时,先绘制中间的高亮的歌词,因为这个歌词的位置确定时在布局的中间位置;绘制完成高亮的后,在绘制高亮歌词的上边的歌词,下面的歌词,每句歌词高度20.
if (lyrcs!=null&&lyrcs.size()>0){ //先绘制当前歌词 String currentContent=lyrcs.get(index).getContent(); canvas.drawText(currentContent,getWidth()/2,getHeight()/2,paint); //开始绘制该句歌词 //绘制前面部分歌词 int tempY=getHeight()/2; //当前高亮歌词的Y轴坐标 for (int i=index-1;i>=0;i--){ //循环来得到每句上面歌词的Y轴坐标 String preContent=lyrcs.get(i).getContent(); tempY=tempY-textHeight; //循环来得到每句上面歌词的Y轴坐标 if (tempY<0){ //当最上面的一行歌词已经隐藏了,不显示,那么就不再处理 break; } canvas.drawText(preContent,getWidth()/2,tempY,whitepaint); } //绘制后面部分的歌词 tempY=getHeight()/2; for (int i=index+1;igetHeight()){ //超出控件的高度,就不处理 break; } canvas.drawText(nextContent,getWidth()/2,tempY,whitepaint); } }else { canvas.drawText("没有歌词",getWidth()/2,getHeight()/2,paint); }
通过获取当前歌曲播放进度,对比每句歌词的时间戳,来高亮显示哪句歌词
public void setShowNextLyrc(int currentPosition) { this.currentPosition=currentPosition; if (lyrcs==null&&lyrcs.size()==0){ return; }else{ for (int i=1;i=lyrcs.get(tempIndex).getTimePosition()){ //当前正在播放的歌词 index=tempIndex; sleepTime = lyrcs.get(index).getSleepTime(); //歌词的休眠时间,即高亮时间 timePosition = lyrcs.get(index).getTimePosition(); //歌词的时间戳 } } } } //重新绘制,在主线程执行 invalidate(); }
具体的这个类的代码如下:
ShowlyrcView.java
package com.yuanlp.mobileplayer.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; import com.yuanlp.mobileplayer.bean.Lyrc; import java.util.ArrayList; /** * Created by 原立鹏 on 2017/7/30. * * 自定义歌词显示控件 */ public class ShowlyrcView extends android.support.v7.widget.AppCompatTextView { private ArrayListlyrcs; private Paint paint; //当前显示的歌词的画笔 private Paint whitepaint; //白色画笔,用来绘制不是当前高亮的部分 private int width; //控件的宽 private int height; //控件的高 private int index; //当前歌词的索引 private int textHeight=20; //每行歌词的高度 /** * 设置歌词列表 * @param lyrcs */ public void setLyrcs(ArrayList lyrcs){ this.lyrcs=lyrcs; } public ShowlyrcView(Context context) { this(context,null); } public ShowlyrcView(Context context, @Nullable AttributeSet attrs) { this(context, attrs,0); } public ShowlyrcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width=w; height=h; } private void initView() { //创建画笔----当前高亮的画笔 paint=new Paint(); paint.setColor(Color.GREEN); //高亮颜色 paint.setTextSize(20); paint.setAntiAlias(true); //抗锯齿 paint.setTextAlign(Paint.Align.CENTER); //对齐方式,居中显示 //白色画笔 whitepaint=new Paint(); whitepaint.setColor(Color.WHITE); whitepaint.setTextAlign(Paint.Align.CENTER); whitepaint.setTextSize(20); whitepaint.setAntiAlias(true); /** * 暂时先设置一个假的歌词列表 */ lyrcs=new ArrayList<>(); for (int i=0;i<1000;i++){ Lyrc lyrc=new Lyrc(); lyrc.setTimePosition(i*1000); lyrc.setContent("aaaaaa"+i); lyrc.setSleepTime(1500+i); lyrcs.add(lyrc); } } //绘制歌词 @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (lyrcs!=null&&lyrcs.size()>0){ //先绘制当前歌词 String currentContent=lyrcs.get(index).getContent(); canvas.drawText(currentContent,getWidth()/2,getHeight()/2,paint); //绘制前面部分歌词 int tempY=getHeight()/2; //当前高亮歌词的Y轴坐标 for (int i=index-1;i>=0;i--){ String preContent=lyrcs.get(i).getContent(); tempY=tempY-textHeight; if (tempY<0){ //当最上面的一行歌词已经隐藏了,不显示,那么就不再处理 break; } canvas.drawText(preContent,getWidth()/2,tempY,whitepaint); } //绘制后面部分的歌词 tempY=getHeight()/2; for (int i=index+1;i getHeight()){ //超出控件的高度,就不处理 break; } canvas.drawText(nextContent,getWidth()/2,tempY,whitepaint); } }else { canvas.drawText("没有歌词",getWidth()/2,getHeight()/2,paint); } } public void setShowNextLyrc(int currentPosition) { this.currentPosition=currentPosition; if (lyrcs==null&&lyrcs.size()==0){ return; }else{ for (int i=1;i =lyrcs.get(tempIndex).getTimePosition()){ //当前正在播放的歌词 index=tempIndex; sleepTime = lyrcs.get(index).getSleepTime(); timePosition = lyrcs.get(index).getTimePosition(); } } } } //重新绘制,在主线程执行 invalidate(); } }
四、更新歌词
在AudioPlayerActivity中的接收到eventbus的订阅事件里,发一个handler消息,来更新歌词
/** * 订阅eventbus */ @Subscribe(threadMode=ThreadMode.MAIN,sticky = false,priority = 99) public void showData(MediaItem item) { handler.sendEmptyMessage(SHOW_LYRC); showViewData(); checkPlayMode(); }
然后在handler里来处理更新歌词
case SHOW_LYRC: //1、获取当前进度 try { int currentPosition=mservice.getCurrentPosition(); //2、根据当前进度,获取歌词 showlyrcView.setShowNextLyrc(currentPosition); //3、实时发消息去更新歌词 handler.removeMessages(SHOW_LYRC); handler.sendEmptyMessage(SHOW_LYRC); } catch (RemoteException e) { e.printStackTrace(); } break;