Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get UnderlineSpan with another color in Android?

I'd like to have Spannable that looks like error in IDEs - underline with another color.

I've tried to create ColorUnderlineSpan class that extends android UnderlineSpan, but it makes all the text another color (i need to add colored underline only):

/**
 * Underline Span with color
 */
public class ColorUnderlineSpan extends android.text.style.UnderlineSpan {

    private int underlineColor;

    public ColorUnderlineSpan(int underlineColor) {
        super();
        this.underlineColor = underlineColor;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        super.updateDrawState(ds);
        ds.setColor(underlineColor);
    }
}

I've also found DynamicDrawableSpan class but i can't see canvas bounds to draw to.

It would be great to get any Spannable impl with abstract draw method with bounds argument.

like image 875
4ntoine Avatar asked Nov 14 '13 11:11

4ntoine


People also ask

How do I change the color of my SpannableString?

For example, to set a green text color you would create a SpannableString based on the text and set the span. SpannableString string = new SpannableString("Text with a foreground color span"); string. setSpan(new ForegroundColorSpan(color), 12, 28, Spanned. SPAN_EXCLUSIVE_EXCLUSIVE);

How to use Spannable in Android?

Use the Spannable. SPAN_EXCLUSIVE_INCLUSIVE flag to include inserted text, and use Spannable. SPAN_EXCLUSIVE_EXCLUSIVE to exclude the inserted text. val spannable = SpannableStringBuilder("Text is spantastic!")

How do you underline text in XML?

Just use <u> and <\u> in XML, and it's enough.


2 Answers

This isn't the prettiest solution, but it ended up working for me:

public class CustomUnderlineSpan implements LineBackgroundSpan {

int color;
Paint p;
int start, end;
public CustomUnderlineSpan(int underlineColor, int underlineStart, int underlineEnd) {
    super();
    color = underlineColor;
    this.start = underlineStart;
    this.end = underlineEnd;
    p = new Paint();
    p.setColor(color);
    p.setStrokeWidth(3F);
    p.setStyle(Paint.Style.FILL_AND_STROKE);
}

@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 lnum) {

    if (this.end < start) return;
    if (this.start > end) return;

    int offsetX = 0;
    if (this.start > start) {
        offsetX = (int)p.measureText(text.subSequence(start, this.start).toString());
    }

    int length = (int)p.measureText(text.subSequence(Math.max(start, this.start), Math.min(end, this.end)).toString());
    c.drawLine(offsetX, baseline + 3F, length + offsetX, baseline + 3F, this.p);
}

It's weird because you have to specify the character index to start and end your underlining with, but it worked for me.

like image 86
korbonix Avatar answered Sep 27 '22 18:09

korbonix


The answer @korbonix posted works fine. I've made some improvements in Kotlin and supporting multiline TextViews:

class ColorUnderlineSpan(val underlineColor: Int, val underlineStart: Int, val underlineEnd: Int): LineBackgroundSpan {

    val paint = Paint()

    init {
        paint.color = underlineColor
        paint.strokeWidth = 3.0f
        paint.style = Paint.Style.FILL_AND_STROKE
    }

    override fun drawBackground(c: Canvas?, p: Paint?, left: Int, right: Int, top: Int, baseline: Int, bottom: Int, text: CharSequence?, start: Int, end: Int, lnum: Int) {
        if (!(underlineStart < underlineEnd)) {
            throw Error("underlineEnd should be greater than underlineStart")
        }

        if (underlineStart > end || underlineEnd < start) {
            return
        }

        var offsetX = 0

        if (underlineStart > start) {
            offsetX = p?.measureText(text?.subSequence(start, underlineStart).toString())?.toInt() ?: 0
        }

        val length: Int = p?.measureText(text?.subSequence(Math.max(start, underlineStart), Math.min(end, underlineEnd)).toString())?.toInt()
            ?: 0

        c?.drawLine(offsetX.toFloat(), baseline + 3.0f, (length + offsetX).toFloat(), baseline + 3.0f, paint)
    }
}

And here is a sample usage. textText is the TextView. The text is 127 characters long and it underlines from the position 112 to the 127.

Important: For reasons I don't fully understand the span length should be set to the full length of the text. Otherwise the component doesn't even get called. Feel free to educate me on why's that.

    // Sets link color
    val spannable = SpannableString(getString(R.string.forgot_text))
    spannable.setSpan(
            ColorUnderlineSpan(Color.RED), 112, 127),
            0, 127, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
    textText.text = spannable
like image 31
Hernán Pentimalli Avatar answered Sep 27 '22 16:09

Hernán Pentimalli