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.
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);
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!")
Just use <u> and <\u> in XML, and it's enough.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With