Android Spannablecontent with rounded corners

I am trying to change my string to make an icon with a number in the middle using a Spannable String. I can highlight the corresponding letter / number by setting BackGroundColorSpan, but you need help to make it a little prettier. I was hoping to have rounded corners with a little padding around the whole figure.

This article is very close to what I'm trying to do: Android SpannableString sets the background as part of the text

I really need to save the resource as a TextView because of how it interacts with my application.

Any ideas on how to use ResplacementSpan for my specific 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); 
+8
java android design icons
Jul 11 '14 at 15:52
source share
6 answers

After reading a little help with the converter for C #, I came up with this. I still have some kind of setup, but if someone 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); } } 
+9
Jul 12 '14 at 13:29
source share

In fact, I found big problems with all of these answers when displaying multiple line icons. After many tests and adjustments. I finally got the best version above.

The basic idea is to fool a TextView by setting a much larger text size and setting the desired size within the range. In addition, you can see that I draw the background of the icon and text in different ways.

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 use 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 according to your desired style.
  • If you use an external font, be sure to set android: includeFontPadding = "false", otherwise it can ruin the height of the line

Enjoy :)

+13
Jan 05 '16 at 23:53 on
source share

Here's an improved version based on @ericlokness answer, with custom background and text colors. It also works with multiple passes in 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); } } 
+9
Mar 12 '15 at 9:38
source share

I improved the mvandillen class.

It looks very good:

 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); } } 
+4
Apr 04 '15 at 2:20
source share

Ok, so the question is a bit dirty, here is my solution from DanieleB and mvandillen.

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



Tip. You can remove the textColor and the default custom TextView color:

 @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); } 
+1
Jun 21 '16 at 16:08
source share

Here is my version based on @mvandillen's answer. I also needed a margin at the beginning of the range.

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

How to use:

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

Preview:

enter image description here

+1
Jul 28 '17 at 14:40
source share



All Articles