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.
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.
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