11

I would like to use Canvas.drawText() to display multi-color text. More specifically, I want to highlight a substring of the text passed to the drawText() method.

The text is in the form of a SpannableString with 0 or more ForegroundColorSpan objects.

Looking at the Canvas code, it appears that a .toString() call on the passed CharSequence, means that this is not possible.

Is there an alternative way?

EDIT: The text may occasionally change (total changes, not incremental). Also, there are potentially multiple texts positioned in different unrelated locations in the custom view.

Mark
  • 7,446
  • 5
  • 55
  • 75

4 Answers4

29

Yes it is possible by using one of the Layout classes. These are helper classes for drawing text to a canvas and they support Spannables. If your text doesn't change use a StaticLayout.

Example

Add this to your custom view class

private StaticLayout layout;

put this code into your onLayout or onSizeChanged

Spannable wordtoSpan = new SpannableString("I know just how to whisper, And I know just how to cry,I know just where to find the answers");  

wordtoSpan.setSpan(new ForegroundColorSpan(Color.BLUE), 15, 30, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

TextPaint paint = new TextPaint();
paint.setTextSize(20f);
paint.setColor(Color.RED);
layout = new StaticLayout(wordtoSpan, paint, getWidth(), Alignment.ALIGN_NORMAL, 1, 0, false);

Then in your drawing method simply call

layout.draw(canvas);

In case your text changes often you can use a DynamicLayout.

Editable.Factory fac = Editable.Factory.getInstance();
Editable edit = fac.newEditable(wordtoSpan);
DynamicLayout layout = new DynamicLayout(edit,paint,getWidth(),Alignment.ALIGN_CENTER,1,0,false);

change text by using the edit object

edit.append("hello");
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
Renard
  • 6,909
  • 2
  • 27
  • 23
  • Excellent, thanks. This looks very promising. In my case, the text does change. In this case, could some kind of invalidate() call be used to achieve what I want? – Mark May 02 '12 at 09:25
  • In that case you simply use [DynamicLayout](http://developer.android.com/reference/android/text/DynamicLayout.html) or create a new layout each time the text changes. – Renard May 02 '12 at 09:27
  • ok, I will look into this. In fact, I am displaying multiple strings so I suppose one DynamicLayout for each string? Also, when the text changes, the whole text changes (i.e. it is NOT like text being edited in an EditText). Does this affect the desired solution? – Mark May 02 '12 at 09:37
  • You could assemble all those strings to one single string 'TextUtils.concat(span1, span2);' – Renard May 02 '12 at 09:41
  • They are written in seemingly random positions all over the custom view. Exact positioning is essential. – Mark May 02 '12 at 09:42
  • ok then you need multiple Layouts and use canvas.translate to position them. Are those strings always single line? Then you could use BoringLayouts. If not then I dont know what Layout class should be preferred. Both should be relatively easy to integrate. Its something you would need to test yourself. – Renard May 02 '12 at 09:49
  • Yes, always single line. I suppose BoringLayout does not support editing the text? In which case, invalidateLayout() should work? – Mark May 02 '12 at 09:58
  • invalidateLayout() does not exist. You need to use either DynamicLayouts or create new BoringLayouts for each string. I think we are getting off topic with the comments right now. You should consider creating a new question if you have problems. – Renard May 02 '12 at 10:07
  • @Renard you seem to know a lot spans. I am trying to set a ReplacementSpan extended class into a StaticLayout but nothing is drawn. Can you help me out? – mthandr Sep 01 '16 at 20:56
1

Try something like this, if you use TextView

String multiColorText = "<font color=0xff0000>Multi</font><font color=0x000000>Color</font><font color=0xccffff>Text</font>";

textView.setText(Html.fromHtml(multiColorText));

Edit : For SpannableString, check if the below helps you

Spannable WordtoSpan = new SpannableString("partial colored text"); 

WordtoSpan.setSpan(new ForegroundColorSpan(Color.BLUE), 2, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
Vinayak Bevinakatti
  • 40,205
  • 25
  • 108
  • 139
1

Whenever you write that text for that view you can set thatView.setBackgroundResource(R.drawable.multicolor); and

In multicolor.xml write

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
android:shape="rectangle"> 
    <gradient 
            android:startColor="@color/tabBgStart"
            android:endColor="@color/tabBgEnd"
            android:angle="270"/> 
</shape> 

Hope it will works definitely

To Change the text color you can use yourView.setTextColor(R.drawable.multicolor);

Anil Jadhav
  • 2,128
  • 1
  • 17
  • 31
1

i hvn't used in with Canvas. see below code how i used it in textview.

public TextView getTextClipArt1(){
    TextView textView = new TextView(context);
    Typeface tf = new MyTypeface(context, 0).getTypeface();

    Shader textShader=new LinearGradient(0, 0, 0, 30,
            new int[]{Color.GREEN,Color.BLUE},
            new float[]{0, 1}, TileMode.CLAMP);

    textView.setTypeface(tf);
    textView.getPaint().setShader(textShader);
    textView.getPaint().setStyle(Paint.Style.STROKE);
    textView.getPaint().setStrokeWidth(2);
    textView.setText("ABC");
    textView.setTextSize(30);
    textView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));

    return textView;
}

you can now draw textview as bitmap on canvas, Although i think these methods are also exist in paint class. Hope useful to you.

Hiren Dabhi
  • 3,693
  • 5
  • 37
  • 59