I want to draw a SpannedString
to a Canvas
.
SpannableString spannableString = new SpannableString("Hello World!");
ForegroundColorSpan foregroundSpan = new ForegroundColorSpan(Color.RED);
BackgroundColorSpan backgroundSpan = new BackgroundColorSpan(Color.YELLOW);
spannableString.setSpan(foregroundSpan, 1, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(backgroundSpan, 3, spannableString.length() - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
The above example was drawn using a TextView
, which in turn uses a Layout
to draw text with spans. I know that using a Layout
is the recommended way to draw text to the canvas. However, I am making my own text layout from scratch, so I need to implement this myself.
Doing something like this doesn't work
canvas.drawText(spannableString, 0, spannableString.length(), 0, 0, mTextPaint);
because drawText
only gets the text from the spannableString
, not any of the spans. The drawing colors are handled separately by TextPaint
.
How do I use canvas.drawText
(or drawTextRun
) to draw the span information (specifically foreground and background color here)?
Related
Plan for a solution
I was going to directly do a self answer but this is turning out to be more difficult than I thought. So I will post first and then add an answer whenever I can figure it out. (I would of course welcome anyone to answer first.)
Here are the pieces that I have so far:
drawTextRun
to draw the text (examples) (update: not added until API 23)getRunAdvance
to measure where to start the next text run (update: not added until API 23, use measureText
instead)drawRect
or maybe drawPath
? See here, here, and here.)TextView
, StaticLayout
, and TextLine
For most people coming to this question, you should probably use a StaticLayout
to draw your spanned text. See this answer for help with that.
However, if you actually do need to draw the spanned text yourself, then you will need to loop through all the spanned ranges and draw each one separately. You also need to measure the length of the text in each span so that you know where to start drawing the next span.
The code below handles BackgroundColorSpan
and ForegroundColorSpan
.
// set up the spanned string
SpannableString spannableString = new SpannableString("Hello World!");
ForegroundColorSpan foregroundSpan = new ForegroundColorSpan(Color.RED);
BackgroundColorSpan backgroundSpan = new BackgroundColorSpan(Color.YELLOW);
spannableString.setSpan(foregroundSpan, 1, 8, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(backgroundSpan, 3, spannableString.length() - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// draw each span one at a time
int next;
float xStart = 0;
float xEnd;
for (int i = 0; i < spannableString.length(); i = next) {
// find the next span transition
next = spannableString.nextSpanTransition(i, spannableString.length(), CharacterStyle.class);
// measure the length of the span
xEnd = xStart + mTextPaint.measureText(spannableString, i, next);
// draw the highlight (background color) first
BackgroundColorSpan[] bgSpans = spannableString.getSpans(i, next, BackgroundColorSpan.class);
if (bgSpans.length > 0) {
mHighlightPaint.setColor(bgSpans[0].getBackgroundColor());
canvas.drawRect(xStart, mTextPaint.getFontMetrics().top, xEnd, mTextPaint.getFontMetrics().bottom, mHighlightPaint);
}
// draw the text with an optional foreground color
ForegroundColorSpan[] fgSpans = spannableString.getSpans(i, next, ForegroundColorSpan.class);
if (fgSpans.length > 0) {
int saveColor = mTextPaint.getColor();
mTextPaint.setColor(fgSpans[0].getForegroundColor());
canvas.drawText(spannableString, i, next, xStart, 0, mTextPaint);
mTextPaint.setColor(saveColor);
} else {
canvas.drawText(spannableString, i, next, xStart, 0, mTextPaint);
}
xStart = xEnd;
}
The top string in the image below was drawn with the code above. The bottom string was drawn with a regular TextView
(which uses a StaticLayout
).
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