代码已经托管到码云上,有兴趣的小伙伴可以下载看看

        

 一 EventBus 3.0   ---利用eventbus代替广播来获取音乐的数据。

    EventBus是一款针对优化的发布/订阅事件总线。主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。

        1、下载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<Lyrc> lyrcs;

        然后通过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;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();}

具体的这个类的代码如下:

    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 ArrayList
 lyrcs;    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;