13

I'd like to show the text like the below...

enter image description here

My coding is the following:

SpannableString sText = new SpannableString(text);
sText.setSpan(new BackgroundColorSpan(Color.YELLOW), 0, sText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

holder.txtText.setLineSpacing(0, 1.5f);
textView.setText(sText);
Hamid Shatu
  • 9,664
  • 4
  • 30
  • 41
user3511231
  • 131
  • 1
  • 3

7 Answers7

7

try this. Create custom TextView and override method draw(Canvas canvas).

public class BgColorTextView extends TextView {
    public BgColorTextView(Context context) {
        super(context);
    }

    public BgColorTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public BgColorTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public BgColorTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void draw(Canvas canvas) {
        int lineCount = getLayout().getLineCount();
        Rect rect = new Rect();
        Paint paint = new Paint();
        paint.setColor(getResources().getColor(R.color.YOUR_CUSTOM_COLOR));
        for (int i = 0; i < lineCount; i++) {
            rect.top = (getLayout().getLineTop(i));
            rect.left = (int) getLayout().getLineLeft(i);
            rect.right = (int) getLayout().getLineRight(i);
            rect.bottom = (int) (getLayout().getLineBottom(i) - ((i + 1 == lineCount) ? 0 : getLayout().getSpacingAdd()));

            canvas.drawRect(rect, paint);
        }
        super.draw(canvas);
    }
}
qw1nz
  • 71
  • 1
  • 3
  • 1
    This works, but "getLayout()" might return null and crash. What should be done in this case? – android developer Feb 21 '19 at 07:45
  • 1
    you can skip draw iteration when layout is null. What will draw android if it hasn't layout? =) – qw1nz Feb 26 '19 at 14:33
  • 1
    Yes. In the end, that's what I did: https://stackoverflow.com/a/54667013/878126 . But I think there is a different issue here, that draws the background even if there is an empty line... – android developer Feb 26 '19 at 21:04
  • @androiddeveloper just check if the line is empty – cmak Mar 13 '22 at 20:13
  • @cmak How do you do it? What happens if there is an empty line in the middle, which is required to exist? – android developer Mar 15 '22 at 18:36
  • @androiddeveloper I would check in the string for double "\n" characters, then skip those lines – cmak Mar 15 '22 at 19:06
  • @cmak Can you please update the answer with the new code? – android developer Mar 15 '22 at 21:56
  • @androiddeveloper But are you sure it draws on empty lines? I just setup a test project and it didn't draw on empty lines. Can you show a screenshot? – cmak Mar 22 '22 at 13:14
  • @cmak No, I'm not sure. It was a long time ago. Can you please share the sample project on Github, so that I could check it out? – android developer Mar 23 '22 at 10:29
  • @androiddeveloper Just added an image, might take some time to get approved. Sadly the highlighting looks a bit tilted, but empty lines have no color. – cmak Mar 25 '22 at 17:45
  • @cmak Image? No, I meant here: https://github.com/ . I mean that you publish a sample code to demonstrate it working, so that I could play with it and see if indeed it works well – android developer Mar 25 '22 at 22:35
  • 1
    @androiddeveloper Alright, here's the project https://github.com/cmaked/Highlight-TextView Took me some time, first time using git with Android Studio. It's just a copy of the code in the answer. – cmak Mar 27 '22 at 14:23
  • @cmak It seems similar to what I did here: https://stackoverflow.com/a/54667013/878126 , except you didn't check if `getLayout()` is null or not and the reason according to the docs: "This can be null if the text or width has recently changes.". And it will have the same issue (I don't remember the original issue, so maybe I meant "blank line" and not "empty line"). Try to have a line with spaces in it. Example: `Hello\n\t\t\t\t\nWorld` .I can't find how to avoid this – android developer Mar 28 '22 at 19:19
2

It has been a long time but I'll put the static method below. Hopefully it will help another.

public static void setTextWithSpan(final TextView textView, int backgroundColor, String text, float lineSpacingMultiplier) {
    class BackgroundColorSpanWithPaddingAndLineSpacing implements LineBackgroundSpan {
        private float roundedCornerSize;
        private int backgroundColor;
        private int paddingSize;
        private RectF rect;

        private BackgroundColorSpanWithPaddingAndLineSpacing(int backgroundColor, int paddingSize, float roundedCornerSize) {
            super();
            this.backgroundColor = backgroundColor;
            this.paddingSize = paddingSize;
            this.roundedCornerSize = roundedCornerSize;
            this.rect = new RectF();
        }

        @Override
        public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline, int bottom, CharSequence text, int start, int end, int currentLineNumber) {
            final int textWidth = Math.round(p.measureText(text, start, end));
            final int paintColor = p.getColor();

            rect.set(left - paddingSize / 2, top - paddingSize / 4, left + textWidth + paddingSize / 2, top + textView.getTextSize() + paddingSize / 2);
            p.setColor(backgroundColor);
            c.drawRoundRect(rect, roundedCornerSize, roundedCornerSize, p);
            p.setColor(paintColor);
        }
    }

    int padding = textView.getPaddingLeft();
    int radius = padding / 2;

    SpannableStringBuilder builder = new SpannableStringBuilder(text);
    BackgroundColorSpanWithPaddingAndLineSpacing backgroundSpan = new BackgroundColorSpanWithPaddingAndLineSpacing(backgroundColor, padding, radius);
    builder.setSpan(backgroundSpan, 0, text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

    textView.setShadowLayer(padding, 0, 0, 0);
    textView.setLineSpacing(0, lineSpacingMultiplier);

    textView.setText(builder, TextView.BufferType.SPANNABLE);
}

Usage :

SpanUtils.setTextWithSpan(titleTv, android.graphics.Color.BLUE, textStr, 1.4f);

I'm sure you'll manage to modify according to your needs.

Evren Ozturk
  • 918
  • 2
  • 19
  • 39
1

A bit modified solution based on this, with option to set the color, and in kotlin:

//https://stackoverflow.com/a/30096905/878126
class BgColorTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : AppCompatTextView(context, attrs, defStyleAttr) {
    @Suppress("MemberVisibilityCanBePrivate")
    var backgroundTextColor = -0x145bfb
        set(value) {
            field = value
            invalidate()
        }

    override fun draw(canvas: Canvas) {
        val currentLayout = layout
        if (currentLayout == null) {
            super.draw(canvas)
            return
        }
        val lineCount = currentLayout.lineCount
        val rect = Rect()
        val paint = Paint()
        paint.color = backgroundTextColor
        for (i in 0 until lineCount) {
            rect.top = currentLayout.getLineTop(i)
            rect.left = currentLayout.getLineLeft(i).toInt()
            rect.right = currentLayout.getLineRight(i).toInt()
            rect.bottom = currentLayout.getLineBottom(i) - (if (i + 1 == lineCount) 0 else currentLayout.spacingAdd.toInt())
            canvas.drawRect(rect, paint)
        }
        super.draw(canvas)
    }
}
android developer
  • 114,585
  • 152
  • 739
  • 1,270
0

For those who have the same problem, you can Create a new class Extending ReplacemntSpan class and override it's draw method like this:


public class RoundedBackgroundSpan extends ReplacementSpan {

    private int backgroundColor = 0;
    private int textColor = 0;

    public RoundBackgroundSpan(Context context) {
        super();
        backgroundColor = ContextCompat.getColor(context , R.color.amber_600);
        textColor = ContextCompat.getColor(context , 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) {
        float newBottom = bottom - 2;
        float newTop = top + 2;
        RectF rect = new RectF(x , newTop , x + measureText(paint, text, start, end), newBottom );
        paint.setColor(backgroundColor);
        int CORNER_RADIUS = 8;
        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 using it in your Spannable..

0

Based on @evren-ozturk answer without textView link and better typography support

class RoundedBackgroundSpan(
    private val textColor: Int,
    private val backgroundColor: Int
) : ReplacementSpan() {

    private val additionalPadding = 4.toPx().toFloat()
    private val cornerRadius = 4.toPx().toFloat()

    override fun draw(
        canvas: Canvas,
        text: CharSequence,
        start: Int,
        end: Int,
        x: Float,
        top: Int,
        y: Int,
        bottom: Int,
        paint: Paint
    ) {
        val newTop = y + paint.fontMetrics.ascent
        val newBottom = y + paint.fontMetrics.descent
        val rect = RectF(x, newTop, x + measureText(paint, text, start, end) + 2 * additionalPadding, newBottom)
        paint.color = backgroundColor

        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
        paint.color = textColor
        canvas.drawText(text, start, end, x + additionalPadding, y.toFloat(), paint)
    }

    override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: FontMetricsInt?): Int {
        return (paint.measureText(text, start, end) + 2 * additionalPadding).roundToInt()
    }

    private fun measureText(paint: Paint, text: CharSequence, start: Int, end: Int): Float {
        return paint.measureText(text, start, end)
    }

    private fun Int.toPx(): Int {
        val resources = Resources.getSystem()
        val metrics = resources.displayMetrics
        return Math.round(this * (metrics.densityDpi / 160.0f))
    }
}
Kolchuga
  • 716
  • 6
  • 17
-1

Just being facing the same issue and I found this answer works well for me and should be able to solve your issue too. config this on your TextView in xml:

android:lineSpacingMultiplier="2"

link: How to add line spacing after every “\n” in a string in android?

It's been 11 months, so I hope you already worked it throught! Good luck!

bhw1899
  • 87
  • 4
-3

You can use lineSpacingExtra or lineSpacingMultiplier in your XML file. You can find it here: http://developer.android.com/reference/android/widget/TextView.html#attr_android%3alineSpacingExtra

And for background color you can use:

    <TextView
          android:background="#FFFF00" 
   />
Maikel
  • 41
  • 1
  • 8