Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Spannablecontent With Rounded Corners

I am trying to change my string to make a badge with a number in the middle by using Spannable String. I can highlight the appropriate letter/number by setting the BackGroundColorSpan, but need help making it a little prettier. I was hoping to have rounded corners with a little bit of padding around the entire shape.

This article is really close to what I'm trying to do: Android SpannableString set background behind part of text

I really need to keep the resource as a TextView due to the way it interacts with my application.

Any ideas how to utilize ReplacementSpan for my particular situation?

Here is my code snippet:

            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);
like image 583
ericlokness Avatar asked Jul 11 '14 15:07

ericlokness


2 Answers

Actually i found big issues with all of those answers when displaying multiple lines of badges. After lots of testing and tweaking. I Finally got the best version of the above.

The basic idea is to trick the TextView by setting a much bigger text size and setting the wanted size inside the span. Also, you can see i'm drawing the badge background and text differently.

So, this is my RoundedBackgroundSpan:

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

And here is how i'm using it:

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


Note:

  • You can play with all sizes and constants to fit to your wanted style
  • If you use an external font be sure to set android:includeFontPadding="false" otherwise it can mess up the line's height

Enjoy :)

like image 198
Shirane85 Avatar answered Sep 20 '22 11:09

Shirane85


Here's an improved version based on @ericlokness answer, with custom background and text colors. It also works with multiple spans on the same TextView.

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);
  }
}
like image 29
mvandillen Avatar answered Sep 21 '22 11:09

mvandillen