Android SpannableString set background behind part of text

18,125

Solution 1

I managed to solve my problem, based on pskink's suggestion. Here is my class:

public class RoundedBackgroundSpan : ReplacementSpan
{
    public override void Draw(Canvas canvas, ICharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint)
    {
        var rect = new RectF(x, top, x + MeasureText(paint, text, start, end), bottom);
        paint.Color = Application.Context.Resources.GetColor(Resource.Color.nextTimeBackgroundColor);
        canvas.DrawRoundRect(rect, Application.Context.Resources.GetDimensionPixelSize(Resource.Dimension.localRouteDetailsRoundRectValue), Application.Context.Resources.GetDimensionPixelSize(Resource.Dimension.localRouteDetailsRoundRectValue), paint);
        paint.Color = Application.Context.Resources.GetColor(Resource.Color.nextTimeTextColor);
        canvas.DrawText(text, start, end, x, y, paint);
    }

    public override int GetSize(Paint paint, ICharSequence text, int start, int end, Paint.FontMetricsInt fm)
    {
        return Math.Round(MeasureText(paint, text, start, end));
    }

    private float MeasureText(Paint paint, ICharSequence text, int start, int end)
    {
        return paint.MeasureText(text, start, end);
    }
}

Example usage:

var stringBuilder = new SpannableStringBuilder();
var stringToAppend = "hello world";
stringBuilder.Append(stringToAppend);
stringBuilder.SetSpan(new RoundedBackgroundSpan(), stringBuilder.Length() - stringToAppend.Length, stringBuilder.Length(), SpanTypes.ExclusiveExclusive);

Solution 2

If anyone's having difficulty with Roosevelt's code sample (I sure was, maybe because it's Xamarin.Android?), here's a translation into a more basic Android java version:


    public class RoundedBackgroundSpan extends ReplacementSpan {

        private static int CORNER_RADIUS = 8;
        private int backgroundColor = 0;
        private int textColor = 0;

        public RoundedBackgroundSpan(Context context) {
            super();
            backgroundColor = context.getResources().getColor(R.color.gray);
            textColor = context.getResources().getColor(R.color.white);
        }

        @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 + measureText(paint, text, start, end), bottom);
            paint.setColor(backgroundColor);
            canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint);
            paint.setColor(textColor);
            canvas.drawText(text, start, end, x, y, paint);
        }

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

        private float measureText(Paint paint, CharSequence text, int start, int end) {
            return paint.measureText(text, start, end);
        }
    }

And for usage, the following code segment is taken from an Activity and basically puts a nice rounded-corner background around each tag string, with a spacial buffer in between each tag. Note that the commented out line just puts in a background color, which doesn't produce as nice a look...


    SpannableStringBuilder stringBuilder = new SpannableStringBuilder();

    String between = "";
    for (String tag : eventListing.getTags()) {
       stringBuilder.append(between);
       if (between.length() == 0) between = "  ";
       String thisTag = "  "+tag+"  ";
       stringBuilder.append(thisTag);
       stringBuilder.setSpan(new RoundedBackgroundSpan(this), stringBuilder.length() - thisTag.length(), stringBuilder.length() - thisTag.length() + thisTag.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
       //stringBuilder.setSpan(new BackgroundColorSpan(getResources().getColor(R.color.gray)), stringBuilder.length() - thisTag.length(), stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    TextView tv = new TextView(this);
    tv.setText(stringBuilder);

Solution 3

just one word: ReplacementSpan

Share:
18,125
Adam Ivancza
Author by

Adam Ivancza

I'm an enthusiastic full stack cross platform mobile developer who truly enjoys every aspect of development work! I love to use new technologies and I consider myself as an autodidact - I learned everything online at work or after work. I helped startups to create their first MVPs or bring their whole business idea to life. I also helped mature companies to refine their products, improve their development workflows, or coordinate their development work. I've been working remotely full time since 2014 and I’m loving it! I’m only interested in the following: Remote positions around the globe Onsite position in the US which offers VISA sponsorship To be honest I always wanted to be a developer since I got my first computer when I was 7 years old. I loved to play games back then so I wanted to be a game developer. In 1st grade when the teacher asked us to draw what we want to be when we are grown up I drew a computer. So that's when it's all started. Then I attended a programming study group throughout the elementary school where we learned the basics of Pascal and Comenius Logo. After that I went to a high school where I could learn programming in an increased number of hours. There I met a wonderful teacher who taught us the really basics of programming: flow charts, structure charts, the default programming issues (i.e. select the largest element from an array, linear search, etc), and finally Pascal. Then I went to the university where I learned some advanced programming and a lot of math :D. Then in 2010 I got my first iPhone, a 3GS! I loved the apps so I started to take a look at the iOS app development so I went to an iOS development course. And since I left university I haven't stopped working!

Updated on June 07, 2022

Comments

  • Adam Ivancza
    Adam Ivancza almost 2 years

    I would like to create something similar as seen on this image: enter image description here

    I managed to create evertyhing with SpannableStringBuilder, except the orange rounded rectangle. I can set the background to that color with BackgroundColorSpan, but I can't find a way to make it rounded. Any ideas how can I achieve this?

    Thanks in advance!

    EDIT: I'm using Xamarin.Android, but here is my code:

    stringBuilder.SetSpan(new BackgroundColorSpan(Application.Context.Resources.GetColor(Resource.Color.orangeColor)), stringBuilder.Length() - length, stringBuilder.Length(), SpanTypes.ExclusiveExclusive);
    
  • pskink
    pskink over 10 years
    i am glad you solved this by yourself, its better lesson when you try something instead of taking the complete working code
  • jason
    jason over 8 years
    Hi Roosevelt. Can you please look into this stackoverflow.com/questions/32293229/… .ReplacementSpan is not working.
  • dis.iz.peez
    dis.iz.peez about 8 years
    awesome! Thanks for this!
  • android developer
    android developer over 7 years
    This doesn't look like Java style... Anyway, can you check out this question: stackoverflow.com/q/41938378/878126
  • anthony
    anthony over 5 years
    Great! And in my case the tags are in several lines, and how can I do to avoid that the background touch each other on top and bottom?
  • jkincali
    jkincali over 5 years
    Hi Anthony, I haven't tried it, but perhaps something like what is suggested at stackoverflow.com/a/6864017/4307281
  • jkincali
    jkincali over 5 years
    Anthony... also, in the RoundedBackgroundSpan .draw() method, you can adjust the top and/or bottom parameters when the RectF object is created. For example, play around with adding or subtracting 1 from those top and bottom parameters and see if the results yield something more suitable.