Monday, November 7, 2011

Format resource string with html and interpolation operator(Android)

Commonly used methods for showing text on TextView

/* call final void setText(CharSequence text) finally
public final void setText(int resid) {
final void setText(int resid)

String implements from CharSequence, so we can pass String to this method
final void setText(CharSequence text)


Show HTML appearance on TextView

e.g. we’re adding underline for a word.

we have following resource xml:
<string name="mytext1">there is a <u>underline</u> word</string>

java code:
tv.setText(R.string.mytext1); //worked! show underline
tv2.setText("there is a <u>underline</u> word"); //not work

screen capture of code above:


Why 2nd line code didn’t work?

2nd line code did not work because the char[] was transformed to CharWrapper first , and transformed to String inner CharWrapper later. See the process when set a char[] to TextView:
 public final void setText(char[] text, int start, int len) {
... ...

setText(mCharWrapper, mBufferType, false, oldlen);

private static class CharWrapper
implements CharSequence, GetChars, GraphicsOperations {
... ...

public String toString() {
return new String(mChars, mStart, mLength); }

In <>, all setText() overrides calls
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen)

This function checks whether the parameter text was a instance of Spanned. If not, a non-mark up string will be shown in TextView.
Since class String does not implement from Spanned. so, no mark up effect, underline in this example appear in TextView.

How to make String contains HTML show appearance as I expect?

use static method from Html module
static Spanned fromHtml(String source)
//Returns displayable styled text from the provided HTML string.

Why code in 1st line work?

Let’s trace the call stack of the code. Follow the function name in red.

public final void setText(int resid) {

public CharSequence getText(int id) throws NotFoundException {
CharSequence res = mAssets.getResourceText(id);

… …

final CharSequence getResourceText(int ident) {
synchronized (this) {
TypedValue tmpValue = mValue;
int block = loadResourceValue(ident, tmpValue, true);
… …

private native final int loadResourceValue(int ident, TypedValue outValue,
boolean resolve);

OMG. I’m getting headache. We’ve arrive the C level… We should do something smart .

As we known, objects implemented from Spanned will show mark up effect, while others shows plain text. Let’s just print out whether the  returned object from getText(resid) was implemented from Spanned.

Test code:
CharSequence cs = this.getResources().getText(R.string.mytext1);
Log.e(TAG, "spanned:" + (cs instanceof Spanned)); // prints True

** don’t use this.getResources().getString(…) here, because getString() will return String object which was not implemented from Spanned.

Well, code “getResources().getText(resid)” returned a Spanned stuff, that’s the answer why 1st line code show HTML effect.

Format Spanned Strings with parameters

Consider we have a resource string like this:
<string name=”mytext3>we have %d <u>underline</u> words!</string>

there’s a “%d” interpolation operator in string. As I know, there’s no format related method for Charsequence object. So I have to transform it to String, and format it with an integer.
String str = this.getResources().getString(R.string.mytext3, 8);


the integer 8 was inserted into the string, but underline style was missing. It’s because during the formatting process, HTML tags was ignored and dropped.

Mind storm… Solution hereSmile with tongue out
SpannedString sstr = SpannedString.valueOf(this.getResources().getText(R.string.mytext3));
String str = String.format(Html.toHtml(sstr), 8);