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

本文介绍了一种在Android应用中实现歌词显示与滚动的方法。包括自定义LryicView组件来绘制居中文本、实现多行歌词滚动及平滑滚动效果、从文件中解析歌词并加载歌词等功能。

歌词显示实现

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();
    }
}

总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值