Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw a Spanned String with Canvas.drawText in Android

I want to draw a SpannedString to a Canvas.

enter image description here

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

  • How to loop through the spans in a SpannedString or SpannableString in Android
  • Is it possible to display multi-color text with one call to Canvas.drawText()?

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:

  • Draw each span range as a separate text run
  • Use drawTextRun to draw the text (examples) (update: not added until API 23)
  • Use getRunAdvance to measure where to start the next text run (update: not added until API 23, use measureText instead)
  • The background color will probably need to be drawn separately (with drawRect or maybe drawPath? See here, here, and here.)
  • Source code for TextView, StaticLayout, and TextLine
like image 395
Suragch Avatar asked Apr 07 '17 09:04

Suragch


1 Answers

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

enter image description here

like image 95
Suragch Avatar answered Oct 19 '22 03:10

Suragch