Android 用StaticLayout和SpannableString实现歌词逐字更新

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

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值