TextViewでHTMLのLIタグを表示しようとして深い泥沼に足を突っ込んでしまったお話(解決編)

前回の記事 [TextViewでHTMLのLIタグを表示しようとして深い泥沼に足を突っ込んでしまったお話](http://qiita.com/oboenikui/items/802dfb5d1ef695f0528c) を何とか解決(?)することができたので，書きます．

まず，修正後のソースは以下のようになります．

```java:FixedHtmlTagHandler.java
import java.util.Stack;

import org.xml.sax.XMLReader;

import android.text.Editable;
import android.text.Html;
import android.text.style.LeadingMarginSpan;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;

/**
 * Don't think. Feel.
 */
public class FixedHtmlTagHandler implements Html.TagHandler {
    private Stack<Integer> mListItemCounts = new Stack<Integer>();
    private Stack<Integer> mListParents = new Stack<Integer>();
    private Stack<Integer> mListTypes = new Stack<Integer>();
    private int mTextWidth, mLineHeight;
    private final static int TAG_OL = 1;
    private final static int TAG_UL = 2;

    public FixedHtmlTagHandler(int textWidth, int lineHeight){
        mTextWidth = textWidth;
        mLineHeight = lineHeight;
    }

    @Override
    public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {

        if (tag.equals("ol")) {
            if (opening) {

                if(output.length() > 0 && output.charAt(output.length()-1)!= '\n'){
                    output.append('\n');
                }
                mListItemCounts.push(0);
                if(!mListTypes.empty() && !mListParents.empty()){
                    int start = mListParents.pop(), end = output.length();
                    LeadingMarginSpan lmspan = new LeadingMarginSpan.Standard( (mListTypes.size() * 2 + 3) * mTextWidth);
                    ListElementSpan lespan = new ListElementSpan(mLineHeight);
                    output.setSpan(lmspan, start, end, 0);
                    output.setSpan(lespan, start, end, 0);
                }
                mListTypes.push(TAG_OL);
            } else {
                mListTypes.pop();
                mListItemCounts.pop();
                if(mListTypes.size() > 0) {
                    mListParents.push(output.length());
                }
            }
        } else if (tag.equals("ul")) {
            if (opening) {
                if(output.length() > 0 && output.charAt(output.length()-1)!= '\n'){
                    output.append('\n');
                }
                mListItemCounts.push(0);
                if(!mListTypes.empty() && !mListParents.empty()){
                    int start = mListParents.pop(), end = output.length();
                    LeadingMarginSpan lmspan = new LeadingMarginSpan.Standard( (mListTypes.size() * 2 + 3) * mTextWidth);
                    ListElementSpan lespan = new ListElementSpan(mLineHeight);
                    output.setSpan(lmspan, start, end, 0);
                    output.setSpan(lespan, start, end, 0);
                }
                mListTypes.push(TAG_UL);

            } else {
                mListTypes.pop();
                mListItemCounts.pop();
                if(mListTypes.size() > 0) {
                    mListParents.push(output.length());
                }
            }
        } else if (tag.equals("li")) {
            if(opening) {
                if(output.length() > 0 && output.charAt(output.length()-1)!= '\n'){
                    output.append('\n');
                }
                handleListTag(output);

                mListParents.push(output.length());

            } else {
                if(output.length() > 0 && output.charAt(output.length()-1)!='\n'){
                    output.append("\n");
                }
                LeadingMarginSpan lmspan = new LeadingMarginSpan.Standard( (mListTypes.size() * 2 + 3) * mTextWidth);
                ListElementSpan lespan = new ListElementSpan(mLineHeight);
                int start = mListParents.pop(), end = output.length();
                output.setSpan(lmspan, start, end, 0);
                output.setSpan(lespan, start, end, 0);

            }
        }
    }

    private void handleListTag(Editable output) {
        int start = output.length();
        String appendText = "";
        switch (mListTypes.peek()){
            case TAG_OL:
                int itemCount = mListItemCounts.pop()+1;
                appendText = itemCount + ".\n";
                mListItemCounts.push(itemCount);
                break;
            case TAG_UL:
                appendText = "・\n";
                break;
        }
        output.append(appendText);
        int end = output.length();

        LeadingMarginSpan span = new LeadingMarginSpan.Standard(mListTypes.size() * 2 * mTextWidth);
        output.setSpan(span, start, end, 0);
    }

    /**
     * Shift above 1 line
     */
    public class ListElementSpan extends MetricAffectingSpan {
        int mHeight;
        public ListElementSpan(int height){
            mHeight = height;
        }
        @Override
        public void updateMeasureState(TextPaint p) {
        }

        @Override
        public void updateDrawState(TextPaint tp) {
            tp.baselineShift -= mHeight;
        }
    }
}
```

アプローチとしては，

* 文字幅を最初の行とそれ以外の行で正しく設定することは諦め，どの行も一定のインデントにすることではみ出すことを回避する
* 箇条書きの数字・記号を別の行にすることで，箇条書きの数字・記号と文章のインデントを別の値に設定する
* 文章が別の行になっているので，それを1行分上にずらす

ということをしています．ちょっと表示がまだ残念ですが，一応前回述べた箇条書きの数字と文章のインデントを変えるという仕様は満たせているのでこの不毛な作業はここでやめます．