开发者

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>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜