Znacznik listy HTML nie działa w widoku tekstowym systemu Android. co mogę zrobić?

99

Znacznik listy HTML nie działa w Android TextView. Oto zawartość mojego ciągu:

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

Załadowałem go w widoku tekstowym takim jak ten:

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

Wynik wygląda jak akapit. Co mogę zrobić? Czy jest na to jakieś rozwiązanie?

Edytować:

webview.loadData(str,"text/html","utf-8");
Praveen
źródło
1
Powinien to być tekst / html, a nie texl / html.
Chloe,

Odpowiedzi:

156

Jak widać w Htmlkodzie źródłowym klasy , Html.fromHtml(String)nie obsługuje wszystkich tagów HTML. W tym przypadku <ul>i <li>nie są obsługiwane.

Z kodu źródłowego zbudowałem listę dozwolonych tagów HTML:

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

Więc lepiej użyj WebViewi jego loadDataWithBaseURLmetody. Spróbuj czegoś takiego:

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
źródło
co mogę zrobić, aby to naprawić?
Praveen
2
bardzo ważne jest, aby pamiętać, że niektóre atrybuty tych „dozwolonych” tagów również nie są obsługiwane. : = (
Jorgesys
2
Uspokój się ... zredagowałem odpowiedź, daj mi znać, czy działa.
Cristian
6
W rzeczywistości nie możesz użyć WebView w ten sam sposób, więc w rzeczywistości nie jest to rozwiązanie problemu.
Brill Pappin
11
Jak to jest rozwiązanie? nie możesz po prostu użyć WebView, to bardzo drogi widget w porównaniu do TextView. Nie możesz po prostu użyć WebView dla każdego sformatowanego tekstu, który masz.
SpaceMonkey
135

Mam ten sam problem, co zrobiłem, to zastąpienie domyślnego TagHandler . Ten pracował dla mnie.

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

i do wyświetlania tekstu ...

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

[Edytować]

Kuitsi opublikował również naprawdę dobrą bibliotekę, która robi to samo, pobrała ją z tego linku SO .

Aman Gautam
źródło
W końcu zastosowaliśmy to podejście. Wszelkie nieobsługiwane znaczniki HTML sami kodujemy w tekście. Na razie to tylko ol i ul, ale dodaliśmy w stosach, aby obsłużyć zagnieżdżanie list i przechowywanie indeksów podczas zagnieżdżania ol. Dodatkowo możesz użyć otwierającego parametru boolowskiego w miejsce pierwszego.
JonWillis,
6
@Aman Gautam, bardzo niesamowite dzięki za to! Czy masz pomysł, jak umieścić tekst tabulatorem, gdy zawiera więcej niż jedną linię? W przypadku tego kodu po drugiej linii tekst jest wyrównany z numerem, a nie tabulatorem, aby numer był oddzielony. Próbowałem kilku rzeczy, ale nie mogłem tego
rozgryźć
to samo tutaj, podziały wierszy na liście powodują problemy z tym podejściem.
Andreas Rudolph
Zamiast używać wklejonego znaku punktora, może lepiej użyć znaku Unicode: output.append ("\ n \ t \ u2022");
Matt McMinn
Dziękuję za ten fajny kod, ale nie mogę go użyć, dopóki nie znajdziemy rozwiązania, jak naprawić wcięcie wielu wierszy
peter.bartos
68

Pełny przykładowy projekt znajduje się pod adresem https://bitbucket.org/Kuitsi/android-textview-html-list .
Przykładowe zdjęcie jest dostępne pod adresem https://kuitsi.bitbucket.io/stackoverflow3150400_screen.png

To rozwiązanie jest najbliższe odpowiedzi Maszy . Część kodu jest również pobierana z klasy wewnętrznej android.text.Html.HtmlToSpannedConverter. Obsługuje zagnieżdżone listy uporządkowane i nieuporządkowane, ale zbyt długie teksty na listach uporządkowanych są nadal wyrównane do numeru pozycji, a nie do tekstu. Listy mieszane (ol i ul) też wymagają trochę pracy. Przykładowy projekt zawiera implementację Html.TagHandler, która jest przekazywana do Html.fromHtml (String, ImageGetter, TagHandler) .

Edycja: aby uzyskać szerszą obsługę tagów HTML, warto również spróbować https://github.com/NightWhistler/HtmlSpanner .

Kuitsi
źródło
Jak dotąd najlepsze rozwiązanie. Dziękuję
peter.bartos
Nie śledzenie problemów w BitBucket repo, więc delegowania tutaj: trzeba dodać kontrole tutaj i tutaj dla output.length() > 0jak wif (output.length() > 0 && output.charAt(output.length() - 1) != '\n')
mindeh
2
Aby inni ludzie nie tracili na to 2 godzin, NightWhistler HtmlSpanner usuwa wszystkie akcentowane znaki z nieznanego powodu.
EpicPandaForce,
@Kuitsi dzięki za rozwiązanie. Jest z tym jeden problem, gdy tekst html to „<ul> <li> coś </li> </ul>”, to ostatnia litera „coś” nie jest wyświetlana na liście.
Sam Berg,
To bardzo dobre rozwiązanie, ALE dwie wady: 1) nie obsługuje Androida ≥ 7 i 2) nie umieszcza wcięcia początkowego dla pierwszego poziomu listy.
soshial
24

Mała poprawka do kodu Aman Guatam. Powyższa funkcja ma problem z renderowaniem znaku nowej linii. Na przykład: jeśli przed <li>tagiem jest <p>tag, renderowane są 2 znaki nowej linii. Oto ulepszony kod:

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
źródło
Proste, ale skuteczne
steven0529
A co z listą uporządkowaną?
programista Androida
13

OSTRZEŻENIE

od 10 lutego 2016 r. android.text.Htmlfaktycznie obsługuje lii ultaguje oraz używa podstawowego new BulletSpan(), co oznacza, że ​​w najnowszych wersjach Androida Html.TagHandlerzamieszczone tutaj rozwiązania będą ignorowane

upewnij się, że twój kod obsługuje tę zmianę na wypadek, gdyby spodziewasz się BulletSpan z większą przerwą niż domyślna, będziesz musiał mieć jakieś rozwiązanie, które wykonuje wyszukiwanie / zamianę rozpiętości

kassim
źródło
4
Ale ta nowa klasa HTML jest dostępna tylko w systemie Android Ni nowszych.
Sakiboy
1
Tak - musisz więc liczyć się z tym, że różne wersje systemu operacyjnego będą się zachowywać inaczej. Dlatego polecam rozwiązanie, które wyszukuje i zastępuje BulletSpan po przeanalizowaniu kodu HTML w różnych zakresach. Domyślna implementacja wersji po N będzie używać domyślnego marginesu, który możesz znaleźć i zastąpić wybranym marginesem.
kassim
Zawsze na bieżąco.
Kai Wang
9

Inne rozwiązanie wykorzystujące LeadingMarginSpan. Obsługuje listy uporządkowane i nieuporządkowane, a także zagnieżdżanie.

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 );
        }
    }
}
masza
źródło
5
Podoba mi się pomysł używania Spans, ale nie mogę uzyskać zagnieżdżonej listy pracującej z tym kodem. Obie linie output.setSpan(...)java.lang.RuntimeException: PARAGRAPH span must start at paragraph boundary
ulegają
Dzięki za fajne rozwiązanie!
Wcina
2
dlaczego używasz wektora zamiast prostej ArrayList? a Vector jest dla wielu wątków ...
programista Androida
@androiddeveloper C ++ programmer, my bad, zapraszam do edycji odpowiedzi
Masha
1
Pisałem jak Snippet androidsnippets.com/...
Pratik Butani
8

Jeśli potrzebujesz tylko sformatować listę, zachowaj prostotę i skopiuj / wklej znak Unicode w swoim TextView, aby osiągnąć ten sam wynik.

• Znak Unicode „BULLET” (U + 2022)

Naku
źródło
6

Przyszedłem tutaj, szukając implementacji TagHandler. Odpowiedzi Truong Nguyen i Aman Guatam są bardzo ładne, ale potrzebowałem mieszanej wersji obu: potrzebowałem mojego rozwiązania, aby go nie przeformatować i móc rozwiązać <ol>tagi, ponieważ parsuję coś takiego <h3>title</h3><ol><li>item</li><li>item</li><li>item</li></ol>.

Oto moje rozwiązanie.

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

Zwróć uwagę, że ponieważ resetujemy wartość indeksu za każdym razem, gdy rozpoczyna się nowa lista, NIE BĘDZIE to działać, jeśli zagnieżdżasz listy, takie jak w <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

Z tym kodem otrzymasz 1, 1, 2, 3zamiast 1, 1, 2, 2.

Charlie-Blake
źródło
Ten kod działa do wersji 23. Jak sprawić, by działał na 24 i wyżej?
Abhinav Tyagi
3

Jasne, istnieje sposób na wyświetlanie punktorów w Android TextView. Możesz zamienić <li>tagi na &#149;(co jest kodem HTML punktora).

Jeśli chcesz wypróbować inne ikony listy, użyj preferowanej ikony z tabeli to ten link;

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

Taner
źródło
Nie działa dla mnie. Zamiast tego w systemach Android 7.1.1 i 6.0.1 zamiast kropki w TextView pojawia się ramka ze znakiem x.
user1652110
3

Możesz po prostu zamienić „li” na 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");
        }
    }
}
arkuszowy
źródło
2

Odpowiedź Lorda Voldermorta jest dobrym punktem wyjścia. Jednak olwymagałem, aby tag wyświetlał uporządkowaną listę 1. 2. 3. ....zamiast punktorów. Ponadto zagnieżdżone znaczniki wymagają specjalnej obsługi, aby działały prawidłowo.

W moim kodu, mam utrzymuje stos (parentList) śledzić otwierane i zamykane uli oltagi, a także znać aktualny znacznik otwarty. Ponadto a levelWiseCountersłuży do utrzymywania różnych liczników w przypadku zagnieżdżonych oltagów.

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
źródło
2

A co z następnym kodem (na podstawie tego linku ):

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
    {
    }
  }
programista Androida
źródło
1
Ta odpowiedź ma tylko trochę inne formatowanie w porównaniu z oryginalnym źródłem tego, które zostało utworzone w celu obsługi innej odpowiedzi na to samo pytanie: stackoverflow.com/a/17365740/262462 :)
Kuitsi
prawdziwe. nie zauważyłem tego.
programista Androida
2

Miałem problem, że zawsze otrzymywałem pusty wiersz po liście z rozwiązaniem @Kuitsis. Dodałem kilka linii w handleTag () i teraz puste linie zniknęły:

@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
źródło
2

Możesz użyć Html.TagHandler. Poniżej można użyć 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•")
    }
}

i

textView.setText(Html.fromHtml(myHtmlText, null, UlTagHandler()));
Shalu TD
źródło
0

jest to potwierdzenie tego, co powiedział Kassim. występuje fragmentacja. znalazłem, jak to rozwiązać. muszę zmienić nazwę <li>i ul na niestandardowy tag. więc:

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

wtedy w moim handlerze mogę poszukać tego customTag (który nic nie robi) i sprawić, żeby coś zrobił.

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

to powinno działać na wszystkich wersjach Androida.

j2emanue
źródło