La balise Html List ne fonctionne pas dans Android Textview. Que puis-je faire?

99

La balise Html List ne fonctionne pas dans Android TextView. Voici le contenu de ma chaîne:

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

Je l'ai chargé dans une vue texte comme celle-ci:

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

La sortie ressemble à un paragraphe. Que puis-je faire? Y a-t-il une solution pour cela?

Éditer:

webview.loadData(str,"text/html","utf-8");
Praveen
la source
1
Doit être text / html et non texl / html.
Chloe

Réponses:

156

Comme vous pouvez le voir dans le Htmlcode source de la classe , Html.fromHtml(String)ne prend pas en charge toutes les balises HTML. Dans ce cas précis, <ul>et <li>ne sont pas pris en charge.

À partir du code source, j'ai construit une liste de balises HTML autorisées:

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

Vous feriez donc mieux d'utiliser WebViewet sa loadDataWithBaseURLméthode. Essayez quelque chose comme ceci:

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
la source
alors que puis-je faire pour le rectifier?
Praveen
2
très important de noter que certains attributs de ces balises «autorisées» ne sont pas non plus pris en charge. : = (
Jorgesys
2
Calmez-vous ... J'ai édité ma réponse, faites-moi savoir si cela fonctionne.
Cristian
6
Vous ne pouvez pas réellement utiliser une WebView de la même manière, donc ce n'est pas vraiment une solution à un problème.
Brill Pappin
11
Comment est-ce une solution? vous ne pouvez pas simplement utiliser un WebView, c'est un widget très cher par rapport à un TextView. Vous ne pouvez pas simplement utiliser une WebView pour chaque texte formaté que vous avez.
SpaceMonkey
135

J'ai le même problème, ce que j'ai fait est de remplacer le TagHandler par défaut . Celui-ci a fonctionné pour moi.

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

et pour afficher le texte ...

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

[Éditer]

Kuitsi a également publié une très bonne bibliothèque qui fait de même, obtenue à partir de ce lien SO .

Aman Gautam
la source
Nous avons utilisé cette approche à la fin. Toutes les balises HTML non prises en charge, nous codons nous-mêmes dans le texte. Pour l'instant, c'est juste ol et ul, mais nous avons ajouté des piles pour gérer l'imbrication des listes et stocker les index lors de l'imbrication de ol. De plus, vous pouvez utiliser le paramètre booléen d'ouverture en remplacement de first.
JonWillis
6
@Aman Gautam merci très génial pour cela! Avez-vous une idée de la façon de tabuler le texte lorsqu'il englobe plus d'une ligne? Avec ce code après la deuxième ligne, le texte est aligné avec le nombre plutôt que tabulé pour garder le nombre séparé. J'ai essayé quelques choses mais je ne pouvais pas le comprendre
RyanG
même chose ici, les sauts de ligne dans une liste provoquent des trobules avec cette approche.
Andreas Rudolph le
Au lieu d'utiliser le caractère puce collé, il peut être préférable d'utiliser le caractère unicode: output.append ("\ n \ t \ u2022");
Matt McMinn
Merci pour ce bon code, mais je ne peux pas l'utiliser tant que nous ne trouverons pas une solution pour corriger l'indentation sur plusieurs lignes
peter.bartos
68

L'exemple de projet complet se trouve à l' adresse https://bitbucket.org/Kuitsi/android-textview-html-list .
Un exemple d'image est disponible sur https://kuitsi.bitbucket.io/stackoverflow3150400_screen.png

Cette solution est la plus proche de la réponse de Masha . Une partie du code est également tirée de la classe interne android.text.Html.HtmlToSpannedConverter. Il prend en charge les listes ordonnées et non ordonnées imbriquées, mais les textes trop longs dans les listes ordonnées sont toujours alignés sur le numéro d'élément plutôt que sur le texte. Les listes mixtes (ol et ul) nécessitent également du travail. L'exemple de projet contient l'implémentation de Html.TagHandler qui est passé à Html.fromHtml (String, ImageGetter, TagHandler) .

Edit: Pour une prise en charge plus large des balises HTML, https://github.com/NightWhistler/HtmlSpanner peut également valoir la peine d'être essayé.

Kuitsi
la source
Jusqu'à présent, la meilleure solution. Merci
peter.bartos
Aucun problème de suivi dans le dépôt BitBucket, alors publiez ici: vous devez ajouter des chèques ici et ici pour output.length() > 0comme àif (output.length() > 0 && output.charAt(output.length() - 1) != '\n')
mindeh
2
Juste pour éviter que d'autres personnes ne perdent 2 heures à ce sujet, NightWhistler HtmlSpanner supprime tous les caractères accentués pour une raison inconnue.
EpicPandaForce
@Kuitsi merci pour la solution. Il y a un problème avec cela, lorsque le texte html est "<ul> <li> quelque chose </li> </ul>", alors la dernière lettre de "quelque chose" n'est pas affichée dans la liste.
Sam Berg
C'est une très bonne solution, MAIS deux inconvénients: 1) il ne prend pas en charge Android ≥ 7 et 2) il ne met pas de retrait de départ pour le premier niveau de liste.
soshial
24

Une petite correction au code Aman Guatam. La fonction ci-dessus a un problème de rendu du caractère de nouvelle ligne. Par exemple: si avant la <li>balise est une <p>balise, 2 caractères de nouvelle ligne sont rendus. Voici le code mis à jour:

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
la source
Simple mais efficace
steven0529
Qu'en est-il de la liste ordonnée?
développeur android
13

AVERTISSEMENT

à partir du 10 février 2016 android.text.Htmlprend en charge li, ulbalise et utilise un élément de base new BulletSpan(), ce qui signifie que dans les dernières versions d'Android, leHtml.TagHandler solutions publiées ici seront ignorées

assurez-vous que votre code gère ce changement au cas où vous attendez un BulletSpan avec un écart plus grand que la valeur par défaut, vous devrez avoir une sorte de solution qui effectue une recherche / remplacement des étendues

Kassim
la source
4
Mais cette nouvelle classe Html n'est disponible que sur Android Net au-dessus.
Sakiboy
1
Oui, vous devez donc tenir compte du fait que les différentes versions du système d'exploitation se comporteront différemment. C'est pourquoi je recommande une solution qui trouve et remplace le BulletSpan après que le HTML a été analysé dans différentes étendues. L'implémentation par défaut des versions après N utilisera une marge par défaut, vous pouvez les trouver et les remplacer par la marge souhaitée.
kassim
Restez toujours à jour.
Kai Wang
9

Solution différente utilisant LeadingMarginSpan. Gère les listes ordonnées et non ordonnées ainsi que l'imbrication.

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
la source
5
J'aime l'idée d'utiliser Spans mais je ne peux pas faire fonctionner la liste imbriquée avec ce code. Les deux lignes se output.setSpan(...)java.lang.RuntimeException: PARAGRAPH span must start at paragraph boundary
bloquent
Merci pour la belle solution! Il
indente
2
pourquoi utilisez-vous un vecteur au lieu d'une simple ArrayList? un vecteur est pour multi threads ...
développeur android
@androiddeveloper programmeur c ++, mon mauvais, n'hésitez pas à modifier la réponse
masha
1
J'ai posté comme Snippet androidsnippets.com/...~~V~~singular~~3rd
Pratik Butani
8

Si vous avez seulement besoin de formater une liste, restez simple et copiez / collez un caractère Unicode dans votre TextView pour obtenir le même résultat.

• Caractère Unicode "BULLET" (U + 2022)

Naku
la source
6

Je suis venu ici à la recherche d'implémentations de TagHandler. Les réponses de Truong Nguyen et d'Aman Guatam sont très belles, mais j'avais besoin d'une version mixte des deux: j'avais besoin de ma solution pour ne pas la sur-formater et pour pouvoir résoudre les <ol>balises, puisque j'analyse quelque chose comme <h3>title</h3><ol><li>item</li><li>item</li><li>item</li></ol>.

Voici ma solution.

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

Notez que, puisque nous réinitialisons la valeur d'index chaque fois qu'une nouvelle liste démarre, cela NE fonctionnera PAS si vous imbriquez des listes comme dans <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

Avec ce code, vous obtiendrez 1, 1, 2, 3au lieu de 1, 1, 2, 2.

Charlie-Blake
la source
Ce code fonctionne jusqu'à la version 23. Comment le faire fonctionner pour 24 et plus?
Abhinav Tyagi le
3

Bien sûr, il existe un moyen d'afficher des puces dans Android TextView. Vous pouvez remplacer les <li>balises par&#149; (qui est le code HTML de la puce).

Si vous voulez essayer d'autres icônes de liste, utilisez celle que vous préférez dans le tableau est ce lien;

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

Taner
la source
Cela n'a pas fonctionné pour moi. Au lieu de cela, sur Android 7.1.1 et 6.0.1, une boîte avec un x à travers elle apparaît à la place de la puce dans TextView.
user1652110
3

Vous pouvez simplement remplacer le "li" par 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");
        }
    }
}
feuille
la source
2

La réponse de Lord Voldermort est un bon point de départ. Cependant, j'avais besoin d'une olbalise pour afficher la liste ordonnée 1. 2. 3. ....au lieu de puces. De plus, les balises imbriquées nécessitent un traitement spécial pour fonctionner correctement.

Dans mon code, je l' ai maintenu pile (parentList) de garder une trace de ouverte et fermée ulet olbalises et aussi de connaître l'étiquette actuelle ouverte. En outre, a levelWiseCounterest utilisé pour conserver différents décomptes en cas de olbalises imbriquées .

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
la source
2

Que diriez-vous du code suivant (basé sur ce lien ):

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
    {
    }
  }
développeur android
la source
1
Cette réponse n'a qu'un formatage légèrement différent par rapport à la source originale de ceci, qui a été créée pour prendre en charge une autre réponse à cette même question: stackoverflow.com/a/17365740/262462 :)
Kuitsi
vrai. n'a pas remarqué cela.
développeur android
2

J'ai eu le problème, que j'avais toujours une ligne vide après une liste avec la solution @Kuitsis. J'ai ajouté quelques lignes dans handleTag () et maintenant les lignes vides ont disparu:

@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
la source
2

Vous pouvez utiliser Html.TagHandler. Ci-dessous peut être utilisé pour 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•")
    }
}

et

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

ceci est une confirmation de ce que Kassim a déclaré. il y a fragmentation. j'ai trouvé comment résoudre ce problème. je dois renommer <li>et ul en une balise personnalisée. alors:

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

puis dans mon gestionnaire je peux rechercher ce customTag (qui ne fait rien) et lui faire faire quelque chose.

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

cela devrait le faire fonctionner pour toutes les versions d'Android.

j2emanue
la source