40

I have this TextView:

<TextView
    android:id="@+id/issue_journal_item_notes"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignLeft="@+id/issue_journal_item_details"
    android:layout_below="@+id/issue_journal_item_details"
    android:background="@drawable/journal_item_notes_background"
    android:padding="8dp"
    android:text="issue_journal_item_notes"
    android:textIsSelectable="true" />

I'm filling this with:

String html = "<p>Hi,<br/>Do you think you could get a logcat during the crash? That seems really strange, especially the fact that it makes Android reboot.<br/>You can get the SDK here: http://developer.android.com/sdk/index.html<br/>(needed for logcat)</p>";
theTextView.setText(Html.fromHtml(html));

This results in:

Resulting screenshot

"Assignee ..." is another TextView.
My TextView is the one with the grey background. Its bounds are clearly seen with the very light grey. The left darker gray bar to the left is part of the background, so it's also the TextView

We can clearly see the 8dp square padding. However, what's the empty space at the bottom? It's some kind of padding, but I havent set this in XML nor in code!

In case somebody asks, I need HTML support, because unlike in the screenshot shown above, the contents may have some HTML content (<pre>, <i>, <b>, etc).

Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125

9 Answers9

70

The extra 'padding' you're seeing, is in fact just a line break followed by another line break:

enter image description here

When you dive into the Html.fromHtml(...) implementation, you'll come across the following method that handles paragraph tags:

private static void handleP(SpannableStringBuilder text) {
    int len = text.length();

    if (len >= 1 && text.charAt(len - 1) == '\n') {
        if (len >= 2 && text.charAt(len - 2) == '\n') {
            return;
        }

        text.append("\n");
        return;
    }

    if (len != 0) {
        text.append("\n\n");
    }
}

Above snippet was takes from the Android 4.2.2 source. The logic is quite straightforward and basically ensures that every paragraph tag ends with \n\n, to give a visual gap between two element blocks. It means the framework will not into account whether the whole Html text consists of only a single paragraph (your case), or multiple, successive paragaps - there will always be two line breaks at the end of a transformed paragraph.

That being said, if you know you're always dealing with a single paragraph, the easiest solution is to remove that wrapping paragraph before feeding it to Html.fromHtml(...). This is pretty much what was proposed in one of the other answers.

Now, since you mentioned this isn't really an option, the alternative would be to 'trim' the result of Html.fromHtml(...), removing any trailing white spaces. Android returns a Spanned (usually this is a SpannableStringBuilder object), which, unfortunately, doesn't come with a built-in trim() method. It's not too tricky to come up with your own though, or borrow one of several implementations available out there.

A basic implementation for a trim() method would like somewhat like this:

public static CharSequence trim(CharSequence s, int start, int end) {
    while (start < end && Character.isWhitespace(s.charAt(start))) {
        start++;
    }

    while (end > start && Character.isWhitespace(s.charAt(end - 1))) {
        end--;
    }

    return s.subSequence(start, end);
}

To use it, change your original code to:

String html = "<p>Hi,<br/>Do you think you could get a logcat during the crash? That seems really strange, especially the fact that it makes Android reboot.<br/>You can get the SDK here: http://developer.android.com/sdk/index.html<br/>(needed for logcat)</p>";
CharSequence trimmed = trim(Html.fromHtml(html));
theTextView.setText(trimmed);

And voilà, before and after:

enter image description here

MH.
  • 45,303
  • 10
  • 103
  • 116
  • `Html.fromHtml` returns a Spanned, which can't be trimmed like a `CharSequence`. Also, my text is already trimmed: `

    text

    ` doesn't contain any whitespace character, but it has the extra padding at the bottom.
    – Benoit Duffez May 26 '13 at 15:43
  • 10
    It sounds like you dismissed my suggestion without giving it a try - that's a little ungrateful considering the time I put in and all the ready-to-use code I've given. Please note that `Spanned implements CharSequence` (like all classes in Java representing textual content), so anything that works on the latter will also work on the former. Also, trimming the styled/marked up `CharSequence` (or `Spanned` if you like) is different from trimming the raw input `String`. I suggest you try above first, which should take less than 1 minute... – MH. May 26 '13 at 19:08
  • My wording was bad and I apologize for that. I am very grateful that you put a lot of effort into this. However, what I meant is that if I convert my HTML code to a dumb `CharSequence` and simply remove the extra `\n` added by Android, I will lose all formatting. You solution does work, but at the cost of HTML formatting, which in fact just put the text in the `TextView`. I agree that you did find the glitch with the extra double `\n` at the end which are unnecessary. However what you proposed, unless I did wrong, doesn't do what I want to do. – Benoit Duffez May 26 '13 at 21:11
  • Sounds like you figured out what I was trying to point out: trimming the result of `Html.fromHtml()` should leave the actual content (with all its formatting) intact, while removing the extra spacing at the bottom at the same time. Glad to hear it worked out alright - no harm done. :) – MH. May 27 '13 at 02:57
  • Yep exactly. After a bit of rework it worked like a charm. I'm very grateful, thanks a lot! – Benoit Duffez May 27 '13 at 08:48
  • I did it like this: textview.setText(Html.fromHtml(theHTMLText).toString().trim()); – Boy May 13 '14 at 09:13
  • @Boy: by calling `toString()` you have effectively removed all html markup from the result of the `Html.fromHtml()` call. That is, if you had any bold, italics, text colours etc, it'll all be gone and plain text now. – MH. May 14 '14 at 08:49
  • still not sure how to do trimming on a Spanned object though – Boy May 14 '14 at 09:33
  • @Boy: the generic `trim()` method as outlined in my answer should do the trick. ;) – MH. May 19 '14 at 04:28
  • @MH. nope, because 'subSequence' does not return a spanned – Boy May 20 '14 at 08:03
  • @Boy: What does your context look like? Do you *really* need a `Spanned` instance? Since `Spanned` is an implementation of `Charsequence`, perhaps you can make your code more generic, or (but less pretty) include a cast. – MH. May 21 '14 at 08:21
  • 1
    @Boy: Yep, shouldn't make a difference. Heck, you could even assign it to an `Object` instance ... (but please don't). – MH. May 24 '14 at 06:29
  • What are start and end? 0 and string.length? – Aman Verma Dec 01 '20 at 17:34
  • If you want to trim the leading/trailing whitespace from the full sequence, then yes. I guess you should be able to self-answer that based on the implementation? – MH. Dec 01 '20 at 21:22
20

You can also use below code

 myTextView.setText(noTrailingwhiteLines(html));

private CharSequence noTrailingwhiteLines(CharSequence text) {

    while (text.charAt(text.length() - 1) == '\n') {
        text = text.subSequence(0, text.length() - 1);
    }
    return text;
}
Kirtikumar A.
  • 4,140
  • 43
  • 43
  • This is the answer that helped me with this problem. I thought it was a css problem as well... That's a really nasty trick on android's part to add those extra \n's – bwoogie Nov 17 '15 at 16:53
9

Html.fromHtml(html) returns Spannable so you can convert this to string by calling toString() then trim the string then set it to textview

String html = "<p>Hi,<br/>Do you think you could get a logcat during the crash? That seems really strange, especially the fact that it makes Android reboot.<br/>You can get the SDK here: http://developer.android.com/sdk/index.html<br/>(needed for logcat)</p>";
theTextView.setText(Html.fromHtml(html).toString().trim());
Praveena
  • 6,340
  • 2
  • 40
  • 53
2

As per Android documentation the Html.fromHtml() uses FROM_HTML_MODE_LEGACY for converting HTML to text. But FROM_HTML_MODE_LEGACY adds two newline characters in between.

From Android N onwards there is FROM_HTML_MODE_COMPACT which by default uses only one newline character.

/**
     * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level
     * elements with blank lines (two newline characters) in between. This is the legacy behavior
     * prior to N.
     */
    public static final int FROM_HTML_MODE_LEGACY = 0x00000000;

    /**
     * Flags for {@link #fromHtml(String, int, ImageGetter, TagHandler)}: Separate block-level
     * elements with line breaks (single newline character) in between. This inverts the
     * {@link Spanned} to HTML string conversion done with the option
     * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}.
     */
    public static final int FROM_HTML_MODE_COMPACT =
            FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH
            | FROM_HTML_SEPARATOR_LINE_BREAK_HEADING
            | FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM
            | FROM_HTML_SEPARATOR_LINE_BREAK_LIST
            | FROM_HTML_SEPARATOR_LINE_BREAK_DIV
            | FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE;

So for converting HTML to plaintext we can use,

HtmlCompat.fromHtml(htmlString, HtmlCompat.FROM_HTML_MODE_COMPACT);
Saikrishna Rajaraman
  • 3,205
  • 2
  • 16
  • 29
1

Try this, by removing the <p> tag.

String html = "Hi,<br/>Do you think you could get a logcat during the crash? That seems really strange, especially the fact that it makes Android reboot.<br/>You can get the SDK here: http://developer.android.com/sdk/index.html<br/>(needed for logcat)";
theTextView.setText(Html.fromHtml(html));

Hope it works.

Oam
  • 912
  • 6
  • 7
  • I thought about that, but in fact my text comes from a Textile markup text, which transforms any line of text followed by nothing or an empty line to that line inside a `

    ` tag. So I don't want to remove the paragraph tag, because 1) it is required in some cases (one `

    ` followed by a `

    `, for example) and 2) because it should just work.

    – Benoit Duffez May 16 '13 at 11:11
1

Processing HTML tags lead to newlines \n being added and sometimes multiple \n following each other. The result will be a string with multiple newlines between the text (specially if the HTML string contains <br/> tag).

The following method removes leading and trailing newlines that are resulted from <br/> tag.

Also, reduces multiple new lines between text (API > 24).

/**
 *
 * @param htmlString
 * @return Spanned represents the html string removed leading and trailing newlines. Also, reduced newlines resulted from processing HTML tags (for API >24)
 */
public static Spanned processHtmlString(String htmlString){

    // remove leading <br/>
    while (htmlString.startsWith("<br/>")){

        htmlString = htmlString.replaceFirst("<br/>", "");
    }

    // remove trailing <br/>
    while (htmlString.endsWith("<br/>")){

        htmlString =  htmlString.replaceAll("<br/>$", "");
    }

    // reduce multiple \n in the processed HTML string 
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

        return Html.fromHtml(htmlString,  FROM_HTML_MODE_COMPACT);
    }else{

        return Html.fromHtml(htmlString);
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Islam Assi
  • 991
  • 1
  • 13
  • 18
1

Kotlin code

  fun trimTrailingWhitespace(source: CharSequence?): CharSequence? {
        if (source == null) return ""
        var i = source.length

        // loop back to the first non-whitespace character
        while (--i >= 0 && Character.isWhitespace(source[i])) {
        }
        return source.subSequence(0, i + 1)
    }

Usage

val trimmedMsg = trimTrailingWhitespace(
                                Html.fromHtml(
                                   message,
                                    Html.FROM_HTML_MODE_LEGACY
                                )
                            )
 binding.tvBotText.text = trimmedMsg
MarGin
  • 2,078
  • 1
  • 17
  • 28
0

What is the layout around your TextView? What I remember is that some layouts ignore the height preference of your component and simply make it fit.

Jeroen Peeters
  • 1,974
  • 1
  • 20
  • 24
  • That's a `RelativeLayout` with layout height/width, background, and another 8dp padding (the greyest at both edges of the screenshot) – Benoit Duffez May 16 '13 at 11:01
0

https://stackoverflow.com/a/16745540/2761728 shows that Android will add extra \n at the end of your html tag. The answer shows a way to trim the unwanted \n at the end of your content, but it won't remove the \n between each tag Android insert for you.

For my case I have this html content:

<p>This is the first paragraph.</p><p>And this is second paragraph.</p>

After Html.fromHtml , the text will become

This is the first paragraph. 


And this is second paragraph. 


I tried some method and find that the \n can be avoid with the flag Html provided. I combine the code with trim and come out with this solution:

trimLastBreak(HtmlCompat.fromHtml(htmlContent, FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH))

And the result is perfect:

This is the first paragraph.
And this is second paragraph.
Anthonyeef
  • 2,595
  • 1
  • 27
  • 25