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);
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:
Enjoy :)
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With