'Extra padding on TextView with HTML contents
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:

"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).
Solution 1:[1]
The extra 'padding' you're seeing, is in fact just a line break followed by another line break:

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:

Solution 2:[2]
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;
}
Solution 3:[3]
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());
Solution 4:[4]
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.
Solution 5:[5]
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);
}
}
Solution 6:[6]
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);
Solution 7:[7]
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.
Solution 8:[8]
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.
Solution 9:[9]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
