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 标记。在这种情况下,不支持&lt;ul&gt;&lt;li&gt;

从源代码中我建立了一个允许的 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 代码的一个小修复。上面的函数有渲染换行符的问题。例如:如果&lt;li&gt; 标记之前是&lt;p&gt; 标记,则呈现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 实际上支持 liul 标签并使用基本的 BulletSpan(),这意味着在最新版本的 Android 中,此处发布的 Html.TagHandlersolutions 将被忽略

确保您的代码能够处理此更改。如果您想要一个比默认值更大的 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 类只在 Android N 及以上版本中可用。 是的 - 所以你需要考虑到不同的操作系统版本会有不同的行为。因此,为什么我推荐一种在 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 的答案都非常好,但我需要两者的混合版本:我需要我的解决方案不要过度格式化它并能够解析 &lt;ol&gt; 标签,因为我正在解析 &lt;h3&gt;title&lt;/h3&gt;&lt;ol&gt;&lt;li&gt;item&lt;/li&gt;&lt;li&gt;item&lt;/li&gt;&lt;li&gt;item&lt;/li&gt;&lt;/ol&gt; 之类的东西。

这是我的解决方案。

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;
                
            
        
    

请注意,由于我们会在新列表启动时重置索引值,因此如果您像 &lt;ol&gt;&lt;li&gt;1&lt;ol&gt;&lt;li&gt;1.1&lt;/li&gt;&lt;li&gt;1.2&lt;/li&gt;&lt;/ol&gt;&lt;li&gt;2&lt;/li&gt;&lt;/ol&gt; 那样嵌套列表,它将不起作用

    1
      1.1 1.2
    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 中显示项目符号。您可以将&lt;li&gt; 标记替换为&amp;#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) 以跟踪打开和关闭的 ulol 标记,并了解当前打开的标记。 此外,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】:

这是对卡西姆所说的话的确认。有碎片化。我找到了如何解决这个问题。我必须将 &lt;li&gt; 和 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

Android:遍历字符串数组 - 意图方法的逻辑需要帮助

Android TextView 上支持的 html 标签

(转载)Android自定义标签列表控件LabelsView解析

Android textAppearance的属性设置及TextView属性详解