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 BackGrou
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 :)
After reading getting a little help with a converter for C#, I came up with this. I still have some tweaking to do, but if anyone is also looking for a similar answer.
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);
}
}
Hopefully this answer simplifies it for those still looking...
You can simply use a "chip" drawable. It does all the calculations correctly and is of much more minimal code.
See Standalone ChipDrawable
For completeness copied here:
res/xml/standalone_chip.xml
:
<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" />
in java:
// 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);
End result:
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);
}
}
Watching Google's video, they offer this solution:
Sadly I see here various things missing, and I can't find the full code, so I can't try it out.
I further improved mvandillen class.
This seems to work very fine:
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);
}
}