hunterliy小作品之 HunterMusic音乐播放器(Day4-歌词显示实现)

来源:互联网 发布:音频降噪软件 编辑:程序博客网 时间:2024/04/27 15:48

歌词显示实现

1.绘制单行居中文本

自定义一个显示歌词的 LryicView,歌词本身就是一个文本,所以在这里我们继承 TextView,它还有一个好处继承 TextView 之后不需要再去重写 onMeasure 方法,在 onDraw 方法中去绘制一个文本

LryicView.java

package com.itheima.medioplayer.ui.view;    import android.content.Context;    import android.graphics.Canvas;    import android.graphics.Color;    import android.graphics.Paint;    import android.graphics.Rect;    import android.util.AttributeSet;    import android.widget.TextView;    import com.itheima.medioplayer.R;    public class LryicView extends TextView {    private float hightlightSize;    private float normalSize;    private int hightLightColor;    private int normalColor;    private Paint paint;    public LryicView(Context context) {    super(context);    initView();    }    public LryicView(Context context, AttributeSet attrs) {    super(context, attrs);    initView();    }    public LryicView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    initView();    }    private void initView() {    hightlightSize = getResources().getDimension(R.dimen.lryic_hightlight_size);    normalSize = getResources().getDimension(R.dimen.lryic_normal_size);    hightLightColor = Color.GREEN;    normalColor = Color.WHITE;    paint = new Paint();    paint.setAntiAlias(true);//抗锯齿    paint.setTextSize(hightlightSize);    paint.setColor(hightLightColor);    }    @Override    protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    String text="正在加载歌词...";    canvas.drawText(text,0,0, paint);    }    }

在项目中的歌词布局中引用 View,重新 build即可以显示,但是文本显示的坐标是 view 的左上角,那么我们需要将文本显示的位置设置在 view 的中间,下面描述下怎么居中。

单行文本居中:X=View一半宽度-文本一半宽度, Y=View一半高度+文本一半高度,X、Y分别为宽和高

在onSizeChang中计算出View宽和高的一半, 通过paint.getTextBounds方法计算出文本的宽高的一半

@Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);         halfViewW = w/2;         halfViewH = h/2;    }@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        String text="正在加载歌词...";        Rect bounds=new Rect();        //计算 text 的宽和高        int halfTextW=bounds.width()/2;        int halfTextH=bounds.height()/2;        //计算 text 位置        int drawX=halfViewW-halfTextW;        int drawY=halfTextH+halfViewH;        canvas.drawText(text,drawX,drawY, paint);    }

1.2 绘制多行歌词

1.首先用 List 模拟歌词的数据并且记录高亮行的行数

private void initView() {        hightlightSize = getResources().getDimension(R.dimen.lryic_hightlight_size);        normalSize = getResources().getDimension(R.dimen.lryic_normal_size);        hightLightColor = Color.GREEN;        normalColor = Color.WHITE;        paint = new Paint();        paint.setAntiAlias(true);//抗锯齿        paint.setTextSize(hightlightSize);        paint.setColor(hightLightColor);        //高亮的行数        currentLine = 5;        //模拟初始化数据        lryics = new ArrayList<>();        for (int i = 0; i <30 ; i++) {        lryics.add(new Lryic(i * 2000, "当前正在播放行数为:"+i));        }        }

2.获取高亮行的位置

        /** 绘制多行文本*/        private void drawMutiLineText(Canvas canvas) {            Lryic lryic=lryics.get(currentLine);            //获取高亮行 Y 的位置            Rect bounds=new Rect();            //计算 text 的宽和高    paint.getTextBounds(lryic.getContent(),0,lryic.getContent().length(),bounds);            int halfTextW=bounds.width()/2;            int halfTextH=bounds.height()/2;            int centerY=halfTextH+halfViewH;        }

3.按行绘制文本

for (int i = 0; i < lryics.size(); i++) {        if (currentLine==i){        paint.setColor(hightLightColor);        paint.setTextSize(hightlightSize);    }else{        paint.setColor(normalColor);        paint.setTextSize(normalSize);    }

4.y=居中行 y 的位置+(绘制行的位置-高亮行的行数)*行高

lineHeight=getResources().getDimensionPixelSize(R.dimen.lryic_line_height);//y=居中行 Y 的位置+(绘制行的行数-高亮行的行数)*行号int downY=centerY+(i-currentLine)*lineHeight;

5.x=水平居中的 x

drawHorizontalText(canvas,lryics.get(i).getContent(),downY);

1.3按行滚动歌词

1.在 LryicView 中提供一个滚动歌词的方法,说白了其实只要设置歌词高亮的位置就可以了。
高亮行:起始时间<=播放时间,下一行起始时间>播放时间

public void roll(int position,int duration){        int endPoint;        for (int i =1;i<lyrics.size();i++){            Lyric lyric = lyrics.get(i);            if (i==lyrics.size()-1){                endPoint = duration;                }else{                Lyric nextLryic=lyrics.get(i+1);                endPoint=nextLryic.getLrcTime();                }            if (lyric.getLrcTime()<=position&&endPoint>position){                current_line=i;                break;                }        }        invalidate();    }

2.在音乐播放界面中发消息让歌词滚动。在接收到准备完成的广播之后就让歌词开始滚动

private static final int UPDATE_LRYIC_ROLL = 1;private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            switch (msg.what){                case UPDATA_POSITION:                    updataCurrentPosition();                    seekbar.setProgress(binder.getCurrentPosition());                    break;                case UPDATE_LRYIC_ROLL:                    startRoll();            }        }    };private void startRoll() { lryicView.roll(binder.getCurrentPosition(),binder.getDuration()); handler.sendEmptyMessage(UPDATE_LRYIC_ROLL);}

1.4 平滑滚动歌词

平滑滚动歌词算法 :
变化位置=居中行位置+偏移位置
偏移位置=移动百分比*行高
移动时间百分比=移动时间/可用时间
可用时间=下一段的时间-本段的时间
移动时间=已播放时间-起始时间

public void drawMutiLineText(Canvas canvas){        Lyric lyric =lyrics.get(current_line);        int endStartPoint;        if (current_line==lyrics.size()-1){           endStartPoint=mDuration;          }else{            Lyric nextLryic=lyrics.get(current_line+1);            endStartPoint=nextLryic.getLrcTime();            }        int moveTime=mPosition-lyric.getLrcTime();        int useTime=endStartPoint-lyric.getLrcTime();        float movePercent=moveTime/(float)useTime;        int offset= (int) (movePercent*lineHeight);        Rect bound = new Rect();        paint.getTextBounds(lyric.getLrcString(),0,lyric.getLrcString().length(),bound);        int halfTextW = bound.width()/2;        int halfTextH = bound.height()/2;        int centerY = halfTextH+halfViewH-offset;        int centerY = halfTextH+halfViewH;        for (int i = 0;i<lyrics.size();i++){            if (current_line == i){                paint.setColor(highlightColor);            }else {                paint.setColor(nomalColor);            }            int downY=centerY+(i-current_line)*lineHeight;            canvas.drawText(lyrics.get(i).getLrcString(),halfTextW,downY,paint);        }    }

1.5从文件中解析歌词

1.从文件中解析歌词。将歌词一行一行的读出来,并且根据歌词的格式解析成 List 集合,并将歌词排序

import com.itheima.medioplayer.bean.Lryic;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStreamReader;import java.io.UnsupportedEncodingException;import java.util.ArrayList;import java.util.Collections;    public class LryicParser {    /** 从歌词文件中解析歌词列表*/    public static List<Lryic> parseLryicFromFile(File lryicFile){        List<Lryic> lryics=new ArrayList<>();        //数据可用性检查         if (lryicFile==null || !lryicFile.exists()){        lryics.add(new Lryic(0,"没有找到歌词文件"));        return lryics;    }        try {        BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(new        FileInputStream(lryicFile),"GBK"));        String line=bufferedReader.readLine();        while (line!=null){        List<Lryic> lineLryics=parserLine(line);        lryics.addAll(lineLryics);        line=bufferedReader.readLine();    }    } catch (UnsupportedEncodingException e) {        e.printStackTrace();    } catch (FileNotFoundException e) {        e.printStackTrace();    } catch (IOException e) {        e.printStackTrace();    }    //歌词排序        Collections.sort(lryics);        return lryics;    }    /** 解析一行歌词 [ 01:22.51][ 01:22.51]爱爱爱*/    private static List<Lryic> parserLine(String line) {        List<Lryic> lineLryic=new ArrayList<>();        // [ 01:22.51 [ 01:22.51 爱爱爱        String[] arr=line.split("]");        String content=arr[arr.length-1];        for (int i = 0; i < arr.length - 1; i++) {            int startPoint=parserPoint(arr[i]);            lineLryic.add(new Lryic(startPoint,content));    }        return lineLryic;    }    /** 解析一行歌词的时间 [ 01:22.51*/    private static int parserPoint(String s) {        int time=0;        String timeStr=s.substring(1);        String[] arr=timeStr.split(":");        String minStr=arr[0];        arr=arr[1].split("\\.");        String senStr=arr[0];        String mSenStr=arr[1];    time=Integer.parseInt(minStr)*60*1000+Integer.parseInt(senStr)*1000+Integer.parse        Int(mSenStr)*100;        return time;    }}

Lryic.java 需要实现 Comparable 接口,实现 compareTo 方法

@Override    public int compareTo(Lryic lryic) {        return startPoint-lryic.getStartPoint();    }

在 LryicView 中提供从文件中获取歌词集合和设置当前高亮行的方法

public void setLryicFile(File lryicFile){    lryics=LryicParser.parseLryicFromFile(lryicFile);    currentLine=0; }

在 onDraw 方法中绘制的时候,需要去判断集合是否有数据,没有数据的话就显示歌词正在加载中,如果有数据的话就显示歌词

@Override    protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    if (lryics==null||lryics.size()==0){    //绘制单行居中    drawSingleLineText(canvas);    }else{    drawMutiLineText(canvas);    }}

在接收准备的广播中的滚动歌词之前将歌词加载出来

private class AudioBroadcastReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {    //准备完成    //更新界面的按钮    updatePlayBtn();    //初始化歌曲名和歌手    AudioItem audioItem= (AudioItem) intent.getSerializableExtra("audioItem");    tv_name.setText(StringUtil.formatDisplayName(audioItem.getName()));    tv_artist.setText(audioItem.getArtist());    sk_position.setMax(binder.getDuration());    //更新播放进度    updateCurrentPosition();    //初始化播放模式    updatePlayModeBtn();    File file=new File(Environment.getExternalStorageDirectory(),"test/audio/"+StringUtil.formatDisplayName(audioItem.getName())+".lrc");    lryicView.setLryicFile(file);    //开启歌词滚动更新    startRoll();    }}

总结

到这一篇为止,我已经实现了主页面、音乐播放服务、通知栏和歌词,这时一个音乐播放器已经基本完成,但是我还有一些功能没有实现,没有关系,之后我会进行第二版的开发,现在还有其他项目在忙,所以只能暂时搁置,我相信第二版肯定会更好的,也希望大家可以多提意见,我会悉心听取的。

1 0