Android - 使用ReplacementSpan为SpannableStringBuilder添加边距

6
我正在尝试在TextView/EditText中格式化Hashtags(例如Material Design规范中提到的Chips)。我能够使用ReplacementSpan格式化背景。但问题是我无法增加TextView/EditText中的行间距。请参见下面的图片。 enter image description here 问题是如何为hashtags添加顶部和底部边距?
这是我将背景添加到文本的代码:
   /**
     * First draw a rectangle
     * Then draw text on top
     */
    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        RectF rect = new RectF(x, top, x + measureText(paint, text, start, end), bottom);
        paint.setColor(backgroundColor);
        canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint);
        paint.setColor(textColor);
        canvas.drawText(text, start, end, x, y, paint);
    }

那最终你成功了吗? - Daniel W.
3个回答

11

我之前也遇到了类似的问题,这是我想出的解决方案:

在xml中托管TextView:

<TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="18dp"
        android:paddingBottom="18dp"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:gravity="fill"
        android:textSize="12sp"
        android:lineSpacingExtra="10sp"
        android:textStyle="bold"
        android:text="@{viewModel.renderedTagBadges}">

一个自定义版本的ReplacementSpan

public class TagBadgeSpannable extends ReplacementSpan implements LineHeightSpan {

    private static int CORNER_RADIUS = 30;
    private final int textColor;
    private final int backgroundColor;
    private final int lineHeight;

    public TagBadgeSpannable(int lineHeight, int textColor, int backgroundColor) {
        super();
        this.textColor = textColor;
        this.backgroundColor = backgroundColor;
        this.lineHeight = lineHeight;
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        final float textSize = paint.getTextSize();
        final float textLength = x + measureText(paint, text, start, end);
        final float badgeHeight = textSize * 2.25f;
        final float textOffsetVertical = textSize * 1.45f;

        RectF badge = new RectF(x, y, textLength, y + badgeHeight);
        paint.setColor(backgroundColor);
        canvas.drawRoundRect(badge, CORNER_RADIUS, CORNER_RADIUS, paint);

        paint.setColor(textColor);
        canvas.drawText(text, start, end, x, y + textOffsetVertical, paint);
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        return Math.round(paint.measureText(text, start, end));
    }

    private float measureText(Paint paint, CharSequence text, int start, int end) {
        return paint.measureText(text, start, end);
    }

    @Override
    public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3, Paint.FontMetricsInt fontMetricsInt) {
        fontMetricsInt.bottom += lineHeight;
        fontMetricsInt.descent += lineHeight;
    }
}

最后,一个创建可扩展字符串的构建器

public class AndroidTagBadgeBuilder implements TagBadgeBuilder {

    private final SpannableStringBuilder stringBuilder;
    private final String textColor;
    private final int lineHeight;

    public AndroidTagBadgeBuilder(SpannableStringBuilder stringBuilder, int lineHeight, String textColor) {
        this.stringBuilder = stringBuilder;
        this.lineHeight = lineHeight;
        this.textColor = textColor;
    }

    @Override
    public void appendTag(String tagName, String badgeColor) {
        final String nbspSpacing = "\u202F\u202F"; // none-breaking spaces

        String badgeText = nbspSpacing + tagName + nbspSpacing;
        stringBuilder.append(badgeText);
        stringBuilder.setSpan(
            new TagBadgeSpannable(lineHeight, Color.parseColor(textColor), Color.parseColor(badgeColor)),
            stringBuilder.length() - badgeText.length(),
            stringBuilder.length()- badgeText.length() + badgeText.length(),
            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        );
        stringBuilder.append("  ");
    }

    @Override
    public CharSequence getTags() {
        return stringBuilder;
    }

    @Override
    public void clear() {
        stringBuilder.clear();
        stringBuilder.clearSpans();
    }
}
结果将看起来像这样: Rendered badges in TextView 调整TagBadgeSpannable中的度量以适合您的需求。
我已上传了一个非常简洁的示例项目,其中包含使用此代码的内容,请随意查看:github
注意:这个示例使用Android DataBinding并采用了MVVM模式。

对于跟随此解决方案的任何人,您可能会看到一些非常奇怪的芯片。这似乎使此代码更加“内联”,请参阅onDraw方法中的这些行。RectF badge = new RectF(x, y, textLength, y + badgeHeight); ///..... canvas.drawText(text,start,end,x,y + textOffsetVertical,paint); - oorosco

8

Android中的文本标记非常缺乏文档,编写此代码就像在黑暗中摸索一样。

我已经了解了一些内容,现在和大家分享一下。

您可以通过将芯片跨度包装在LineHeightSpan中来处理行间距。 LineHeightSpan是扩展ParagraphStyle标记接口的接口,因此这告诉您它会影响段落级别的外观。也许一个好的比较方式是将您的ReplacementSpan子类与HTML <span>进行比较,而LineHeightSpan这样的ParagraphStyle跨度则类似于HTML <div>

LineHeightSpan接口由一个方法组成:

public void chooseHeight(CharSequence text, int start, int end,
                         int spanstartv, int v,
                         Paint.FontMetricsInt fm);

这个方法会为你段落中的每一行都调用一次

  • text 是你的Spanned字符串。
  • start 是当前行开头字符的索引。
  • end 是当前行结尾字符的索引。
  • spanstartv 是(如我所记)整个跨度本身的垂直偏移量。
  • v 是(如我所记)当前行的垂直偏移量。
  • fmFontMetrics对象,实际上是一个返回参数。你的代码将更改fm,而TextView在绘制时将使用它们。

因此,TextView会为其处理的每一行调用此方法。根据参数以及你的Spanned字符串,你可以设置FontMetrics来使用你选择的值渲染该行。

以下是我为列表中的项目(思考<ol><li>)编写的示例,其中我希望在每个列表项之间存在一些分隔:

    @Override
    public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) {

        int incr = Math.round(.36F * fm.ascent); // note: ascent is negative

        // first line: add space to the top
        if (((Spanned) text).getSpanStart(this) == start) {
            fm.ascent += incr;
            fm.top = fm.ascent + 1;
        }

        // last line: add space to the bottom
        if (((Spanned) text).getSpanEnd(this) == end) {
            fm.bottom -= incr;
        }

    }

你的版本可能会更简单,只需为每行调用FontMetrics并以相同方式进行更改。
当需要解密FontMetrics时,日志记录器和调试器是你的好朋友。你只需不断调整数值,直到得到你喜欢的结果。

1

这个BackgroundColorSpan不起作用吗?

对于您的特定情况,您还可以为TextView设置lineSpacing。

最后一个选项(未经测试)是计算跨度的高度大于您正在绘制的高度。您可以检查DynamicDrawableSpan中的getSize实现,以了解如何使用给定的FontMetrics实例设置跨度的高度。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接