A tag Html List não funciona na visualização de texto do Android. o que eu posso fazer?

99

A tag Html List não funciona no Android TextView. Este é o meu conteúdo de string:

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>";

Eu carreguei em uma visualização de texto como esta:

textview.setText(Html.fromHtml(str));

A saída parece um parágrafo. O que eu posso fazer? Existe alguma solução para isso?

Editar:

webview.loadData(str,"text/html","utf-8");
Praveen
fonte
1
Deve ser text / html e não texl / html.
Chloe

Respostas:

156

Como você pode ver no Htmlcódigo-fonte da classe , Html.fromHtml(String)não oferece suporte a todas as tags HTML. Neste mesmo caso, <ul>e<li> não são suportados.

A partir do código-fonte, construí uma lista de tags HTML permitidas:

  • br
  • p
  • div
  • em
  • b
  • strong
  • cite
  • dfn
  • i
  • big
  • small
  • font
  • blockquote
  • tt
  • monospace
  • a
  • u
  • sup
  • sub

Então é melhor você usar WebViewe seu loadDataWithBaseURLmétodo. Experimente algo assim:

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);
Cristian
fonte
então o que posso fazer para corrigir isso?
Praveen
2
muito importante notar que alguns atributos dessas tags "permitidas" também não são suportados. : = (
Jorgesys
2
Calma ... Eu editei minha resposta, por favor me diga se funciona.
Cristian
6
Na verdade, você não pode usar um WebView da mesma maneira, então essa não é realmente uma solução para o problema.
Brill Pappin
11
Como isso é uma solução? você não pode simplesmente usar um WebView, é um widget muito caro em comparação com um TextView. Você não pode simplesmente usar um WebView para cada texto formatado que possui.
SpaceMonkey
135

Estou tendo o mesmo problema, o que fiz foi substituir o TagHandler padrão . Este funcionou para mim.

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;
                }
            }   
        }
    }
}

e para exibir o texto ...

myTextView.setText(Html.fromHtml("<ul><li>I am an Android developer</li><li>Another Item</li></ul>", null, new MyTagHandler()));

[Editar]

Kuitsi também postou uma biblioteca muito boa que faz o mesmo, obteve neste link do SO .

Aman Gautam
fonte
Usamos essa abordagem no final. Quaisquer tags HTML não suportadas, nós mesmos codificamos em texto. Por enquanto, são apenas ol e ul, mas adicionamos pilhas para lidar com o aninhamento de listas e armazenar índices ao aninhar ol. Além disso, você pode usar o parâmetro booleano de abertura no lugar do primeiro.
JonWillis de
6
@Aman Gautam muito obrigado por isso! Você tem alguma ideia de como tabular o texto quando envolve mais de 1 linha? Com este código após a segunda linha, o texto é alinhado com o número em vez de tabulado para manter o número separado. Eu tentei algumas coisas, mas não consegui descobrir
RyanG
mesma coisa aqui, quebras de linha em uma lista causam confusão com esta abordagem.
Andreas Rudolph
Em vez de usar o caractere de marcador colado, pode ser melhor usar o caractere Unicode: output.append ("\ n \ t \ u2022");
Matt McMinn
Obrigado por este código legal, mas eu não posso usá-lo até que encontremos uma solução para consertar o recuo de múltiplas linhas
peter.bartos
68

O projeto de amostra completo está localizado em https://bitbucket.org/Kuitsi/android-textview-html-list .
A imagem de amostra está disponível em https://kuitsi.bitbucket.io/stackoverflow3150400_screen.png

Esta solução está mais próxima da resposta de masha . Algum código também é obtido da classe interna android.text.Html.HtmlToSpannedConverter. Ele suporta listas aninhadas ordenadas e não ordenadas, mas textos muito longos em listas ordenadas ainda estão alinhados com o número do item em vez do texto. Listas mistas (ol e ul) também precisam de algum trabalho. O projeto de amostra contém a implementação de Html.TagHandler que é passado para Html.fromHtml (String, ImageGetter, TagHandler) .

Editar: para um suporte mais amplo de tag HTML, https://github.com/NightWhistler/HtmlSpanner também pode valer a pena tentar.

Kuitsi
fonte
Até agora a melhor solução. Obrigado
peter.bartos
Não há rastreamento de problemas no repositório BitBucket, então poste aqui: você precisa adicionar cheques aqui e aqui para output.length() > 0como emif (output.length() > 0 && output.charAt(output.length() - 1) != '\n')
mindeh
2
Apenas para evitar que outras pessoas percam 2 horas nisso, NightWhistler HtmlSpanner remove todos os caracteres acentuados por qualquer motivo desconhecido.
EpicPandaForce
@Kuitsi obrigado pela solução. Há um problema com isso, quando o texto html é "<ul> <li> alguma coisa </li> </ul>", a última letra em "alguma coisa" não é exibida na lista.
Sam Berg
Esta é uma solução muito boa, MAS duas desvantagens: 1) não suporta Android ≥ 7 e 2) não coloca recuo inicial para o primeiro nível da lista.
soshial
24

Uma pequena correção para o código Aman Guatam. A função acima tem problemas para renderizar caracteres de nova linha. Por exemplo: se before <li>tag for uma <p>tag, 2 caracteres de nova linha são renderizados. Aqui está o código atualizado:

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;
            }
        }
    }
}
Truong Nguyen
fonte
Simples, mas eficaz
steven0529
E a lista ordenada?
desenvolvedor Android
13

AVISO

a partir de 10 de fevereiro de 2016, android.text.Htmlna verdade, suporta lie ulmarca e usa um básico new BulletSpan(), o que significa que nas versões mais recentes do Android as Html.TagHandlersoluções postadas aqui serão ignoradas

certifique-se de que seu código lida com essa mudança, caso você esteja esperando um BulletSpan com uma lacuna maior do que o padrão, você terá que ter algum tipo de solução que encontre / substitua os spans

kassim
fonte
4
Mas essa nova classe Html está disponível apenas no Android Ne superior.
Sakiboy
1
Sim - então você precisa levar em conta o fato de que diferentes versões do sistema operacional se comportam de maneira diferente. É por isso que recomendo uma solução que encontre e substitua o BulletSpan depois que o HTML tiver sido analisado em diferentes extensões. A implementação padrão de versões após N usará uma margem padrão, você pode localizar e substituí-la pela margem desejada.
kassim
Mantenha-se sempre atualizado.
Kai Wang
9

Solução diferente usando LeadingMarginSpan. Lida com listas ordenadas e não ordenadas, bem como aninhamento.

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 );
        }
    }
}
masha
fonte
5
Gosto da ideia de usar Spans, mas não consigo fazer com que a lista aninhada funcione com esse código. Ambas as linhas output.setSpan(...)java.lang.RuntimeException: PARAGRAPH span must start at paragraph boundary
travam
Obrigado pela boa solução! Também recua textos de várias linhas
peter.bartos de
2
por que você usa um vetor em vez de uma simples ArrayList? um vetor é para vários segmentos ...
desenvolvedor Android
@androiddeveloper programador c ++, meu mal, fique à vontade para editar a resposta
masha
1
Eu postei como Snippet androidsnippets.com/…
Pratik Butani
8

Se você só precisa formatar uma lista, mantenha-a simples e copie / cole um caractere Unicode em seu TextView para obter o mesmo resultado.

• Caractere Unicode 'BULLET' (U + 2022)

Naku
fonte
6

Vim aqui procurando implementações do TagHandler. As respostas de Truong Nguyen e Aman Guatam são muito boas, mas eu precisava de uma versão mista de ambas: eu precisava da minha solução para não superformatá-la e ser capaz de resolver <ol>tags, já que estou analisando algo como<h3>title</h3><ol><li>item</li><li>item</li><li>item</li></ol> .

Aqui está minha solução.

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;
                }
            }
        }
    }
}

Observe que, uma vez que estamos redefinindo o valor do índice sempre que uma nova lista começa, NÃO funcionará se você aninhar listas como em <ol><li>1<ol><li>1.1</li><li>1.2</li></ol><li>2</li></ol>

  1. 1
    1. 1,1
    2. 1,2
  2. 2

Com esse código, você obteria 1, 1, 2, 3 vez de 1, 1, 2, 2.

Charlie-Blake
fonte
Este código funciona até a versão 23. Como fazê-lo funcionar para 24 e acima?
Abhinav Tyagi
3

Claro, existe uma maneira de mostrar marcadores no Android TextView. Você pode substituir <li>tags por&#149; (que é o código HTML para marcador).

Se quiser experimentar outros ícones de lista, use o preferido da tabela neste link;

http://www.ascii-code.com/

Taner
fonte
Não funcionou para mim. Em vez disso, no Android 7.1.1 e 6.0.1, uma caixa com um x aparece em vez do marcador em TextView.
user1652110
3

Você pode simplesmente substituir o "li" por 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");
        }
    }
}
folha
fonte
2

A resposta de Lord Voldermort é um bom ponto de partida. No entanto, eu olexigi a tag para exibir a lista ordenada em 1. 2. 3. ....vez de marcadores. Além disso, as tags aninhadas precisam de tratamento especial para funcionar corretamente.

No meu código, eu tenho mantido pilha (parentList) para acompanhar abertos e fechados ule oltags e também para conhecer a tag aberta atual. Além disso, a levelWiseCounteré usado para manter contagens diferentes no caso de oltags aninhadas .

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
      }
   }
Kshitij
fonte
2

E quanto ao próximo código (com base neste 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
    {
    }
  }
desenvolvedor android
fonte
1
Esta resposta tem uma formatação apenas ligeiramente diferente em comparação com a fonte original desta, que foi criada para suportar outra resposta a esta mesma pergunta: stackoverflow.com/a/17365740/262462 :)
Kuitsi
verdade. não percebi isso.
desenvolvedor Android
2

Eu tinha o problema, que sempre obtinha uma linha vazia após uma lista com a solução @Kuitsis. Eu adicionei algumas linhas em handleTag () e agora as linhas vazias sumiram:

@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);
    }
}
JensJensen
fonte
2

Você pode usar Html.TagHandler. Abaixo pode ser usado para 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•")
    }
}

e

textView.setText(Html.fromHtml(myHtmlText, null, UlTagHandler()));
Shalu TD
fonte
0

esta é uma confirmação do que kassim declarou. há fragmentação. eu descobri como resolver isso. eu tenho que renomear <li>e ul para uma marca personalizada. tão:

myHTML.replaceAll("</ul>","</customTag>").replaceAll("<ul>","<customTag>");
//likewise for li

então, em meu manipulador, posso procurar aquela customTag (que não faz nada) e fazer com que ela faça alguma coisa.

//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");
        }
    }

isso deve funcionar para todas as versões do Android.

j2emanue
fonte