LyricsView
public class LyricsView extends TextView {
private Paint mPaint;
private int HIGHLIGHTT_COLOR;
private int NORMAL_COLOR;
private int HIGHLIGHT_SIZE;
private int NORMMAL_SIZE;
private int mViewW;
private int mViewH;
private ArrayList<Lyric> lyricArrayList;
private int centerIndex;
private int LINE_HEIGHT;
private int mDuration;
private int mPosition;
public LyricsView(Context context) {
super(context);
initView();
}
public LyricsView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public LyricsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
private void initView() {
HIGHLIGHT_SIZE = getResources().getDimensionPixelSize(R.dimen.hightlight_size);
NORMMAL_SIZE = getResources().getDimensionPixelSize(R.dimen.normal_size);
LINE_HEIGHT = getResources().getDimensionPixelSize(R.dimen.line_height);
HIGHLIGHTT_COLOR = Color.GREEN;
NORMAL_COLOR = Color.WHITE;
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(HIGHLIGHTT_COLOR);
mPaint.setTextSize(HIGHLIGHT_SIZE);
// 伪造歌词数据
// lyricArrayList = new ArrayList<>();
// for (int i = 0; i < 30; i++) {
// lyricArrayList.add(new Lyric(i * 2000,"当前歌词的行数为:"+i));
// }
// centerIndex = 16;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewW = w;
mViewH = h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (lyricArrayList == null || lyricArrayList.size() == 0) {
drawSingleLineText(canvas);
} else {
drawMuliteLineText(canvas);
}
}
private void drawMuliteLineText(Canvas canvas) {
// 获取据中行的歌词
Lyric lyric = lyricArrayList.get(centerIndex);
// 移动的距离 = 已消耗时间百分比 * 行高
// 已消耗时间百分比 = 已消耗时间 / 可用时间
// 已消耗时间 = 当前播放进度 - 行起始时间
// 可用时间 = 下一行起始时间 - 行起始时间
// 下一行起始时间
int nextStartPoint ;
if (centerIndex != lyricArrayList.size()-1){
// 非最后一行
Lyric nextLyric = lyricArrayList.get(centerIndex + 1);
nextStartPoint = nextLyric.getStartPoint();
}else{
// 最后一行
nextStartPoint = mDuration;
}
// 可用时间
int lineTime = nextStartPoint - lyric.getStartPoint();
// 已消耗时间
int pastTime = mPosition - lyric.getStartPoint();
// 已消耗时间百分比
float pastPercent = pastTime / (float)lineTime;
// 移动的距离
float offsetY = pastPercent * LINE_HEIGHT;
// 计算居中行的Y位置
// 获取文本宽高
Rect bounds = new Rect();
mPaint.getTextBounds(lyric.getContent(),0,lyric.getContent().length(),bounds);
// 计算居中行的Y坐标
float centerY = mViewH / 2 + bounds.height() / 2 - offsetY;
for (int i = 0; i < lyricArrayList.size(); i++) {
if (i!=centerIndex){
// 不是居中行
mPaint.setColor(NORMAL_COLOR);
mPaint.setTextSize(NORMMAL_SIZE);
}else{
// 居中行
mPaint.setColor(HIGHLIGHTT_COLOR);
mPaint.setTextSize(HIGHLIGHT_SIZE);
}
Lyric drawLyric = lyricArrayList.get(i);
// centerY + lineH * (drawIndex - centerIndex)
float drawY = centerY + LINE_HEIGHT * (i - centerIndex);
drawHorizontalText(canvas, drawLyric.getContent(), drawY);
}
}
private void drawHorizontalText(Canvas canvas, String text, float drawY) {
// 获取文本宽高
Rect bounds = new Rect();
mPaint.getTextBounds(text,0,text.length(),bounds);
// 计算绘制文本的坐标
float drawX = mViewW / 2 - bounds.width() / 2;
canvas.drawText(text, drawX, drawY, mPaint);
}
private void drawSingleLineText(Canvas canvas) {
String text = "正在加载歌词...";
// X = view宽度的一半 - 文本宽度的一半
// Y = view高度的一半 + 文本高度的一半
// 获取文本宽高
Rect bounds = new Rect();
mPaint.getTextBounds(text,0,text.length(),bounds);
// 计算绘制文本的坐标
float drawX = mViewW / 2 - bounds.width() / 2;
float drawY = mViewH / 2 + bounds.height() / 2;
canvas.drawText(text, drawX, drawY, mPaint);
}
// 根据当前歌曲的播放进度,更新居中行的位置
public void computeCenterIndex(int position,int duration){
mPosition = position;
mDuration = duration;
// 比较所有行的起始时间,如果比较行的时间比播放时间小,并且下一行的时间比播放时间大
for (int i = 0; i < lyricArrayList.size(); i++) {
Lyric lyric = lyricArrayList.get(i);
int nextStartPoint;
if (i!=lyricArrayList.size()-1){
// 不是最后一行歌词
Lyric nextLyric = lyricArrayList.get(i + 1);
nextStartPoint = nextLyric.getStartPoint();
}else{
nextStartPoint = duration;
}
if (lyric.getStartPoint() <= position && nextStartPoint > position){
centerIndex = i;
break;
}
}
invalidate();
}
// 更新歌词文件
public void setLyricsFile(File lyricsFile){
lyricArrayList = LyricsParser.parserFromFile(lyricsFile);
centerIndex = 0;
}
}
LyricsLoader
public class LyricsLoader {
private static final File ROOT = new File(Environment.getExternalStorageDirectory(),"Download/audio");
// 根据指定的歌曲名,加载到对应的歌词文件
public static File loadFile(String title){
File file = new File(ROOT,title+".lrc");
if (file.exists()){
return file;
}
file = new File(ROOT, title+".txt");
if (file.exists()){
return file;
}
// 到专门存放歌词的文件夹查找
//...
// 连接服务器下载歌词文件
// ...
return null;
}
}
LyricsParser
public class LyricsParser {
// 从文件解析出歌词数据列表
public static ArrayList<Lyric> parserFromFile(File lyricsFile){
ArrayList<Lyric> lyrics = new ArrayList<>();
// 数据可用性验证
if (lyricsFile == null || !lyricsFile.exists()) {
lyrics.add(new Lyric(0, "无法解析歌词文件"));
return lyrics;
}
// 按行读取歌词并解析
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(lyricsFile),"GBK"));
String line ;
while ((line= br.readLine())!=null){
// [01:22.04][02:35.04]寂寞的夜和谁说话
ArrayList<Lyric> lineLyrics = parserLine(line);
lyrics.addAll(lineLyrics);
}
} catch (Exception e) {
e.printStackTrace();
}
Collections.sort(lyrics);
return lyrics;
}
// 解析一行歌词 [01:22.04][02:35.04]寂寞的夜和谁说话
private static ArrayList<Lyric> parserLine(String line) {
ArrayList<Lyric> lineLyrics = new ArrayList<>();
String[] splitArr = line.split("]");
// [01:22.04 [02:35.04 寂寞的夜和谁说话
String content = splitArr[splitArr.length-1];
// [01:22.04 [02:35.04
for (int i = 0; i < splitArr.length - 1; i++) {
int startPoint = parserStartPoint(splitArr[i]);
lineLyrics.add(new Lyric(startPoint,content));
}
return lineLyrics;
}
// 解析歌词起始时间 [02:35.04
private static int parserStartPoint(String time) {
String[] splitArr = time.split(":");
// [02 35.04
String minStr = splitArr[0].substring(1);
// 35.04
String[] split = splitArr[1].split("\\.");
// 35 04
String secStr = split[0];
String msecStr = split[1];
// 将字符串转换为数值
int min = Integer.parseInt(minStr);
int sec = Integer.parseInt(secStr);
int msec = Integer.parseInt(msecStr);
// 合成时间戳
int startPoint = min * 60 * 1000
+ sec * 1000
+ msec * 10;
return startPoint;
}
}
Lyric
public class Lyric implements Comparable<Lyric>{
private int startPoint;
private String content;
public Lyric(int startPoint, String content) {
this.startPoint = startPoint;
this.content = content;
}
public int getStartPoint() {
return startPoint;
}
public void setStartPoint(int startPoint) {
this.startPoint = startPoint;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Lyric{" +
"startPoint=" + startPoint +
", content='" + content + '\'' +
'}';
}
@Override
public int compareTo(Lyric another) {
return startPoint - another.startPoint;
}
}