关于java:带有圆角的Android Spanablecontent

Android Spannablecontent With Rounded Corners

我正在尝试使用 Spannable String 更改我的字符串以制作中间带有数字的徽章。我可以通过设置 BackGroundColorSpan 突出显示适当的字母/数字,但需要帮助使它更漂亮一些。我希望在整个形状周围有一点填充的圆角。

这篇文章真的很接近我想要做的:Android SpannableString set background behind part of text

由于它与我的应用程序交互的方式,我确实需要将资源保留为 TextView。

有什么想法可以针对我的特定情况使用 ReplacementSpan 吗?

这是我的代码片段:

1
2
3
4
5
6
7
            if (menuItem.getMenuItemType() == SlidingMenuItem.MenuItemType.NOTIFICATIONS) {
                myMenuRow.setTypeface(null, Typeface.NORMAL);
                myMenuRow.setTextColor(getContext().getResources().getColor(R.color.BLACK));
                myMenuRow.setActivated(false);
                SpannableString spannablecontent = new SpannableString(myMenuRow.getText());
                spannablecontent.setSpan(new BackgroundColorSpan(Color.argb(150,0,0,0)), 18, myMenuRow.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                myMenuRow.setText(spannablecontent);


实际上,在显示多行徽章时,我发现所有这些答案都存在很大问题。经过大量的测试和调整。我终于得到了上面最好的版本。

基本思想是通过设置更大的文本大小并在跨度内设置想要的大小来欺骗 TextView。此外,您可以看到我正在以不同的方式绘制徽章背景和文本。

所以,这是我的 RoundedBackgroundSpan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class RoundedBackgroundSpan extends ReplacementSpan {

    private static final int CORNER_RADIUS = 12;

    private static final float PADDING_X = GeneralUtils.convertDpToPx(12);
    private static final float PADDING_Y = GeneralUtils.convertDpToPx(2);

    private static final float MAGIC_NUMBER = GeneralUtils.convertDpToPx(2);

    private int mBackgroundColor;
    private int mTextColor;
    private float mTextSize;

    /**
     * @param backgroundColor color value, not res id
     * @param textSize        in pixels
     */

    public RoundedBackgroundSpan(int backgroundColor, int textColor, float textSize) {
        mBackgroundColor = backgroundColor;
        mTextColor = textColor;
        mTextSize = textSize;
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        paint = new Paint(paint); // make a copy for not editing the referenced paint

        paint.setTextSize(mTextSize);

        // Draw the rounded background
        paint.setColor(mBackgroundColor);
        float textHeightWrapping = GeneralUtils.convertDpToPx(4);
        float tagBottom = top + textHeightWrapping + PADDING_Y + mTextSize + PADDING_Y + textHeightWrapping;
        float tagRight = x + getTagWidth(text, start, end, paint);
        RectF rect = new RectF(x, top, tagRight, tagBottom);
        canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint);

        // Draw the text
        paint.setColor(mTextColor);
        canvas.drawText(text, start, end, x + PADDING_X, tagBottom - PADDING_Y - textHeightWrapping - MAGIC_NUMBER, paint);
    }

    private int getTagWidth(CharSequence text, int start, int end, Paint paint) {
        return Math.round(PADDING_X + paint.measureText(text.subSequence(start, end).toString()) + PADDING_X);
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        paint = new Paint(paint); // make a copy for not editing the referenced paint
        paint.setTextSize(mTextSize);
        return getTagWidth(text, start, end, paint);
    }
}

这是我使用它的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void setTags(ArrayList<String> tags) {
    if (tags == null) {
        return;
    }

    mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 26); // Tricking the text view for getting a bigger line height

    SpannableStringBuilder stringBuilder = new SpannableStringBuilder();

    String between ="";
    int tagStart = 0;

    float textSize = 13 * getResources().getDisplayMetrics().scaledDensity; // sp to px

    for (String tag : tags) {
        // Append tag and space after
        stringBuilder.append(tag);
        stringBuilder.append(between);

        // Set span for tag
        RoundedBackgroundSpan tagSpan = new RoundedBackgroundSpan(bgColor, textColor, textSize);
        stringBuilder.setSpan(tagSpan, tagStart, tagStart + tag.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

        // Update to next tag start
        tagStart += tag.length() + between.length();
    }

    mTextView.setText(stringBuilder);
}

注意:

  • 您可以使用所有尺寸和常数来适应您想要的风格
  • 如果您使用外部字体,请务必设置 android:includeFontPadding="false" 否则会弄乱行高

享受:)


这是基于@ericlokness 答案的改进版本,具有自定义背景和文本颜色。它也适用于同一个 TextView 上的多个跨度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class RoundedBackgroundSpan extends ReplacementSpan
{
  private final int _padding = 20;
  private int _backgroundColor;
  private int _textColor;

  public RoundedBackgroundSpan(int backgroundColor, int textColor) {
    super();
    _backgroundColor = backgroundColor;
    _textColor = textColor;
  }

  @Override
  public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
    return (int) (_padding + paint.measureText(text.subSequence(start, end).toString()) + _padding);
  }

  @Override
  public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint)
  {
    float width = paint.measureText(text.subSequence(start, end).toString());
    RectF rect = new RectF(x - _padding, top, x + width + _padding, bottom);
    paint.setColor(_backgroundColor);
    canvas.drawRoundRect(rect, 20, 20, paint);
    paint.setColor(_textColor);
    canvas.drawText(text, start, end, x, y, paint);
  }
}

在阅读了关于 C# 转换器的一些帮助之后,我想出了这个。我还有一些调整要做,但如果有人也在寻找类似的答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RoundedBackgroundSpan extends ReplacementSpan
{

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

    @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 + text.length(), bottom);
        paint.setColor(Color.CYAN);
        canvas.drawRoundRect(rect, 20, 20, paint);
        paint.setColor(Color.WHITE);
        canvas.drawText(text, start, end, x, y, paint);
    }
}


我进一步改进了 mvandillen 类。

这似乎工作得很好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class RoundedBackgroundSpan extends ReplacementSpan
    {
        private final int mPadding = 10;
        private int mBackgroundColor;
        private int mTextColor;

        public RoundedBackgroundSpan(int backgroundColor, int textColor) {
            super();
            mBackgroundColor = backgroundColor;
            mTextColor = textColor;
        }

        @Override
        public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
            return (int) (mPadding + paint.measureText(text.subSequence(start, end).toString()) + mPadding);
        }

        @Override
        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint)
        {
            float width = paint.measureText(text.subSequence(start, end).toString());
            RectF rect = new RectF(x, top+mPadding, x + width + 2*mPadding, bottom);
            paint.setColor(mBackgroundColor);
            canvas.drawRoundRect(rect, mPadding, mPadding, paint);
            paint.setColor(mTextColor);
            canvas.drawText(text, start, end, x+mPadding, y, paint);
        }
    }

这是我基于@mvandillen 回答的版本。我还需要在 span 开始时留出一些边距。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.annotation.NonNull;
import android.text.style.ReplacementSpan;

public class CoolBackgroundColorSpan extends ReplacementSpan {

    private final int mBackgroundColor;
    private final int mTextColor;
    private final float mCornerRadius;
    private final float mPaddingStart;
    private final float mPaddingEnd;
    private final float mMarginStart;

    public CoolBackgroundColorSpan(int backgroundColor, int textColor, float cornerRadius, float paddingStart, float paddingEnd, float marginStart) {
        super();
        mBackgroundColor = backgroundColor;
        mTextColor = textColor;
        mCornerRadius = cornerRadius;
        mPaddingStart = paddingStart;
        mPaddingEnd = paddingEnd;
        mMarginStart = marginStart;
    }

    @Override
    public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        return (int) (mPaddingStart + paint.measureText(text.subSequence(start, end).toString()) + mPaddingEnd);
    }

    @Override
    public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
        float width = paint.measureText(text.subSequence(start, end).toString());
        RectF rect = new RectF(x - mPaddingStart + mMarginStart, top, x + width + mPaddingEnd + mMarginStart, bottom);
        paint.setColor(mBackgroundColor);
        canvas.drawRoundRect(rect, mCornerRadius, mCornerRadius, paint);
        paint.setColor(mTextColor);
        canvas.drawText(text, start, end, x + mMarginStart, y, paint);
    }
}

使用方法:

1
2
3
4
5
6
7
8
9
10
int flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
SpannableString staffTitleSpan = new SpannableString("staff:");
SpannableString staffNameSpan = new SpannableString("John Smith");
staffNameSpan.setSpan(new StyleSpan(Typeface.BOLD), 0, staffNameSpan.length(), flag);
staffNameSpan.setSpan(new CoolBackgroundColorSpan(mStaffNameSpanBgColor, mStaffNameSpanTextColor, mStaffNameSpanBgRadius, mStaffNameSpanBgPaddingStart, mStaffNameSpanBgPaddingEnd, mStaffNameSpanMarginStart), 0, staffNameSpan.length(), flag);
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(staffTitleSpan);
builder.append(staffNameSpan);

staffTextView.setText(builder);

预览:

enter


希望这个答案可以为那些仍在寻找的人简化它......

您可以简单地使用"芯片"可绘制对象。它正确地完成了所有计算,并且代码更少。

查看独立的 ChipDrawable

为了完整性,复制到这里:

res/xml/standalone_chip.xml:

1
2
3
4
5
<chip xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:text="@string/item_browse_database_sample"
    app:chipBackgroundColor="@color/blueBase"
    app:closeIconVisible="false" />

在java中:

1
2
3
4
5
6
7
8
9
// Inflate from resources.
ChipDrawable chip = ChipDrawable.createFromResource(getContext(), R.xml.standalone_chip);

// Use it as a Drawable however you want.
chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight());
ImageSpan span = new ImageSpan(chip);

Editable text = editText.getText();
text.setSpan(span, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

最终结果:

result


好的,所以问题有点乱,这是我从 DanieleB 和 mvandillen 得到的解决方案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class RoundedBackgroundSpan extends ReplacementSpan {

    private static final int CORNER_RADIUS = 8;
    private static final int PADDING_X = 12;

    private int   mBackgroundColor;
    private int   mTextColor;

    /**
     * @param backgroundColor background color
     * @param textColor       text color
     */

    public RoundedBackgroundSpan(int backgroundColor, int textColor) {
        mBackgroundColor = backgroundColor;
        mTextColor = textColor;
    }

    @Override
    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        return (int) (PADDING_X + paint.measureText(text.subSequence(start, end).toString()) + PADDING_X);
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        float width = paint.measureText(text.subSequence(start, end).toString());
        RectF rect = new RectF(x, top, x + width + 2 * PADDING_X, bottom);
        paint.setColor(mBackgroundColor);
        canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint);
        paint.setColor(mTextColor);
        canvas.drawText(text, start, end, x + PADDING_X, y, paint);
    }
}

提示:您可以删除 textColor 并使用默认的 TextView 颜色:

1
2
3
4
5
6
7
8
9
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
    Paint paint1 = new Paint(paint);
    float width = paint1.measureText(text.subSequence(start, end).toString());
    RectF rect = new RectF(x, top, x + width + 2 * PADDING_X, bottom);
    paint1.setColor(mBackgroundColor);
    canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint1);
    canvas.drawText(text, start, end, x + PADDING_X, y, paint);
}

如果您正在使用 kotlin 并针对多个密度设备,那么这将适合您

第 1 步:创建一个扩展 ReplacementSpan

的类,即 RoundedBackgroundSpan.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.text.style.ReplacementSpan
import kotlin.math.roundToInt

class RoundedBackgroundSpan(
    private val textColor: Int,
    private val backgroundColor: Int
) : ReplacementSpan() {

    private val additionalPadding = 4.toPx().toFloat()
    private val cornerRadius = 4.toPx().toFloat()

    override fun draw(
        canvas: Canvas,
        text: CharSequence,
        start: Int,
        end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int,
        paint: Paint
    ) {
        val newTop = y + paint.fontMetrics.ascent
        val newBottom = y + paint.fontMetrics.descent
        val rect = RectF(x, newTop, x + measureText(paint, text, start, end) + 2 * additionalPadding, newBottom)
        paint.color = backgroundColor

        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
        paint.color = textColor
        canvas.drawText(text, start, end, x + additionalPadding, y.toFloat(), paint)
    }

    override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
        return (paint.measureText(text, start, end) + 2 * additionalPadding).toInt()
    }

    private fun measureText(paint: Paint, text: CharSequence, start: Int, end: Int): Float {
        return paint.measureText(text, start, end)
    }

    private fun Int.toPx(): Int {
        val resources = Resources.getSystem()
        val metrics = resources.displayMetrics
        return (this * (metrics.densityDpi / 160.0f)).roundToInt()
    }
}

第 2 步:之后调用上面创建的类,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
   private fun updateSubjectName(textView: TextView, fullText: String, spanText: String, spanColor: String) {
        val flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        val fullTextSpan = SpannableString("$fullText")
        val spanTextSpan = SpannableString("$spanText")
        spanTextSpan.setSpan(StyleSpan(Typeface.BOLD), 0, spanTextSpan.length, flag)

        spanTextSpan.setSpan(
            RoundedBackgroundSpan(context.getColor(R.color.color_white), Color.parseColor(spanColor)),
            0, spanTextSpan.length, flag
        )

        val builder = SpannableStringBuilder()
        builder.append(fullTextSpan)
        builder.append(spanTextSpan)
        textView.text = builder
    }

观看 Google 的视频,他们提供了以下解决方案:

enter