Android TextView加载嵌套HTML列表(二级/三级项目符号)显示异常问题
Hey there! I’ve dealt with this exact nested bullet point issue in Android’s TextView before—it’s so annoying when sub-lists don’t render properly. Let’s walk through the most reliable fixes:
Why This Happens
Android’s native Html.fromHtml() (especially the legacy version) has terrible support for nested <ul>/<li> tags. It often ignores indentation and fails to display sub-bullets because the default Spanned text processing doesn’t handle nested list structures well.
Solution 1: Use AndroidX HtmlCompat (Basic Fix)
First, switch to AndroidX’s HtmlCompat library—it has better HTML parsing than the old system Html class. Here’s how to implement it:
import androidx.core.text.HtmlCompat override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val htmlContent = """ <ul> <li>Some bullet point text.</li> <li>Another bullet point:</li> <ul> <li>A Sub Bullet point of the above.</li> <ul> <li>A Sub sub bullet point.</li> </ul> <li>The second sub bullet point.</li> </ul> </ul> """.trimIndent() val textView = findViewById<TextView>(R.id.your_text_view_id) // Use FROM_HTML_MODE_COMPACT for better nested list support textView.text = HtmlCompat.fromHtml(htmlContent, HtmlCompat.FROM_HTML_MODE_COMPACT) }
This will fix basic sub-bullet display, but you might still notice inconsistent indentation. For that, move to the next solution.
Solution 2: Custom TagHandler for Proper Indentation
To get clean, consistent indentation for nested lists, create a custom TagHandler to handle <ul> and <li> tags manually:
Step 1: Create the NestedListTagHandler Class
import android.text.Editable import android.text.Html import android.text.Spanned import android.text.style.BulletSpan import android.text.style.LeadingMarginSpan import android.util.TypedValue import android.content.res.Resources class NestedListTagHandler : Html.TagHandler { private var indentLevel = 0 private val density = Resources.getSystem().displayMetrics.density override fun handleTag(opening: Boolean, tag: String, output: Editable, xmlReader: Html.TagReader) { when (tag.lowercase()) { "ul" -> { if (opening) indentLevel++ else indentLevel-- } "li" -> { if (opening) { // Convert dp to px for consistent spacing across devices val bulletGap = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, density).toInt() val indentSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30f, density).toInt() * indentLevel // Add indentation output.setSpan( LeadingMarginSpan.Standard(indentSize, indentSize), output.length, output.length, Spanned.SPAN_MARK_MARK ) // Add bullet point output.setSpan( BulletSpan(bulletGap), output.length, output.length, Spanned.SPAN_MARK_MARK ) } } } } }
Step 2: Use the Custom TagHandler in Your Activity
textView.text = HtmlCompat.fromHtml( htmlContent, HtmlCompat.FROM_HTML_MODE_COMPACT, null, // ImageGetter (leave null if no images) NestedListTagHandler() )
This will properly indent each sub-list level and display bullet points for all nested items.
Solution 3: Fallback to WebView (For Complex HTML)
If you need full HTML/CSS support (like custom bullet styles or advanced formatting), replace the TextView with a WebView. It handles nested lists flawlessly out of the box:
import android.webkit.WebView import android.graphics.Color override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val htmlContent = // Your nested list HTML here val webView = findViewById<WebView>(R.id.your_web_view_id) // Configure WebView to look like a TextView webView.setBackgroundColor(Color.TRANSPARENT) webView.settings.javaScriptEnabled = false webView.settings.setSupportZoom(false) // Load the HTML content webView.loadData(htmlContent, "text/html", "UTF-8") }
Final Notes
- Always use AndroidX
HtmlCompatinstead of the deprecatedHtmlclass for better compatibility. - The custom
TagHandleris the best balance between native TextView performance and correct nested list rendering. - WebView is great for complex HTML, but it has higher memory overhead than TextView.
内容的提问来源于stack exchange,提问作者Jim Clermonts

