Как отобразить горизонтальные линии между текстом HTML-контента на Android одного Textview?
У меня есть HTML-контент с чем-то вроде
<html>
<h3>nested unordered lists</h3>
<ul>
<li>
first level
<ul>
<li>
second level
<ul>
<li>
third level
<ul>
<li>
fourth level
<ul>
<li>fifth level</li>
</ul>
</li>
<li>fourth level</li>
</ul>
</li>
<li>third level</li>
</ul>
</li>
<li>second level</li>
<li>second level: this should be a long enough text that will be wrapped into multiple lines</li>
</ul>
</li>
<li>first level</li>
</ul>
<hr>
<h3>nested ordered lists</h3>
<ol>
<li>
first level
<ol>
<li>
second level
<ol>
<li>
third level
<ol>
<li>
fourth level
<ol>
<li>fifth level</li>
</ol>
</li>
<li>fourth level</li>
</ol>
</li>
<li>third level</li>
</ol>
</li>
<li>second level</li>
<li>second level: this should be a long enough text that will be wrapped into multiple lines</li>
</ol>
</li>
<li>first level</li>
</ol>
<hr>
<h3>Mixed (ol and ul) nested lists:</h3>
<ul>
<li>
first unordered
<ol>
<li>first ordered</li>
<li>
second ordered
<ul>
<li>
unordered in second ordered
<ol>
<li>ordered in "unordered in second ordered"</li>
<li>another ordered in ""unordered in second ordered"</li>
</ol>
</li>
</ul>
</li>
<li>third ordered with some other formatting: <b>bold</b> and <i>italics</i></li>
</ol>
</li>
<li>second unordered</li>
</ul>
</html>
Сейчас я пытаюсь отобразить содержимое HTML в одном Textview. Фрагмент кода приведен ниже
textView.setText(Html.fromHtml(htmlContent, null, htmlTagHandler));
Вот
- htmlTagHandler является ссылкой HtmlTagHandler, которая используется для поддержки
<ul>
,<ol>
,<li>
теги.
HtmlTagHandler.java:
public class HtmlTagHandler implements Html.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(boolean opening, String tag, Editable output, 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");
}
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.length() > 0 && 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;
}
}
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.length() > 0 && 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(Editable text, Object mark) {
int len = text.length();
text.setSpan(mark, len, len, Spanned.SPAN_MARK_MARK);
}
private static void end(Editable text, Class<?> kind, Object... replaces) {
int len = text.length();
Object obj = getLast(text, kind);
int where = text.getSpanStart(obj);
text.removeSpan(obj);
if (where != len) {
for (Object replace: replaces) {
text.setSpan(replace, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return;
}
private static Object getLast(Spanned text, Class<?> kind) {
/*
* This knows that the last returned object from getSpans()
* will be the most recently added.
*/
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 { }
}
Здесь я могу отображать маркеры и числа, используя Html.fromHtml (htmlContent, htmlImageGet, htmlTagHandler), но не горизонтальные линии. Может ли кто-нибудь направить меня, как отобразить горизонтальную линию или поддержку <hr>
отметить в Html.fromHtml()
метод с помощью HtmlTagHandler или любой другой подход.
Ожидаемый результат как на снимке экрана ниже.
Все работает нормально, ожидайте, что горизонтальные линии. Кто-нибудь может помочь в этом вопросе.
1 ответ
Вот как я решил твой вопрос. Это не очень элегантное решение, но оно работает.
Html.TagHandler:
import org.xml.sax.XMLReader;
import android.text.Editable;
import android.text.Html;
import android.text.Spannable;
import android.text.Spanned;
import android.util.Log;
public class CustomTagHandler implements Html.TagHandler {
@Override
public void handleTag(final boolean opening, final String tag,
Editable output, final XMLReader xmlReader) {
if(tag.equals("hr")) {
handleHRTag(opening,output);
}
}
private void handleHRTag(boolean opening, Editable output) {
if(!opening) return;
int start = output.length();
// The space is necessary, the block requires some content:
output.append(" \n");
output.setSpan(new CustomHRSpan(0xff000000,5.0f,2.0f),
start, output.length(), 0);
}
}
Затем создайте класс CustomHRSpan:
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.text.style.LineBackgroundSpan;
import android.text.style.LineHeightSpan;
public class CustomHRSpan implements LineBackgroundSpan, LineHeightSpan {
private final int color; // Color of line
private final float height; // Height of HR element
private final float line; // Line size
private final float marginLeft; // Margin of line, left side
private final float marginRight; // Margin of line, right side
public CustomHRSpan(int color, float height, float line) {
this.color = color;
this.height = height;
this.line = line;
this.marginLeft = 5.0f;
this.marginRight = 5.0f;
}
@Override
public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline, int bottom,
CharSequence text, int start, int end, int lnum) {
int paintColor = p.getColor();
float y = (float)(top+(bottom-top)/2) - line*0.5f;
RectF r = new RectF((float)left+marginLeft, y,
(float)(right-left)-marginRight, y+line);
p.setColor(color);
c.drawRect(r, p);
p.setColor(paintColor);
}
@Override
public void chooseHeight(CharSequence text, int start, int end, int spanstartv, int v, Paint.FontMetricsInt fm) {
fm.descent = (int)height / 2;
fm.ascent = (int)height - fm.descent;
fm.leading = 0;
fm.top = fm.ascent;
fm.bottom = fm.descent;
}
}