此LrcView实现了歌词的逐字更新, 代码简洁,逻辑也不复杂,不需要考虑文字排版问题,但也存在一些问题:
1. 只能逐字变化, 不能像酷狗那样慢慢覆盖变化
2. 每次都有更新都会NEW对象, 更新过快时预计会引起gc大量回收
public class LrcView2 extends View {
/**
* 加载歌词失败
*/
private static final int STATE_FAIL = 0;
/**
* 正在加载歌词
*/
private static final int STATE_LOADING = 1;
/**
* 加载歌词成功
*/
private static final int STATE_SUCCESS = 2;
/**
* 当前状态
*/
private int mCurrentState = 2;
private CharSequence mLoadingText;
private CharSequence mFailedText;
/**
* 歌词文本
*/
private SpannableStringBuilder mLrcText;
/**
* 正在播放的歌词颜色
*/
private int mCurrentColor;
private ForegroundColorSpan mColorSpan;
/**
* 正在播放的歌词大小
*/
private int mCurrentSize;
private AbsoluteSizeSpan mSizeSpan;
/**
* 正在播放行的第一个字符位置 用于查找当前行所在的行数,以定位文本位置
*/
private int mCurrentOffset;
private TextPaint mPaint;
public LrcView2(Context context) {
super(context);
mPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
}
/**
* 更新颜色位置
*
* @param start
* 正在播放行的第一个字符位置
* @param end
* 正在播放行的最后一个字符位置
* @param span
* 正在播放的当前字符位置
*/
public void updateSpanPosition(int start, int end, int span) {
if (mLrcText != null) {
mLrcText.clearSpans();
if (start >= 0 && end >= span) {
if (span > start) {
if (mColorSpan == null) {
mColorSpan = new ForegroundColorSpan(mCurrentColor);
}
mLrcText.setSpan(mColorSpan, start, span, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (end > start) {
if (mSizeSpan == null) {
mSizeSpan = new AbsoluteSizeSpan(mCurrentSize, false);
}
mLrcText.setSpan(mSizeSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
mCurrentOffset = start;
postInvalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
if (mCurrentState == STATE_FAIL) {
drawTextOfLoadFailed(canvas);
} else if (mCurrentState == STATE_LOADING) {
drawTextOfLoading(canvas);
} else if (mCurrentState == STATE_SUCCESS) {
drawTextOfLoadSuccessed(canvas);
}
}
/**
* 画正在加载时的提示语
*
* @param canvas
*/
private void drawTextOfLoading(Canvas canvas) {
if (TextUtils.isEmpty(mLoadingText)) {
return;
}
StaticLayout layout = new StaticLayout(mLoadingText, 0, mLoadingText.length(), mPaint, getWidth()
- getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_CENTER, 1.5F, 0, true);
canvas.save();
canvas.translate(getPaddingLeft(), getHeight() / 2 - layout.getHeight() / 2);
layout.draw(canvas);
canvas.restore();
}
/**
* 画加载失败时的提示语
*
* @param canvas
*/
private void drawTextOfLoadFailed(Canvas canvas) {
if (TextUtils.isEmpty(mFailedText)) {
return;
}
StaticLayout layout = new StaticLayout(mFailedText, 0, mFailedText.length(), mPaint, getWidth()
- getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_CENTER, 1.5F, 0, true);
canvas.save();
canvas.translate(getPaddingLeft(), getHeight() / 2 - layout.getHeight() / 2);
layout.draw(canvas);
canvas.restore();
}
/**
* 画加载成功时的提示语, 并根据时间进度逐字变换歌词颜色
*
* @param canvas
*/
private void drawTextOfLoadSuccessed(Canvas canvas) {
if (TextUtils.isEmpty(mLrcText)) {
return;
}
StaticLayout layout = new StaticLayout(mLrcText, 0, mLrcText.length(), mPaint, getWidth() - getPaddingLeft()
- getPaddingRight(), Alignment.ALIGN_CENTER, 1.5F, 0, true);
canvas.save();
//查找到mCurrentOffset所在的行数
int line = layout.getLineForOffset(mCurrentOffset);
//计算该行数所在位置
int dy = layout.getLineTop(line);
//偏移
canvas.translate(getPaddingLeft(), getHeight() / 2 - dy);
layout.draw(canvas);
canvas.restore();
}
public void setText(String lrc) {
if (TextUtils.isEmpty(lrc)) {
this.mLrcText = null;
mCurrentState = STATE_FAIL;
} else {
this.mLrcText = new SpannableStringBuilder(lrc);
mCurrentState = STATE_SUCCESS;
}
mCurrentOffset = 0;
postInvalidate();
}
public String getText() {
if (mLrcText == null) {
return null;
}
return mLrcText.toString();
}
public CharSequence getLoadingText() {
return mLoadingText;
}
public void setLoadingText(CharSequence mLoadingText) {
this.mLoadingText = mLoadingText;
postInvalidate();
}
public CharSequence getFailedText() {
return mFailedText;
}
public void setFailedText(CharSequence failedText) {
this.mFailedText = failedText;
postInvalidate();
}
public int getCurrentColor() {
return mCurrentColor;
}
public void setCurrentColor(int currentColor) {
this.mCurrentColor = currentColor;
this.mColorSpan = null;
postInvalidate();
}
public int getCurrentSize() {
return mCurrentSize;
}
public void setCurrentSize(int currentSize, boolean sp) {
if (sp) {
this.mCurrentSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, currentSize, getResources()
.getDisplayMetrics());
} else {
this.mCurrentSize = currentSize;
}
this.mSizeSpan = null;
postInvalidate();
}
public float getDefaultSize() {
return mPaint.getTextSize();
}
public void setDefaultSize(int defaultSize, boolean sp) {
if (sp) {
mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, defaultSize, getResources()
.getDisplayMetrics()));
} else {
mPaint.setTextSize(defaultSize);
}
postInvalidate();
}
public int getDefaultColor() {
return mPaint.getColor();
}
public void setDefaultColor(int defaultColor) {
mPaint.setColor(defaultColor);
postInvalidate();
}
}