Html 列表标签在 android textview 中不起作用。我能做些啥?
Posted
技术标签:
【中文标题】Html 列表标签在 android textview 中不起作用。我能做些啥?【英文标题】:Html List tag not working in android textview. What can I do?Html 列表标签在 android textview 中不起作用。我能做些什么? 【发布时间】:2011-03-10 04:30:48 【问题描述】:html 列表标签在 android TextView 中不起作用。这是我的字符串内容:
String str="A dressy take on classic gingham in a soft, textured weave of stripes that resembles twill. Take a closer look at this one.<ul><li>Trim, tailored fit for a bespoke feel</li><li>Medium spread collar, one-button mitered barrel cuffs</li><li>Applied placket with genuine mother-of-pearl buttons</li><li>;Split back yoke, rear side pleats</li><li>Made in the U.S.A. of 100% imported cotton.</li></ul>";
我将它加载到这样的文本视图中:
textview.setText(Html.fromHtml(str));
输出看起来像一个段落。我能做些什么?有什么解决办法吗?
编辑:
webview.loadData(str,"text/html","utf-8");
【问题讨论】:
应该是 text/html 而不是 texl/html。 【参考方案1】:正如您在Html
class source code 中看到的,Html.fromHtml(String)
不支持所有 HTML 标记。在这种情况下,不支持<ul>
和<li>
。
从源代码中我建立了一个允许的 HTML 标签列表:
br
p
div
em
b
strong
cite
dfn
i
big
small
font
blockquote
tt
monospace
a
u
sup
sub
所以你最好使用WebView
和它的loadDataWithBaseURL
方法。试试这样的:
String str="<html><body>A dressy take on classic gingham in a soft, textured weave of stripes that resembles twill. Take a closer look at this one.<ul><li>Trim, tailored fit for a bespoke feel</li><li>Medium spread collar, one-button mitered barrel cuffs</li><li>Applied placket with genuine mother-of-pearl buttons</li><li>;Split back yoke, rear side pleats</li><li>Made in the U.S.A. of 100% imported cotton.</li></ul></body></html>";
webView.loadDataWithBaseURL(null, str, "text/html", "utf-8", null);
【讨论】:
那我能做些什么来纠正它? 非常重要的是要注意此“允许”标签的某些属性也不支持。 :=( 冷静...我编辑了我的答案,请告诉我它是否有效。 你实际上不能以同样的方式使用 WebView,所以这实际上不是解决问题的方法。 解决方案如何?您不能只使用 WebView,与 TextView 相比,它是一个非常昂贵的小部件。你不能只为你拥有的每个格式化文本使用一个 WebView。【参考方案2】:我遇到了同样的问题,我所做的是覆盖默认的 TagHandler。这个对我有用。
public class MyTagHandler implements TagHandler
boolean first = true;
String parent = null;
int index = 1;
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader)
if (tag.equals("ul"))
parent = "ul";
else if (tag.equals("ol"))
parent = "ol";
if (tag.equals("li"))
if (parent.equals("ul"))
if (first)
output.append("\n\t•");
first = false;
else
first = true;
else
if (first)
output.append("\n\t"+index+". ");
first = false;
index++;
else
first = true;
用于显示文本...
myTextView.setText(Html.fromHtml("<ul><li>I am an Android developer</li><li>Another Item</li></ul>", null, new MyTagHandler()));
[编辑]
Kuitsi 还发布了一个真正的 good library,它也是从 this SO link 获得的。
【讨论】:
我们最终使用了这种方法。任何不受支持的 HTML 标签,我们自己在文本中编码。现在它只是 ol 和 ul,但我们在堆栈中添加了处理列表的嵌套和在嵌套 ol 时存储索引。此外,您可以使用开头的布尔参数替换第一个。 @Aman Gautam 非常感谢这个!您是否知道如何在文本换行超过 1 行时对其进行制表符?在第二行之后使用此代码,文本与数字对齐,而不是通过制表符将数字分开。我尝试了一些事情,但我无法弄清楚 同样的事情,列表中的换行符会导致这种方法出现问题。 不要使用粘贴的项目符号,最好使用 unicode 字符:output.append("\n\t\u2022"); 感谢您提供这么好的代码,但在我们找到解决多行缩进的解决方案之前,我无法使用它【参考方案3】:完整示例项目位于https://bitbucket.org/Kuitsi/android-textview-html-list。 样品图片可在https://kuitsi.bitbucket.io/***3150400_screen.png获取。
此解决方案最接近masha's answer。一些代码也取自内部类android.text.Html.HtmlToSpannedConverter
。它支持嵌套的有序列表和无序列表,但有序列表中太长的文本仍然与项目编号而不是文本对齐。混合列表(ol 和 ul)也需要一些工作。示例项目包含传递给Html.fromHtml(String, ImageGetter, TagHandler) 的Html.TagHandler 的实现。
编辑:对于更广泛的 HTML 标签支持,https://github.com/NightWhistler/HtmlSpanner 可能也值得一试。
【讨论】:
迄今为止最好的解决方案。谢谢 为了避免其他人在这上面浪费 2 个小时,NightWhistler HtmlSpanner 会出于任何未知原因删除所有重音字符。 @Kuitsi 感谢您的解决方案。它有一个问题,当html文本是“something”时,列表中不会显示“something”中的最后一个字母。 这是一个非常好的解决方案,但有两个缺点:1) 它不支持 Android ≥ 7 和 2) 它没有为第一级添加起始缩进列表。【参考方案4】:对 Aman Guatam 代码的一个小修复。上面的函数有渲染换行符的问题。例如:如果<li>
标记之前是<p>
标记,则呈现2 个换行符。这是升级后的代码:
import org.xml.sax.XMLReader;
import android.text.Editable;
import android.text.Html.TagHandler;
public class ListTagHandler implements TagHandler
boolean first = true;
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader)
// TODO Auto-generated method stub
if (tag.equals("li"))
char lastChar = 0;
if (output.length() > 0)
lastChar = output.charAt(output.length() - 1);
if (first)
if (lastChar == '\n')
output.append("\t• ");
else
output.append("\n\t• ");
first = false;
else
first = true;
【讨论】:
有序列表呢?【参考方案5】:警告
从 Android 7 开始,android.text.Html
实际上支持 li
和 ul
标签并使用基本的 BulletSpan()
,这意味着在最新版本的 Android 中,此处发布的 Html.TagHandler
solutions 将被忽略
确保您的代码能够处理此更改。如果您想要一个比默认值更大的 BulletSpan,您可以将其替换为另一个 span:
val html = SpannableStringBuilder(HtmlCompat.fromHtml(source, HtmlCompat.FROM_HTML_MODE_COMPACT))
val bulletSpans = html.getSpans<BulletSpan>(0, html.length)
bulletSpans.forEach
val spanStart = html.getSpanStart(it)
val spanEnd = html.getSpanEnd(it)
html.removeSpan(it)
val bulletSpan = BulletSpan(gapWidthInDp, context.getColor(R.color.textColorBlack))
html.setSpan(bulletSpan, spanStart, spanEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
【讨论】:
但是这个新的 Html 类只在 AndroidN
及以上版本中可用。
是的 - 所以你需要考虑到不同的操作系统版本会有不同的行为。因此,为什么我推荐一种在 HTML 被解析为不同跨度后查找并替换 BulletSpan 的解决方案。 N 之后版本的默认实现会使用默认边距,你可以找到并替换为你想要的边距。
始终保持最新状态。【参考方案6】:
使用LeadingMarginSpan 的不同解决方案。处理有序和无序列表以及嵌套。
public class ListTagHandler implements TagHandler
private int m_index = 0;
private List< String > m_parents = new ArrayList< String >( );
@Override
public void handleTag( final boolean opening, final String tag, Editable output, final XMLReader xmlReader )
if( tag.equals( "ul" ) || tag.equals( "ol" ) || tag.equals( "dd" ) )
if( opening )
m_parents.add( tag );
else m_parents.remove( tag );
m_index = 0;
else if( tag.equals( "li" ) && !opening ) handleListTag( output );
private void handleListTag( Editable output )
if( m_parents.get(m_parents.size()-1 ).equals( "ul" ) )
output.append( "\n" );
String[ ] split = output.toString( ).split( "\n" );
int lastIndex = split.length - 1;
int start = output.length( ) - split[ lastIndex ].length( ) - 1;
output.setSpan( new BulletSpan( 15 * m_parents.size( ) ), start, output.length( ), 0 );
else if( m_parents.get(m_parents.size()-1).equals( "ol" ) )
m_index++ ;
output.append( "\n" );
String[ ] split = output.toString( ).split( "\n" );
int lastIndex = split.length - 1;
int start = output.length( ) - split[ lastIndex ].length( ) - 1;
output.insert( start, m_index + ". " );
output.setSpan( new LeadingMarginSpan.Standard( 15 * m_parents.size( ) ), start, output.length( ), 0 );
【讨论】:
我喜欢使用 Spans 的想法,但我无法让嵌套列表使用此代码。两条线output.setSpan(...)
与 java.lang.RuntimeException: PARAGRAPH span must start at paragraph boundary
崩溃
感谢您的出色解决方案!它还缩进多行文本
为什么使用 Vector 而不是简单的 ArrayList ? Vector 用于多线程...
@androiddeveloper c++ 程序员,我的错,请随意编辑答案
我已发布为 Snippet androidsnippets.com/…【参考方案7】:
如果您只需要格式化列表,请保持简单并在 TextView 中复制/粘贴一个 unicode 字符以达到相同的结果。
• Unicode 字符“BULLET”(U+2022)
【讨论】:
【参考方案8】:我来这里是为了寻找 TagHandler 的实现。 Truong Nguyen 和 Aman Guatam 的答案都非常好,但我需要两者的混合版本:我需要我的解决方案不要过度格式化它并能够解析 <ol>
标签,因为我正在解析 <h3>title</h3><ol><li>item</li><li>item</li><li>item</li></ol>
之类的东西。
这是我的解决方案。
import org.xml.sax.XMLReader;
import android.text.Editable;
import android.text.Html.TagHandler;
public class MyTagHandler implements TagHandler
boolean first = true;
String parent = null;
int index = 1;
public void handleTag(final boolean opening, final String tag,
final Editable output, final XMLReader xmlReader)
if (tag.equals("ul"))
parent = "ul";
index = 1;
else if (tag.equals("ol"))
parent = "ol";
index = 1;
if (tag.equals("li"))
char lastChar = 0;
if (output.length() > 0)
lastChar = output.charAt(output.length() - 1);
if (parent.equals("ul"))
if (first)
if (lastChar == '\n')
output.append("\t• ");
else
output.append("\n\t• ");
first = false;
else
first = true;
else
if (first)
if (lastChar == '\n')
output.append("\t" + index + ". ");
else
output.append("\n\t" + index + ". ");
first = false;
index++;
else
first = true;
请注意,由于我们会在新列表启动时重置索引值,因此如果您像 <ol><li>1<ol><li>1.1</li><li>1.2</li></ol><li>2</li></ol>
那样嵌套列表,它将不起作用
-
1
-
1.1
1.2
使用该代码,您将获得1, 1, 2, 3
而不是1, 1, 2, 2
。
【讨论】:
此代码在 23 版之前有效。如何使其适用于 24 及更高版本?【参考方案9】:您可以简单地将“li”替换为 unicodes
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader)
if (tag.equalsIgnoreCase("li"))
if (opening)
output.append("\u2022 ");
else
output.append("\n");
【讨论】:
【参考方案10】:当然,有一种方法可以在 Android TextView 中显示项目符号。您可以将<li>
标记替换为&#149;
(这是子弹的HTML 代码)。
如果您想尝试其他列表图标,请使用表中首选的一个是此链接;
http://www.ascii-code.com/
【讨论】:
对我不起作用。相反,在 Android 7.1.1 和 6.0.1 上,会出现一个带有 x 的框,而不是 TextView 中的项目符号。【参考方案11】:Lord Voldermort's answer 是一个很好的起点。但是我需要ol
标签来显示有序列表1. 2. 3. ....
而不是项目符号。此外,嵌套标签需要特殊处理才能正常工作。
在我的代码中,我维护了 stack(parentList) 以跟踪打开和关闭的 ul
和 ol
标记,并了解当前打开的标记。
此外,levelWiseCounter
用于在嵌套 ol
标签的情况下维护不同的计数。
myTextView.setText(Html.fromHtml("your string", null, new CustomTagHandler()));
。 . .
private static class CustomTagHandler implements TagHandler
int level = 0;
private LinkedList<Tag> parentList = new LinkedList<DetailFragment.CustomTagHandler.Tag>();
private HashMap<Integer, Integer> levelWiseCounter = new HashMap<Integer, Integer>();
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader)
if (tag.equalsIgnoreCase("ul") || tag.equalsIgnoreCase("ol"))
if (opening)
if (tag.equalsIgnoreCase("ul"))
parentList.push(Tag.UL);
else
parentList.push(Tag.OL);
level++;
else
if (!parentList.isEmpty())
parentList.pop();
//remove counter at that level, in any present.
levelWiseCounter.remove(level);
level--;
if (level < 0)
level = 0;
else if (tag.equalsIgnoreCase("li"))
if (opening && level > 0)
//new line check
int length = output.toString().length();
if (length > 0 && (output.toString().charAt(length - 1) == '\n'))
else
output.append("\n");
//add tabs as per current level of li
for (int i = 0; i < level; i++)
output.append("\t");
// append dot or numbers based on parent tag
if (Tag.UL == parentList.peek())
output.append("•");
else
//parent is OL. Check current level and retreive counter from levelWiseCounter
int counter = 1;
if (levelWiseCounter.get(level) == null)
levelWiseCounter.put(level, 1);
else
counter = levelWiseCounter.get(level) + 1;
levelWiseCounter.put(level, counter);
output.append(padInt(counter) + ".");
//trailing tab
output.append("\t");
/**
* Add padding so that all numbers are aligned properly. Currently supports padding from 1-99.
*
* @param num
* @return
*/
private static String padInt(int num)
if (num < 10)
return " " + num;
return "" + num;
private enum Tag
UL, OL
【讨论】:
【参考方案12】:下一个代码怎么样(基于this link):
public class TextViewHtmlTagHandler implements TagHandler
/**
* Keeps track of lists (ol, ul). On bottom of Stack is the outermost list
* and on top of Stack is the most nested list
*/
Stack<String> lists =new Stack<String>();
/**
* Tracks indexes of ordered lists so that after a nested list ends
* we can continue with correct index of outer list
*/
Stack<Integer> olNextIndex =new Stack<Integer>();
/**
* List indentation in pixels. Nested lists use multiple of this.
*/
private static final int indent =10;
private static final int listItemIndent =indent*2;
private static final BulletSpan bullet =new BulletSpan(indent);
@Override
public void handleTag(final boolean opening,final String tag,final Editable output,final XMLReader xmlReader)
if(tag.equalsIgnoreCase("ul"))
if(opening)
lists.push(tag);
else lists.pop();
else if(tag.equalsIgnoreCase("ol"))
if(opening)
lists.push(tag);
olNextIndex.push(Integer.valueOf(1)).toString();// TODO: add support for lists starting other index than 1
else
lists.pop();
olNextIndex.pop().toString();
else if(tag.equalsIgnoreCase("li"))
if(opening)
if(output.length()>0&&output.charAt(output.length()-1)!='\n')
output.append("\n");
final String parentList=lists.peek();
if(parentList.equalsIgnoreCase("ol"))
start(output,new Ol());
output.append(olNextIndex.peek().toString()+". ");
olNextIndex.push(Integer.valueOf(olNextIndex.pop().intValue()+1));
else if(parentList.equalsIgnoreCase("ul"))
start(output,new Ul());
else if(lists.peek().equalsIgnoreCase("ul"))
if(output.charAt(output.length()-1)!='\n')
output.append("\n");
// Nested BulletSpans increases distance between bullet and text, so we must prevent it.
int bulletMargin=indent;
if(lists.size()>1)
bulletMargin=indent-bullet.getLeadingMargin(true);
if(lists.size()>2)
// This get's more complicated when we add a LeadingMarginSpan into the same line:
// we have also counter it's effect to BulletSpan
bulletMargin-=(lists.size()-2)*listItemIndent;
final BulletSpan newBullet=new BulletSpan(bulletMargin);
end(output,Ul.class,new LeadingMarginSpan.Standard(listItemIndent*(lists.size()-1)),newBullet);
else if(lists.peek().equalsIgnoreCase("ol"))
if(output.charAt(output.length()-1)!='\n')
output.append("\n");
int numberMargin=listItemIndent*(lists.size()-1);
if(lists.size()>2)
// Same as in ordered lists: counter the effect of nested Spans
numberMargin-=(lists.size()-2)*listItemIndent;
end(output,Ol.class,new LeadingMarginSpan.Standard(numberMargin));
else if(opening)
Log.d("TagHandler","Found an unsupported tag "+tag);
private static void start(final Editable text,final Object mark)
final int len=text.length();
text.setSpan(mark,len,len,Spanned.SPAN_MARK_MARK);
private static void end(final Editable text,final Class<?> kind,final Object... replaces)
final int len=text.length();
final Object obj=getLast(text,kind);
final int where=text.getSpanStart(obj);
text.removeSpan(obj);
if(where!=len)
for(final Object replace : replaces)
text.setSpan(replace,where,len,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return;
private static Object getLast(final Spanned text,final Class<?> kind)
/*
* This knows that the last returned object from getSpans()
* will be the most recently added.
*/
final Object[] objs=text.getSpans(0,text.length(),kind);
if(objs.length==0)
return null;
return objs[objs.length-1];
private static class Ul
private static class Ol
【讨论】:
此答案的格式与原始来源相比仅略有不同,原始来源是为了支持同一问题的另一个答案而创建的:***.com/a/17365740/262462 :) 真的。没注意到。【参考方案13】:我遇到了问题,我总是在带有@Kuitsis 解决方案的列表之后得到一个空行。我在 handleTag() 中添加了几行,现在空行消失了:
@Override
public void handleTag(final boolean opening, final String tag, final Editable output, final XMLReader xmlReader)
if (UL_TAG.equalsIgnoreCase(tag))
if (opening) // handle <ul>
lists.push(new Ul());
else // handle </ul>
lists.pop();
if (output.length() > 0 && output.charAt(output.length() - 1) == '\n')
output.delete(output.length() - 1, output.length());
else if (OL_TAG.equalsIgnoreCase(tag))
if (opening) // handle <ol>
lists.push(new Ol()); // use default start index of 1
else // handle </ol>
lists.pop();
if (output.length() > 0 && output.charAt(output.length() - 1) == '\n')
output.delete(output.length() - 1, output.length());
else if (LI_TAG.equalsIgnoreCase(tag))
if (opening) // handle <li>
lists.peek().openItem(output);
else // handle </li>
lists.peek().closeItem(output, lists.size());
else
Log.d("TagHandler", "Found an unsupported tag " + tag);
【讨论】:
【参考方案14】:您可以使用Html.TagHandler
。下面可用于kotlin
class UlTagHandler : Html.TagHandler
override fun handleTag(
opening: Boolean, tag: String, output: Editable,
xmlReader: XMLReader
)
if (tag == "ul" && !opening) output.append("\n")
if (tag == "li" && opening) output.append("\n\t•")
和
textView.setText(Html.fromHtml(myHtmlText, null, UlTagHandler()));
【讨论】:
【参考方案15】:这是对卡西姆所说的话的确认。有碎片化。我找到了如何解决这个问题。我必须将 <li>
和 ul 重命名为自定义标签。所以:
myHTML.replaceAll("</ul>","</customTag>").replaceAll("<ul>","<customTag>");
//likewise for li
然后在我的处理程序中,我可以查找那个 customTag(它什么都不做)并让它做一些事情。
//now my handler can handle the customtags. it was ignoring them after nougat.
public class UlTagHandler implements Html.TagHandler
//for ul in nougat and up this tagHandler is completely ignored
@Override
public void handleTag(boolean opening, String tag, Editable output,
XMLReader xmlReader)
if (tag.equals("customtag2") && opening)
output.append("\n\t\u25CF\t");
if (tag.equals("customtag2") && !opening)
output.append("\n");
这应该使它适用于所有版本的 android。
【讨论】:
以上是关于Html 列表标签在 android textview 中不起作用。我能做些啥?的主要内容,如果未能解决你的问题,请参考以下文章
java.lang.ClassCastException: android.widget.RelativeLayout cannot be cast to android.widget.TextVie
java 添加指向TextView的链接。 Linkify。字体:https://stackoverflow.com/questions/4746293/android-linkify-textvie