Тег Html List не работает в текстовом окне Android. Что я могу сделать?

99

Тег 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");
Правин
источник
1
Должен быть text / html, а не texl / html.
Хлоя

Ответы:

156

Как видно из Htmlисходного кода класса , Html.fromHtml(String)не поддерживаются все теги HTML. В данном случае <ul>и <li>не поддерживаются.

Из исходного кода я составил список разрешенных тегов HTML:

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

Так что лучше воспользуйтесь WebViewи его loadDataWithBaseURLметодом. Попробуйте что-то вроде этого:

String str="<html><body>A dressy take on classic gingham in a soft, textured weave of stripes that resembles twill.  Take a closer look at this one.<ul><li>Trim, tailored fit for a bespoke feel</li><li>Medium spread collar, one-button mitered barrel cuffs</li><li>Applied placket with genuine mother-of-pearl buttons</li><li>;Split back yoke, rear side pleats</li><li>Made in the U.S.A. of 100% imported cotton.</li></ul></body></html>";
webView.loadDataWithBaseURL(null, str, "text/html", "utf-8", null);
Кристиан
источник
тогда что я могу сделать, чтобы исправить это?
Praveen
2
очень важно отметить, что некоторые атрибуты этих «разрешенных» тегов также не поддерживаются. : = (
Jorgesys
2
Успокойтесь ... Я отредактировал свой ответ, дайте пожалуйста знать, работает ли.
Cristian
6
На самом деле вы не можете использовать WebView таким же образом, так что на самом деле это не решение проблемы.
Брилл Паппин
11
Как это решение? вы не можете просто использовать WebView, это очень дорогой виджет по сравнению с TextView. Вы не можете просто использовать WebView для каждого имеющегося форматированного текста.
SpaceMonkey
135

У меня та же проблема, что я сделал, это переопределил 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()));

[Редактировать]

Куитси также опубликовал действительно хорошую библиотеку, которая делает то же самое, полученная по этой ссылке SO .

Аман Гаутам
источник
В итоге мы использовали этот подход. Любые неподдерживаемые HTML-теги мы сами кодируем в тексте. На данный момент это просто ol и ul, но мы добавили в стеки для обработки вложенности списков и хранения индексов при вложении ol. Кроме того, вы можете использовать открывающий логический параметр вместо first.
JonWillis
6
@Aman Gautam очень здорово, спасибо за это! Вы знаете, как добавить табуляцию к тексту, если он занимает более одной строки? С помощью этого кода после 2-й строки текст выравнивается с номером, а не с табуляцией, чтобы число было разделено. Я попытался сделать несколько вещей, но не смог этого понять
RyanG
То же самое и здесь, разрывы строк в списке вызывают проблемы с этим подходом.
Андреас Рудольф
Вместо использования вставленного символа маркера, может быть лучше использовать символ Unicode: output.append ("\ n \ t \ u2022");
Мэтт Макминн
Спасибо за этот красивый код, но я не могу использовать его, пока мы не найдем решение, как исправить отступы в несколько строк
peter.bartos
68

Полный пример проекта находится по адресу https://bitbucket.org/Kuitsi/android-textview-html-list .
Образец изображения доступен по адресу https://kuitsi.bitbucket.io/stackoverflow3150400_screen.png.

Это решение наиболее близко к ответу Маши . Некоторый код также взят из внутреннего класса android.text.Html.HtmlToSpannedConverter. Он поддерживает вложенные упорядоченные и неупорядоченные списки, но слишком длинные тексты в упорядоченных списках по-прежнему выравниваются по номеру элемента, а не по тексту. Смешанные списки (ol и ul) тоже нуждаются в доработке. Пример проекта содержит реализацию Html.TagHandler, который передается в Html.fromHtml (String, ImageGetter, TagHandler) .

Изменить: для более широкой поддержки тегов HTML, возможно, стоит попробовать https://github.com/NightWhistler/HtmlSpanner .

Kuitsi
источник
Пока что лучшее решение. Спасибо
peter.bartos
Ни один вопрос отслеживания в Bitbucket репо, поэтому размещение здесь: вам нужно добавить проверку здесь и здесь для output.length() > 0как вif (output.length() > 0 && output.charAt(output.length() - 1) != '\n')
mindeh
2
Чтобы другие люди не тратили на это 2 часа, NightWhistler HtmlSpanner удаляет все символы с диакритическими знаками по неизвестной причине.
EpicPandaForce
@Kuitsi спасибо за решение. С этим есть одна проблема: когда html-текст имеет вид «<ul> <li> something </li> </ul>», то последняя буква в «something» не отображается в списке.
Сэм Берг,
Это очень хорошее решение, НО с двумя недостатками: 1) оно не поддерживает Android ≥ 7 и 2) не устанавливает начальный отступ для первого уровня списка.
soshial
24

Небольшое исправление в коде Aman Guatam. У функции выше есть проблема с отображением символа новой строки. Например: если перед <li>тегом является <p>тег, отображаются 2 символа новой строки. Вот обновленный код:

import org.xml.sax.XMLReader;

import android.text.Editable;
import android.text.Html.TagHandler;

public class ListTagHandler implements TagHandler {
    boolean first = true;

    @Override
    public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

        // TODO Auto-generated method stub
        if (tag.equals("li")) {
            char lastChar = 0;
            if (output.length() > 0)
                lastChar = output.charAt(output.length() - 1);
            if (first) {
                if (lastChar == '\n')
                    output.append("\t•  ");
                else
                    output.append("\n\t•  ");
                first = false;
            } else {
                first = true;
            }
        }
    }
}
Чыонг Нгуен
источник
Просто, но эффективно
steven0529 03
А как насчет упорядоченного списка?
разработчик Android
13

ПРЕДУПРЕЖДЕНИЕ

по состоянию на 10 февраля 2016 г. android.text.Htmlфактически поддерживаетli и ulтеги и использует базовые new BulletSpan(), что означает , в последних версиях Android отHtml.TagHandler решения , размещенные здесь будут игнорироваться

убедитесь, что ваш код обрабатывает это изменение, если вы ожидаете BulletSpan с большим промежутком, чем по умолчанию, вам нужно будет какое-то решение, которое выполняет поиск / замену промежутков

касим
источник
4
Но этот новый класс Html доступен только в Android Nи более поздних версиях .
Сакибой
1
Ага - значит, нужно учитывать, что разные версии ОС будут вести себя по-разному. Поэтому я рекомендую решение, которое находит и заменяет BulletSpan после того, как HTML был проанализирован на разные промежутки. Реализация по умолчанию для версий после N будет использовать поле по умолчанию, вы можете найти и заменить его на нужное поле.
Кассим
Всегда будьте в курсе.
Кай Ван
9

Другое решение с использованием 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 );
        }
    }
}
Маша
источник
5
Мне нравится идея использовать Spans, но я не могу заставить вложенный список работать с этим кодом. Обе линии output.setSpan(...)java.lang.RuntimeException: PARAGRAPH span must start at paragraph boundary
вылетели
Спасибо за хорошее решение! Он также делает отступы для многострочного текста
peter.bartos
2
почему вы используете Vector вместо простого ArrayList? Вектор предназначен для многопоточности ...
разработчик Android
@androiddeveloper программист на c ++, мой плохой, не стесняйтесь редактировать ответ
Маша
1
Я написал как Фрагмент androidsnippets.com/…
Pratik Butani
8

Если вам нужно только отформатировать список, сделайте его простым и скопируйте / вставьте символ Юникода в свой TextView для достижения того же результата.

• Символ Юникода "BULLET" (U + 2022)

Наку
источник
6

Я пришел сюда в поисках реализации TagHandler. Оба ответа Truong Nguyen и Aman Guatam очень хороши, но мне нужна была смешанная версия обоих: мне нужно было мое решение, чтобы не переформатировать его и иметь возможность повторно распределять <ol>теги, поскольку я разбираю что-то вроде<h3>title</h3><ol><li>item</li><li>item</li><li>item</li></ol> .

Вот мое решение.

import org.xml.sax.XMLReader;

import android.text.Editable;
import android.text.Html.TagHandler;

public class MyTagHandler implements TagHandler {
    boolean first = true;
    String parent = null;
    int index = 1;

    public void handleTag(final boolean opening, final String tag,
            final Editable output, final XMLReader xmlReader) {

        if (tag.equals("ul")) {
            parent = "ul";
                    index = 1;
        } else if (tag.equals("ol")) {
            parent = "ol";
                    index = 1;
        }
        if (tag.equals("li")) {
            char lastChar = 0;
            if (output.length() > 0) {
                lastChar = output.charAt(output.length() - 1);
            }
            if (parent.equals("ul")) {
                if (first) {
                    if (lastChar == '\n') {
                        output.append("\t•  ");
                    } else {
                        output.append("\n\t•  ");
                    }
                    first = false;
                } else {
                    first = true;
                }
            } else {
                if (first) {
                    if (lastChar == '\n') {
                        output.append("\t" + index + ". ");
                    } else {
                        output.append("\n\t" + index + ". ");
                    }
                    first = false;
                    index++;
                } else {
                    first = true;
                }
            }
        }
    }
}

Обратите внимание: поскольку мы сбрасываем значение индекса всякий раз, когда начинается новый список, он НЕ БУДЕТ работать, если вы вложите списки, как в <ol><li>1<ol><li>1.1</li><li>1.2</li></ol><li>2</li></ol>

  1. 1
    1. 1.1
    2. 1.2
  2. 2

С этим кодом вы получите 1, 1, 2, 3вместо 1, 1, 2, 2.

Чарли-Блейк
источник
Этот код работает до версии 23. Как заставить его работать на 24 и выше?
Абхинав Тьяги,
3

Конечно, в Android TextView есть способ отображать маркеры. Вы можете заменить <li>теги на &#149;(это HTML-код для маркера).

Если вы хотите попробовать другие значки списка, используйте предпочтительный значок из таблицы - эта ссылка;

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

Танер
источник
У меня не сработало. Вместо этого в Android 7.1.1 и 6.0.1 вместо маркера в TextView появляется прямоугольник со сквозным знаком x.
user1652110
3

Вы можете просто заменить "li" на юникоды.

    @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

Ответ лорда Волдерморта - хорошая отправная точка. Однако мне нужен olтег для отображения упорядоченного списка 1. 2. 3. ....вместо маркеров. Кроме того, для правильной работы вложенных тегов требуется особая обработка.

В моем коде, я поддерживал стек (parentList) следить за открытыми и закрытыми ulи olтег , а также знать текущий открытый тег. Кроме того, a 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
      }
   }
Кшитидж
источник
2

Как насчет следующего кода (по этой ссылке ):

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
    {
    }
  }
разработчик Android
источник
1
Этот ответ имеет лишь немного другое форматирование по сравнению с исходным источником этого, который был создан для поддержки другого ответа на тот же вопрос: stackoverflow.com/a/17365740/262462 :)
Куитси 02
правда. не заметил этого.
разработчик Android
2

У меня была проблема, что после списка с решением @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);
    }
}
JensJensen
источник
2

Вы можете использовать Html.TagHandler. Ниже можно использовать для котлина

    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()));
Шалу ТД
источник
0

это подтверждение того, что сказал касим. есть фрагментация. Я нашел, как решить эту проблему. мне нужно переименовать <li>и ul в настраиваемый тег. так:

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

затем в моем обработчике я могу найти этот customTag (который ничего не делает) и заставить его что-то делать.

//now my handler can handle the customtags. it was ignoring them after nougat. 
 public class UlTagHandler implements Html.TagHandler {
        //for ul in nougat and up this tagHandler is completely ignored
        @Override
        public void handleTag(boolean opening, String tag, Editable output,
                              XMLReader xmlReader) {

            if (tag.equals("customtag2") && opening)
            output.append("\n\t\u25CF\t");
        if (tag.equals("customtag2") && !opening)
            output.append("\n");
        }
    }

это должно заставить его работать для всех версий Android.

j2emanue
источник