Il tag dell'elenco HTML non funziona nella visualizzazione di testo Android. cosa posso fare?


99

Il tag dell'elenco HTML non funziona in TextView Android. Questo è il mio contenuto di stringa:

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

L'ho caricato in una visualizzazione di testo come questa:

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

L'output sembra un paragrafo. Cosa posso fare? C'è qualche soluzione per questo?

Modificare:

webview.loadData(str,"text/html","utf-8");

1
Dovrebbe essere text / html non texl / html.
Chloe

Risposte:


156

Come puoi vedere nel Htmlcodice sorgente della classe , Html.fromHtml(String)non supporta tutti i tag HTML. In questo caso, <ul>e<li> non sono supportati.

Dal codice sorgente ho creato un elenco di tag HTML consentiti:

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

Quindi è meglio usare WebViewe il suo loadDataWithBaseURLmetodo. Prova qualcosa di simile:

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

allora cosa posso fare per correggerlo?
Praveen

2
molto importante notare che anche alcuni attributi di questi tag "consentiti" non sono supportati. : = (
Jorgesys

2
Calmati ... Ho modificato la mia risposta, fammi sapere se funziona.
Cristian

6
Non puoi effettivamente usare un WebView allo stesso modo, quindi questa non è effettivamente una soluzione al problema.
Brill Pappin

11
Come è una soluzione? non puoi semplicemente usare un WebView è un widget molto costoso rispetto a un TextView. Non puoi semplicemente usare una WebView per ogni testo formattato che hai.
SpaceMonkey

135

Sto riscontrando lo stesso problema, quello che ho fatto è sovrascrivere il TagHandler predefinito . Questo ha funzionato per me.

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 per visualizzare il testo ...

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

[Modificare]

Kuitsi ha anche pubblicato un'ottima libreria che fa lo stesso, ottenuta da questo link SO .


Alla fine abbiamo utilizzato questo approccio. Qualsiasi tag HTML non supportato, codifichiamo nel testo noi stessi. Per ora è solo ol e ul, ma abbiamo aggiunto in stack per gestire l'annidamento di elenchi e l'archiviazione degli indici durante l'annidamento di ol. Inoltre è possibile utilizzare il parametro booleano di apertura in sostituzione del primo.
JonWillis

6
@ Aman Gautam, grazie davvero fantastici per questo! Hai idea di come tabulare il testo quando va a capo su più di 1 riga? Con questo codice dopo la seconda riga il testo è allineato con il numero anziché essere inserito in tabulazioni per mantenere il numero separato. Ho provato alcune cose ma non sono riuscito a capirlo
RyanG

stessa cosa qui, le interruzioni di riga in un elenco causano trobule con questo approccio.
Andreas Rudolph

Invece di utilizzare il carattere punto elenco incollato, potrebbe essere meglio utilizzare il carattere Unicode: output.append ("\ n \ t \ u2022");
Matt McMinn

Grazie per questo bel codice, ma non posso usarlo finché non troveremo una soluzione su come correggere il rientro di più righe
peter.bartos

68

Il progetto di esempio completo si trova su https://bitbucket.org/Kuitsi/android-textview-html-list .
L'immagine di esempio è disponibile all'indirizzo https://kuitsi.bitbucket.io/stackoverflow3150400_screen.png

Questa soluzione è la più vicina alla risposta di Masha . Alcuni codici sono presi anche dalla classe interna android.text.Html.HtmlToSpannedConverter. Supporta elenchi nidificati ordinati e non ordinati, ma i testi troppo lunghi negli elenchi ordinati sono ancora allineati con il numero di elemento anziché con il testo. Anche gli elenchi misti (ol e ul) richiedono un po 'di lavoro. Il progetto di esempio contiene l'implementazione di Html.TagHandler che viene passato a Html.fromHtml (String, ImageGetter, TagHandler) .

Modifica: per un supporto più ampio dei tag HTML, potrebbe valere la pena provare anche https://github.com/NightWhistler/HtmlSpanner .


Finora la soluzione migliore. Grazie
peter.bartos

Nessun problema di rilevamento nel repository BitBucket, quindi postando qui: è necessario aggiungere controlli qui e qui per output.length() > 0come inif (output.length() > 0 && output.charAt(output.length() - 1) != '\n')
mindeh

2
Solo per evitare che altre persone perdano 2 ore su questo, NightWhistler HtmlSpanner rimuove tutti i caratteri accentati per qualsiasi motivo sconosciuto.
EpicPandaForce

@Kuitsi grazie per la soluzione. C'è un problema con esso, quando il testo html è "<ul> <li> qualcosa </li> </ul>", l'ultima lettera in "qualcosa" non viene visualizzata nell'elenco.
Sam Berg

Questa è un'ottima soluzione, MA due inconvenienti: 1) non supporta Android ≥ 7 e 2) non mette il rientro iniziale per il primo livello dell'elenco.
soshial

24

Una piccola correzione al codice Aman Guatam. La funzione sopra ha problemi di rendering del carattere di nuova riga. Ad esempio: se before <li>tag è un <p>tag, vengono visualizzati 2 caratteri di nuova riga. Ecco il codice aggiornato:

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

Semplice ma efficace
steven0529

E l'elenco ordinato?
sviluppatore Android

13

AVVERTIMENTO

a partire dal 10 febbraio 2016 in android.text.Htmlrealtà supporta lie ulutilizza tag e utilizza una base new BulletSpan(), il che significa che nelle ultime versioni di Android ilHtml.TagHandler soluzioni pubblicate qui verranno ignorate

assicurati che il tuo codice gestisca questa modifica nel caso in cui ti aspetti un BulletSpan con uno spazio maggiore rispetto a quello predefinito, dovrai avere una sorta di soluzione che trovi / sostituisca gli intervalli


4
Ma quella nuova classe Html è disponibile solo su Android Ne versioni successive.
Sakiboy

1
Sì, quindi è necessario tenere conto del fatto che diverse versioni del sistema operativo si comporteranno in modo diverso. Ecco perché consiglio una soluzione che trovi e sostituisca BulletSpan dopo che l'HTML è stato analizzato in intervalli diversi. L'implementazione predefinita delle versioni dopo N userà un margine predefinito, puoi trovarli e sostituirli con il margine che desideri.
kassim

Tieniti sempre aggiornato.
Kai Wang

9

Soluzione diversa utilizzando LeadingMarginSpan. Gestisce elenchi ordinati e non ordinati nonché annidamenti.

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

5
Mi piace l'idea di utilizzare Spans ma non riesco a far funzionare un elenco nidificato con questo codice. Entrambe le linee output.setSpan(...)si java.lang.RuntimeException: PARAGRAPH span must start at paragraph boundary
bloccano

Grazie per la bella soluzione! Rientra anche i testi su più righe
peter.bartos

2
perché usi un vettore invece di un semplice ArrayList? un vettore è per più thread ...
sviluppatore Android

@androiddeveloper programmatore c ++, colpa mia, sentiti libero di modificare la risposta
masha


8

Se hai solo bisogno di formattare un elenco, mantienilo semplice e copia / incolla un carattere Unicode nel tuo TextView per ottenere lo stesso risultato.

• Carattere Unicode "BULLET" (U + 2022)


6

Sono venuto qui alla ricerca di implementazioni di TagHandler. Entrambe le risposte di Truong Nguyen e Aman Guatam sono molto belle, ma avevo bisogno di una versione mista di entrambe: avevo bisogno della mia soluzione per non sovrascriverla e per essere in grado di risolvere nuovamente i <ol>tag, dato che sto analizzando qualcosa come<h3>title</h3><ol><li>item</li><li>item</li><li>item</li></ol> .

Ecco la mia soluzione.

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

Nota che, poiché stiamo reimpostando il valore dell'indice ogni volta che inizia un nuovo elenco, NON funzionerà se anniderai elenchi come in <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

Con quel codice, otterrai 1, 1, 2, 3invece di 1, 1, 2, 2.


Questo codice funziona fino alla versione 23. Come farlo funzionare per 24 e superiori?
Abhinav Tyagi

3

Certo, c'è un modo per mostrare i punti elenco in Android TextView. Puoi sostituire i <li>tag con &#149;(che è il codice HTML per il punto elenco).

Se vuoi provare altre icone della lista, usa quella preferita dalla tabella è questo link;

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


Non ha funzionato per me. Invece, su Android 7.1.1 e 6.0.1, viene visualizzata una casella contrassegnata da una x al posto del punto elenco in TextView.
user1652110

3

Puoi semplicemente sostituire "li" con unicode

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

2

La risposta di Lord Voldermort è un buon punto di partenza. Tuttavia ho richiesto il oltag per visualizzare l'elenco ordinato 1. 2. 3. ....invece dei proiettili. Inoltre, i tag nidificati richiedono una gestione speciale per funzionare correttamente.

Nel mio codice, ho mantenuto pila (parentList) per tenere traccia dei aperte e chiuse ule oltag e anche di conoscere l'attuale etichetta aperta. Inoltre, a levelWiseCounterviene utilizzato per mantenere conteggi diversi in caso di oltag nidificati .

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

2

Che ne dici del codice successivo (basato su questo collegamento ):

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

1
Questa risposta ha solo leggermente diversa formattazione rispetto alla fonte originale di questo, che è stato creato per sostenere un'altra risposta a questa stessa domanda: stackoverflow.com/a/17365740/262462 :)
Kuitsi

vero. non se ne accorse.
sviluppatore Android

2

Ho avuto il problema di ricevere sempre una riga vuota dopo un elenco con la soluzione @Kuitsis. Ho aggiunto alcune righe in handleTag () e ora le righe vuote sono sparite:

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

2

Puoi usare Html.TagHandler. Di seguito può essere utilizzato per 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()));

0

questa è una conferma di quanto affermato da Kassim. c'è frammentazione. ho trovato come risolvere questo problema. devo rinominare <li>e ul in un tag personalizzato. così:

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

quindi nel mio gestore posso cercare quel customTag (che non fa nulla) e fargli fare qualcosa.

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

questo dovrebbe farlo funzionare per tutte le versioni di Android.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.