Тег списку Html не працює в андроїдному текстовому перегляді. що я можу зробити?


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
Має бути текст / 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
Дуже важливо відзначити, що деякі атрибути цього "дозволеного" тегів теж не підтримуються. : = (
Хорхесіс

2
Заспокойтеся ... Я відредагував свою відповідь, будь ласка, повідомте, чи працює вона.
Крістіан

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

[Редагувати]

Kuitsi також опублікував дійсно гарну бібліотеку, яка робить те саме, отримала це за цим посиланням SO .


Ми використовували такий підхід врешті-решт. Будь-які непідтримувані HTML-теги ми самі кодуємо в тексті. Наразі це просто ol та ul, але ми додали в стеки для обробки вкладення списків та зберігання індексів під час введення ol. Крім того, ви можете використовувати початковий булевий параметр для заміни першого.
JonWillis

6
@Aman Gautam дуже дивовижний за це! Чи маєте ви якесь уявлення про те, як розмітити текст, коли він охоплює більше 1 рядка? За допомогою цього коду після 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 також варто спробувати.


Поки найкраще рішення. Дякую
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> щось </li> </ul>", то остання літера в "щось" не відображається в списку.
Сем Берг

Це дуже хороше рішення, Але два недоліки: 1) він не підтримує Android ≥ 7 та 2) він не ставить початковий відступ для першого рівня списку.
soshial

24

Невелике виправлення коду Амана Гуатама. Наведена вище функція має проблему виведення символу нового рядка. Наприклад: якщо перед <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

А як щодо замовленого списку?
андроїд розробник

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
чому ви використовуєте вектор замість простого ArrayList? a Vector is for multi thread ...
android developer

@androiddeveloper c ++ програміст, моє погано, не соромтесь відредагувати відповідь
masha

1
Я відправив в Сніппет androidsnippets.com / ...
Pratik Butani

8

Якщо вам потрібно лише відформатувати список, просто його скопіюйте та скопіюйте / вставте символ унікоду у TextView, щоб досягти такого ж результату.

• Символ Unicode '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
    {
    }
  }

1
Відповідь на це питання має лише трохи відрізняється форматуванням по порівнянні з початковим джерелом цього, який був створений для підтримки іншої відповіді на це ж питання: stackoverflow.com/a/17365740/262462 :)
Kuitsi

правда. цього не помічав.
андроїд розробник

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

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.