上图就是效果。
背景和字体颜色可以自己调,都定义成了常量并有说明,直接更改即可。
可以自适应不同分别率。
可以自己将其改成根据屏幕高度动态决定显示几行。只需要调整ondraw就好了。
package com.godyde.musicplayer;
/**
* 显示歌词的自定义控件。
* 核心思想就是根据Mediaplayer.getCurrentPosition()返回的当前进度
* 从歌词中查找当前进度对应的行mCount。以及当前行进行的百分比mPercent
* 然后根据mCount 来决定打印哪些行[mCount-6,mCount+6
* 每行歌词打印的基线会随着mPercent的增加而上移(mPercent/100)*mLineHeight.
* 第一行的透明度会随着mPercent的增加而增加形成淡出效果。
* 第十三行的透明度会随着mPercent的增加而减少形成淡入效果。
* 当前进度对应的行为第7行。第7行的效果来自于渐变效果。只是将渐变设置成了类似突变的效果。
* 突变的位置会随着mPercent的增加而右移。形成卡拉OK效果。
* <p/>
* 几个未完成的事情。
* 2.当歌词单行过长时未分行。会导致部分歌词行显示不完全。(TODO 根据字符串长度和控件宽度来分行显示)
* 3.未实现从文件加载。直接加载的项目内资源。(TODO 使用Uri来加载歌词)
* 4.是否考虑使用surfaceview
*
* V0.2 :已解决1,2,
* @author 易登
* @version V0.2 06/12/2016
*/
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.media.MediaPlayer;
import android.util.AttributeSet;
import android.view.View;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
public class LrcTextView extends View {
//歌词显示的基本颜色
static final int BASECOLOR = 0xFFEEEEEE;
//歌词显示的卡拉OK效果颜色
static final int COVERCOLOR = 0xFF39DF7C;
static final int BACKGROUNDCOLOR=0xFF201633;
//字体大小
static final float MAXTEXTSIZE = 48.0f;
static final float MINTEXTSIZE = 8f;
//歌词刷新频率 每秒25帧
static final long REFRESHRATE = 25;
//歌词字体大小
private float mTextSize;
//每行歌词占用的高度
private int mLineHeight = 100;
//歌词最多的一行。所包含的字符数。
private int mMaxLrcLine;
//画笔
private Paint mPaint;
//歌词字符串数组。从lrc文件提取
private String mLrc[];
//显示歌词的时间索引。从lrc文件提取
private int mLrcTime[];
//播放歌曲的播放器(由start函数传入)
private MediaPlayer mMediaPlayer;
//当前播放进度对应的歌词行
private int mCount;
//当前播放进度对应的歌词当前行的百分比 0-100
private float mPercent;
public LrcTextView(Context context) {
super(context);
initPaint();
}
public LrcTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public LrcTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaint();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//偷懒的做法。控件总是从宽度上铺满父控件。高度取800和父控件高度中的较小值。
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), Math.min(widthMeasureSpec, MeasureSpec.getSize(heightMeasureSpec)));
}
@Override
protected void onDraw(Canvas canvas) {
if (mMediaPlayer != null) {
//获取当前的进度
getCountAndPrecent();
//循环打印七行歌词
setTextSize(canvas);
for (int i = 0; i < 13; i++) {
//计算需要打印的歌词所在行
int line = mCount - 6 + i;
//这个判断是在歌曲开始和结束时,防止越界。小于0 和大于等于mLrc.length无需打印
if (line >= 0 && line < mLrc.length) {
//最上面一行歌词淡出效果
if (i == 0) {
//根据mPercent设置透明度
mPaint.setAlpha((int) ((1 - mPercent / 100) * 255));
//打印歌词
canvas.drawText(mLrc[line], mTextSize, (i + 1 - mPercent / 100) * mLineHeight, mPaint);
//恢复成不透明。
mPaint.setAlpha(255);
} else if (i == 6) { //当前正在播放的歌词 实现卡拉OK效果
//获取当前行歌词的显示宽度
float mTextWidth = mPaint.measureText(mLrc[mCount]);
//创建一个类似突变的渐变效果 。变化节点根据 mPercent设置。
LinearGradient shader = new LinearGradient(0, 0,mTextSize*2+mTextWidth * mPercent / 100, 0,
new int[]{COVERCOLOR, BASECOLOR}, new float[]{0.99f, 0.01f},
Shader.TileMode.CLAMP);
//将突变效果设置给画笔。
mPaint.setShader(shader);
//使用该画笔画出当前歌词。
canvas.drawText(mLrc[line], mTextSize, (i + 1 - mPercent / 100) * mLineHeight, mPaint);
//恢复画笔属性。
//mPaint.setColor(BASECOLOR);
mPaint.setShader(null);
} else if (i == 12) {//最下面一行歌词实现淡入效果
//根据mPercent设置透明度
mPaint.setAlpha((int) ((mPercent / 100) * 255));
//打印歌词
canvas.drawText(mLrc[line],mTextSize, (i + 1 - mPercent / 100) * mLineHeight, mPaint);
//恢复画笔效果
mPaint.setAlpha(255);
} else {
//打印其他行歌词。
canvas.drawText(mLrc[line], mTextSize, (i + 1 - mPercent / 100) * mLineHeight, mPaint);
}
}
}
//根据刷新频率设置下一次刷新时间
postInvalidateDelayed(1000 / REFRESHRATE);
} else
super.onDraw(canvas);
}
/**
* 初始化画笔属性
*/
private void initPaint() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(BASECOLOR);
//mPaint.setTextSize(mTextSize);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setShader(null);
mPaint.setTextAlign(Paint.Align.LEFT);
setBackgroundColor(BACKGROUNDCOLOR);
}
/**
* 计算当前的歌词显示参数
* 当前行,以及当前行进行的百分比
*/
private void getCountAndPrecent() {
if (mMediaPlayer != null) {
int mPosition = mMediaPlayer.getCurrentPosition();
//计算当前对应的歌词行。
for (int i = mLrc.length - 1; i >= 0; i--) {
if (mPosition > mLrcTime[i]) {
mCount = i;
break;
}
}
//计算当前行进行的百分比
if (mCount == mLrc.length) {
//如果已经到了最后一行,表示播放完毕。
mPercent = 100;
} else {
mPercent = ((float) (mPosition - mLrcTime[mCount]) * 100) / (mLrcTime[mCount + 1] - mLrcTime[mCount]);
}
}
}
/**
* 根据最长一行歌词的字符数来确认歌词的字体大小。
* @param canvas 显示歌词的画布
*/
private void setTextSize(Canvas canvas) {
if (mTextSize == 0) {
float textWidth;
mTextSize = MAXTEXTSIZE;
//逐步缩小字体让最长一行的歌词也能显示完整。
do {
mPaint.setTextSize(mTextSize);
textWidth = mPaint.measureText(mLrc[mMaxLrcLine]);
if (textWidth > canvas.getWidth()-mTextSize && mTextSize > MINTEXTSIZE)
mTextSize -= 0.5f;
else
break;
} while (true);
System.out.println("字体大小:" + mTextSize);
//调整行高。
mLineHeight = canvas.getHeight()/13;
System.out.println("行高:" + mLineHeight);
}
}
/**
* 开始显示歌词
* @param lrcSource 歌词文件
* @param mediaPlayer 播放器
*/
public void start(int lrcSource, MediaPlayer mediaPlayer) {
this.mMediaPlayer = mediaPlayer;
//加载和分解歌词
InputStream inputStream = getResources().openRawResource(lrcSource);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
StringBuilder builder = new StringBuilder("");
byte buffer[] = new byte[1024];
try {
int count;
while ((count = bufferedInputStream.read(buffer)) != -1) {
builder.append(new String(buffer, 0, count));
}
mLrc = builder.toString().split("\n");
//歌词时间索引数组比歌词数组长度要大1.用来标识最后一行歌词时间(这里默认5秒)
mLrcTime = new int[mLrc.length + 1];
//去掉第一行初始的一个字符 百度歌词下载下来,第一行的第一个字节未特殊字符。
while (mLrc[0].charAt(0) != '[')
mLrc[0] = mLrc[0].substring(1);
int mMaxLrcLineChar = 0;
mMaxLrcLine = 0;
for (int i = 0; i < mLrc.length; i++) {
String item[] = mLrc[i].split("]");
mLrcTime[i] = 0;
//本行的开始时间
mLrcTime[i] += Integer.parseInt(item[0].substring(1, 3)) * 60 * 1000;
mLrcTime[i] += Integer.parseInt(item[0].substring(4, 6)) * 1000;
mLrcTime[i] += Integer.parseInt(item[0].substring(7, 9)) * 10;
//本行显示的歌词
if (item.length == 2)
mLrc[i] = item[1];
else
mLrc[i] = "";
if (mMaxLrcLineChar < mLrc[i].length()) {
mMaxLrcLineChar = mLrc[i].length();
mMaxLrcLine = i;
}
}
//最后一行歌词默认耗时5秒
mLrcTime[mLrc.length] = mLrcTime[mLrc.length - 1] + 5000;
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bufferedInputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
mTextSize = 0;//置0让onDraw重新计算字体大小和行高。
invalidate();//触发onDraw 开始刷新歌词
}
}
/**
* 停止显示歌词
*/
public void stop() {
mMediaPlayer = null;
invalidate();
}
}