Ordered List in XML in Android?
I am trying to make an ordered list from XML in a textview. This list would be bulleted and properly justified with subheadings. Unfortunately, it seems that the support for this in the xml is minimal. I have tried the following in strings.xml:
<ol>
<li> item 1\n
<li>sub-item1\n</li>
<li>sub-item2\n</li>
<li>item2\n</li>
<li>item3</li>
</ol>
with various permutations having ol around each item etc etc. The result typically shows subitem2 and item 2 being indented away from the bullet. I have been really scratching my head on 开发者_Python百科this one. Any guidance on this would be great.
First, create a Custom Tag handler class:
package com.thecitybank.myca.ui;
import android.text.Editable;
import android.text.Html;
import android.text.Spanned;
import android.text.style.BulletSpan;
import android.text.style.LeadingMarginSpan;
import android.util.Log;
import org.xml.sax.XMLReader;
import java.util.Stack;
public class MyTagHandler implements Html.TagHandler {
private static final String OL_TAG = "ol";
private static final String UL_TAG = "ul";
private static final String LI_TAG = "li";
private static final int INDENT_PX = 10;
private static final int LIST_ITEM_INDENT_PX = INDENT_PX * 2;
private static final BulletSpan BULLET_SPAN = new BulletSpan(INDENT_PX);
private final Stack<ListTag> lists = new Stack<ListTag>();
@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();
}
} 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();
}
} 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);
}
}
/**
* Abstract super class for {@link Ul} and {@link Ol}.
*/
private abstract static class ListTag {
/**
* Opens a new list item.
*
* @param text
*/
public void openItem(final Editable text) {
if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
text.append("\n");
}
final int len = text.length();
text.setSpan(this, len, len, Spanned.SPAN_MARK_MARK);
}
/**
* Closes a list item.
*
* @param text
* @param indentation
*/
public final void closeItem(final Editable text, final int indentation) {
if (text.length() > 0 && text.charAt(text.length() - 1) != '\n') {
text.append("\n");
}
final Object[] replaces = getReplaces(text, indentation);
final int len = text.length();
final ListTag listTag = getLast(text);
final int where = text.getSpanStart(listTag);
text.removeSpan(listTag);
if (where != len) {
for (Object replace : replaces) {
text.setSpan(replace, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
protected abstract Object[] getReplaces(final Editable text, final int indentation);
/**
* Note: This knows that the last returned object from getSpans() will be the most recently added.
*
* @see Html
*/
private ListTag getLast(final Spanned text) {
final ListTag[] listTags = text.getSpans(0, text.length(), ListTag.class);
if (listTags.length == 0) {
return null;
}
return listTags[listTags.length - 1];
}
}
/**
* Class representing the unordered list ({@code <ul>}) HTML tag.
*/
private static class Ul extends ListTag {
@Override
protected Object[] getReplaces(final Editable text, final int indentation) {
// Nested BulletSpans increases distance between BULLET_SPAN and text, so we must prevent it.
int bulletMargin = INDENT_PX;
if (indentation > 1) {
bulletMargin = INDENT_PX - BULLET_SPAN.getLeadingMargin(true);
if (indentation > 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 -= (indentation - 2) * LIST_ITEM_INDENT_PX;
}
}
return new Object[]{
new LeadingMarginSpan.Standard(LIST_ITEM_INDENT_PX * (indentation - 1)),
new BulletSpan(bulletMargin)
};
}
}
/**
* Class representing the ordered list ({@code <ol>}) HTML tag.
*/
private static class Ol extends ListTag {
private int nextIdx;
/**
* Creates a new {@code <ul>} with start index of 1.
*/
public Ol() {
this(1); // default start index
}
/**
* Creates a new {@code <ul>} with given start index.
*
* @param startIdx
*/
public Ol(final int startIdx) {
this.nextIdx = startIdx;
}
@Override
public void openItem(final Editable text) {
super.openItem(text);
text.append(Integer.toString(nextIdx++)).append(". ");
}
@Override
protected Object[] getReplaces(final Editable text, final int indentation) {
int numberMargin = LIST_ITEM_INDENT_PX * (indentation - 1);
if (indentation > 2) {
// Same as in ordered lists: counter the effect of nested Spans
numberMargin -= (indentation - 2) * LIST_ITEM_INDENT_PX;
}
return new Object[]{new LeadingMarginSpan.Standard(numberMargin)};
}
}
}
Then add a string in your string.xml as done below:
<string name="change_password_note"><![CDATA[<ol><li>Your password should contain at least one character of a-z, A-Z and 0-9. No special character is allowed.</li> <li>Please do not use any of your last three passwords.</li> <li>Please do not enter your User ID as password.</li> <li>Password minimum of 8 and maximum of 20 characters.</li></ol>]]></string>
Lastly, set text into your TextView as below:
txtNotesDescription.setText(Html.fromHtml(getString(R.string.change_password_note),null,new MyTagHandler()));
This worked for me. You can use un-ordered list by using the (ul)
tag.
<string name="accessibility_service_instructions">
1. Enable TalkBack (Settings -> Accessibility -> TalkBack).
\n\n2. Enable Explore-by-Touch (Settings -> Accessibility -> Explore by Touch).
\n\n3. Touch explore the Clock application and the home screen.
\n\n4. Go to the Clock application and change the time of an alarm.
\n\n5. Enable ClockBack (Settings -> Accessibility -> ClockBack).
\n\n6. Go to the Clock application and change an alarm.
\n\n7. Set the ringer to vibration mode and change an alarm.
\n\n8. Set the ringer to muted mode and change an alarm.
</string>
精彩评论