美文网首页Android开发Android技术知识Android知识
使用ReplacementSpan在TextView中自定义Ta

使用ReplacementSpan在TextView中自定义Ta

作者: tianbin | 来源:发表于2017-08-16 15:26 被阅读517次

代码已上传Github,欢迎star

UI效果

ReplacementSpan介绍

ReplacementSpan类比较简单,主要用到getSize、draw这两个方法。下面是参数介绍。

int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm)

该方法的返回值会被作为span的width

text:TextView中要显示的文本内容

start、end:span的起始位置

fm:包含TextView的字体属性值,top、ascent、descent、bottom、leading

void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint)

在该方法内绘制span到画布

canvas:画布

text :TextView中要显示的文本内容

start、end:span的起始位置

x:Edge of the replacement closest to the leading margin(不知道该怎么翻译【捂脸】)

top:该行文字显示区域的top值

y: 字体的baseline值

bottom:该行文字显示区域的bottom值

paint:画笔

代码实现

计算绘制位置需要了解字体的相关知识,见下图

绘制背景边框

private void drawTagRect(Canvas canvas, float x, int y, Paint paint) {
        paint.setColor(mColor);
        paint.setAntiAlias(true);

        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        RectF oval = new RectF(x, y + fontMetrics.ascent, x + mSize - mRightMarginPx, y + fontMetrics.descent);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRoundRect(oval, mRadiusPx, mRadiusPx, paint);
    }

RectF(float left, float top, float right, float bottom)

使用x作为leftx + mSize - mRightMarginPx作为right,其中mSizespan的宽度。UI效果图中的Tag距离右边的文字有空隙,所以这里需要减去空隙的宽度才是线框背景的宽度。

UI效果图中显示线框背景的高度正好等于右边文字高度,参考字体属性值的示意图,ascent线与descent线之间为文字显示区域。所以,使用y + fontMetrics.ascent作为topy + fontMetrics.descent作为bottom

绘制文字

private void drawTagText(Canvas canvas, CharSequence text, float x, int start, int end, int y, Paint paint) {
        paint.setTextSize(mTextSizePx);
        paint.setColor(mColor);
        paint.setAntiAlias(true);
        paint.setTextAlign(Paint.Align.CENTER);

        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        final int textCenterX = (int) x + (mSize - mRightMarginPx) / 2;
        int textBaselineY = (y - fontMetrics.descent - fontMetrics.ascent) / 2 + fontMetrics.descent;
        final String tag = text.subSequence(start, end).toString();
        canvas.drawText(tag, textCenterX, textBaselineY, paint);
    }

paint.setTextAlign(Paint.Align.CENTER);

设置画笔以textCenterX为中心点绘制文字

int textBaselineY = (y - fontMetrics.descent - fontMetrics.ascent) / 2 + fontMetrics.descent;

计算出文字在背景线框中居中显示的baseline值,便于理解,可以将上面的计算方式转换为

int textBaselineY = y / 2 + (fontMetrics.descent + Math.abs(fontMetrics.ascent)) / 2 - fontMetrics.descent;

下面是Tag字体属性值

top ... -32
ascent ... -28
descent ... 7
bottom ... 9
leading ... 0

可以发现top、ascent值为负,是因为这里是按照baseline为0的标准进行计算的

遇到的问题

  • 设置不同倍数行间距对参数的影响

    android:lineSpacingMultiplier="1.3"

    start --- 0
    end --- 2
    x --- 0.0
    top --- 0
    y --- 42
    bottom --- 68
    

    android:lineSpacingMultiplier="3"

    start --- 0
    end --- 2
    x --- 0.0
    top --- 0
    y --- 42
    bottom --- 156
    

    根据以上信息可以看出,调整TextView行间距,主要会影响到bottom的值,所以在绘制文字时不要使用bottom来计算位置

完整代码

public class TagSpan extends ReplacementSpan {

    // span width
    private int mSize;
    // text and background wireframe color
    private int mColor;
    // tag text size
    private int mTextSizePx;
    // background radius
    private int mRadiusPx;
    // background wireframe right margin 
    private int mRightMarginPx;

    public RadiusBackgroundSpan(int color, int textSizePx, int radiusPx, int rightMarginPx) {
        mColor = color;
        mTextSizePx = textSizePx;
        mRadiusPx = radiusPx;
        mRightMarginPx = rightMarginPx;
    }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        mSize = (int) paint.measureText(text, start, end) + mRightMarginPx;
        return mSize;
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        drawTagRect(canvas, x, y, paint);
        drawTagText(canvas, text, x, start, end, y, paint);
    }

    private void drawTagRect(Canvas canvas, float x, int y, Paint paint) {
        paint.setColor(mColor);
        paint.setAntiAlias(true);

        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        final float strokeWidth = paint.getStrokeWidth();
        RectF oval = new RectF(x + strokeWidth + 0.5f, y + fontMetrics.ascent, x + mSize + strokeWidth + 0.5f - mRightMarginPx, y + fontMetrics.descent);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawRoundRect(oval, mRadiusPx, mRadiusPx, paint);
    }

    private void drawTagText(Canvas canvas, CharSequence text, float x, int start, int end, int y, Paint paint) {
        paint.setTextSize(mTextSizePx);
        paint.setColor(mColor);
        paint.setAntiAlias(true);
        paint.setTextAlign(Paint.Align.CENTER);

        Paint.FontMetricsInt fontMetrics = paint.getFontMetricsInt();
        final int textCenterX = (int) x + (mSize - mRightMarginPx) / 2;
        int textBaselineY = (y - fontMetrics.descent - fontMetrics.ascent) / 2 + fontMetrics.descent;
        final String tag = text.subSequence(start, end).toString();
        canvas.drawText(tag, textCenterX, textBaselineY, paint);
    }
}

参考资料

相关文章

网友评论

    本文标题:使用ReplacementSpan在TextView中自定义Ta

    本文链接:https://www.haomeiwen.com/subject/gpcurxtx.html