Тег Html List не работает в текстовом виде Android. Что я могу сделать?
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));
Результат выглядит как абзац. Что я могу сделать? Есть ли для этого решение?
Edit:
webview.loadData(str,"text/html","utf-8");
Ответы
Ответ 1
Как вы можете видеть в 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);
Ответ 2
Похоже на старый вопрос,
У меня такая же проблема. Что я сделал, это переопределение по умолчанию TagHandler, я новичок в StackOverflow и Android, и ценю любую коррекцию или лучший метод:) Это работало для меня.
package com.tumblr.amangautam.cars;
import org.xml.sax.XMLReader;
import android.app.Activity;
import android.os.Bundle;
import android.text.Editable;
import android.text.Html;
import android.text.Html.TagHandler;
import android.util.Log;
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..
Ответ 3
Полный образец проекта находится по адресу 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 также стоит попробовать.
Ответ 4
Небольшое исправление для кода Амана Гуатама. Вышеупомянутая функция имеет проблему рендеринга символа новой строки. Например: если тег <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;
}
}
}
}
Ответ 5
Внимание
по состоянию на 10 февраля 2016 года android.text.Html
фактически поддерживает теги li
и ul
и использует базовый new BulletSpan()
, что означает, что в последних версиях Android решения Html.TagHandler
, размещенные здесь, будут игнорироваться
убедитесь, что ваш код обрабатывает это изменение в случае, если вы ожидаете BulletSpan с большим разрывом, чем по умолчанию, вам нужно будет иметь какое-то решение, которое найдет/заменит промежутки
Ответ 6
Разное решение с использованием 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 );
}
}
}
Ответ 7
Если вам нужно только отформатировать список, сохраните его простым и скопируйте/вставьте символ Юникода в свой TextView, чтобы достичь того же результата.
• Юникодный символ "BULLET" (U + 2022)
Ответ 8
Я пришел сюда, чтобы найти реализации 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.2
- 2
С помощью этого кода вы получите 1, 1, 2, 3
вместо 1, 1, 2, 2
.
Ответ 9
Конечно, есть способ показать маркеры в Android TextView. Вы можете заменить теги <li>
на •
(это HTML-код для пули).
Если вы хотите попробовать другие значки списка, используйте предпочтительную из таблицы эту ссылку;
http://www.ascii-code.com/
Ответ 10
Вы можете просто заменить "li" на 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");
}
}
}
Ответ 11
Ответ лорда Вольдермор - хорошая отправная точка. Однако мне потребовался тег ol
, чтобы отображать упорядоченный список 1. 2. 3. ....
вместо пули. Кроме того, вложенные теги нуждаются в специальной обработке для правильной работы.
В моем коде я сохранил stack (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
}
}
Ответ 12
Как насчет следующего кода (на основе эта ссылка):
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 more complicated when we add a LeadingMarginSpan into the same line:
// we have also counter it 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
{
}
}
Ответ 13
У меня возникла проблема: у меня всегда была пустая строка после списка с решением @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);
}
}
Ответ 14
Вы можете использовать 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()));
Ответ 15
Это подтверждение того, что сказал Касим. есть фрагментация. я нашел, как решить эту проблему. я должен переименовать <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.