13
\$\begingroup\$

I have a big string resource (basically the "about" text of the application) which contains styles (such as <b>, <a> etc.). The string resource is written on multiple lines, like this:

<string name="about_text"><big><b>About</b></big>\n
Lorem ipsum dolor sit amet and all that stuff...\n
\n
More stuff.</string>

Now, just like in HTML, Android treats actual new lines (not the special \n) as spaces, so the text ends up looking something like this:

About
  Lorem ipsum dolor sit
amet and all that
stuff...

  More stuff.

Which looks pretty stupid. Now, I have two options:

  1. Write the whole thing on one line. I did not go with this because:
    1. It would be a pretty big line.
    2. The text needs to be translated, and putting it all on one line would give the translators a great headache.
  2. Remove the unneeded whitespace programatically.

I went with the second solution, but I'm not sure my implementation is optimal:

CharSequence aboutText = getText(R.string.about_text);

SpannableStringBuilder ssb = new SpannableStringBuilder(aboutText);
for (int i=0; i < ssb.length()-1; i++) {
    if (ssb.charAt(i) == '\n' && ssb.charAt(i+1) == ' ') {
        ssb.replace(i+1, i+2, "");
    }
}

this.aboutText = (TextView) findViewById(R.id.about_text);

this.aboutText.setText(ssb);

It seems very hackish, but I could not find a better way. Is there a better way?

\$\endgroup\$
  • \$\begingroup\$ How about assigning a SingleLineTransformationMethod to the TextView? \$\endgroup\$ – RoToRa Jun 24 '11 at 14:17
  • \$\begingroup\$ That just makes the entire text single line. I still want new lines, I just don't want spaces and beginning of lines caused by TextView interpreting actual new lines as spaces (like in HTML). I basically want the equivalent of white-space: pre. \$\endgroup\$ – Felix Jun 24 '11 at 15:54
9
\$\begingroup\$

The answer is to put the newlines at the start of each line, directly before the text.

<string name="about_text"><big><b>About</b></big>
    \nLorem ipsum dolor sit amet and all that stuff...
    \n\nMore stuff.
</string>

Therefore all the redundant whitespace appears at the end of each line (e.g. after "About") where it doesn't affect the appearance of your text.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Man, you're a genius \$\endgroup\$ – Viacheslav Apr 15 '19 at 7:24
2
\$\begingroup\$

Since this is XML, you can use the method of ignoring whitespace using comments:

<string name="about_text"><big><b>About</b></big>\n<!--
-->Lorem ipsum dolor sit amet and all that stuff...\n<!--
-->\n<!--
-->More stuff.</string>

It may be ugly, but this technique has the advantage that the actual text content of your XML is the exact string you wanted it to be; there are no workarounds in code and you are not depending on any quirks.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

This should work:

String aboutText = getText(R.string.about_text).toString();

aboutText = aboutText.replace("\n ", "\n");

toString() is there to convert to a String object.

| improve this answer | |
\$\endgroup\$
  • 3
    \$\begingroup\$ A CharSequence is not just a String in disguise. The CharSequence returned by getText() is actually an instance of SpannableString. Thus, when calling toString() all the formatting is lost, which is not the desired effect. \$\endgroup\$ – Felix Jun 24 '11 at 14:03
  • \$\begingroup\$ I successfully tested it on my emulator, and I also checked the class and it was a String. I wonder if we have different API versions? \$\endgroup\$ – Michael K Jun 26 '11 at 1:49
  • \$\begingroup\$ Did it preserve formatting? Seems kind of strange. What's your target API version? \$\endgroup\$ – Felix Jun 27 '11 at 9:52
  • \$\begingroup\$ The Resources are clever enough not to construct a SpannableString if it's really just plain text. For more follow getText()->getResourceText()->StringBlock.get() and notice res = str and res = applyStyles(str, ... where applyStyles shortcuts to str as well if there are no styles. Note that the links are Gingerbread. \$\endgroup\$ – TWiStErRob May 25 '15 at 15:57
1
\$\begingroup\$

I'm not sure string.xml is such a good choice or long strings.

For the about dialog, I chose to have HTML page in assets and use a WebView (use strings.xml to store the name of the html file for the locale)

This is more flexible and feature rich.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ or use res/raw so translation is automatic \$\endgroup\$ – TWiStErRob May 25 '15 at 0:41
1
\$\begingroup\$

I think your use of SpannableStringBuilder makes sense. You could adopt Character.isSpaceChar() or Character.isWhitespace() if you want to easily trim additional whitespace characters.

I realize it doesn't directly address your question, but I wrote a similar function to remove trailing whitespace from a CharSequence this afternoon:

public static CharSequence trimTrailingWhitespace(CharSequence source) {

    if(source == null)
        return "";

    int i = source.length();

    // loop back to the first non-whitespace character
    while(--i >= 0 && Character.isWhitespace(source.charAt(i))) {
    }

    return source.subSequence(0, i+1);
}

Edit: I ran into a similar problem this morning where I needed to remove internal blank lines, so I took a stab at writing a method that:

  • minimizes the number of calls to replace(), and
  • handles other forms of whitespace such as linefeeds, tabs, etc.

Again, this doesn't exactly solve your problem (and as the other answers have pointed out, there are ways to work around your issue in the source), but an approach similar to this would be suitable for strings you didn't author, e.g. downloaded strings.

public static CharSequence removeExcessBlankLines(CharSequence source) {

    if(source == null)
        return "";

    int newlineStart = -1;
    int nbspStart = -1;
    int consecutiveNewlines = 0;
    SpannableStringBuilder ssb = new SpannableStringBuilder(source);
    for(int i = 0; i < ssb.length(); ++i) {
        final char c = ssb.charAt(i);
        if(c == '\n') {
            if(consecutiveNewlines == 0)
                newlineStart = i;

            ++consecutiveNewlines;
            nbspStart = -1;
        }
        else if(c == '\u00A0') {
            if(nbspStart == -1)
                nbspStart = i;
        }
        else if(consecutiveNewlines > 0) {

            // note: also removes lines containing only whitespace,
            //       or nbsp; except at the beginning of a line
            if( !Character.isWhitespace(c) && c != '\u00A0') {

                // we've reached the end
                if(consecutiveNewlines > 2) {
                    // replace the many with the two
                    ssb.replace(newlineStart, nbspStart > newlineStart ? nbspStart : i, "\n\n");
                    i -= i - newlineStart;
                }

                consecutiveNewlines = 0;
                nbspStart = -1;
            }
        }
    }

    return ssb;
}
| improve this answer | |
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.