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(); }}
总结
到这一篇为止,我已经实现了主页面、音乐播放服务、通知栏和歌词,这时一个音乐播放器已经基本完成,但是我还有一些功能没有实现,没有关系,之后我会进行第二版的开发,现在还有其他项目在忙,所以只能暂时搁置,我相信第二版肯定会更好的,也希望大家可以多提意见,我会悉心听取的。
- hunterliy小作品之 HunterMusic音乐播放器(Day4-歌词显示实现)
- hunterliy小作品之 HunterMusic音乐播放器(Day2-后台播放服务实现)
- hunterliy小作品之 HunterMusic音乐播放器(Day1-主页面实现)
- hunterliy小作品之 HunterMusic音乐播放器(Day3-自定义通知栏实现)
- hunterliy小作品之 HunterMusic音乐播放器(开发介绍)
- Android 音乐播放器实现歌词显示
- 实现音乐播放器歌词显示效果
- 关于音乐播放器中歌词同步显示的实现
- Android中音乐播放器实现歌词同步显示
- android编程实例-音乐播放器之歌词显示
- Android 音乐播放器的开发教程(九) 歌词的显示----- 小达
- ubuntu音乐播放器显示歌词
- 音乐播放器(3)--歌词显示
- archlinux 音乐播放器与歌词显示
- android音乐播放器显示歌词
- 个人小作品之迷你音乐播放器(移动端)
- 音乐播放器实现歌词同步
- Android 音乐播放器 实现歌词同步
- 读书笔记——《图解TCP/IP》(2/4)
- mysql批量将列名改成小写
- 2.第九章
- Git使用教程(1)
- Sql语句返回自增Id
- hunterliy小作品之 HunterMusic音乐播放器(Day4-歌词显示实现)
- Android NDK基本理解
- web.xml中的url-pattern 写法小结(附源码分析)
- setValueForKey&setObjectForKey
- 清除浮动的三种常用方法
- 面试题总结
- 半全局立体匹配方法调研,以及一些立体匹配方向的思考
- 对分易教学平台考勤漏洞探索,批量签到app制作杂谈
- 图片显示器(Java+Python)