Android中的TextView,本身就支持部分的Html格式标签。这其中包括常用的字体大小颜色设置,文本链接等。使用起来也比较方便,只需要使用Html类转换一下即可。比如:

textView.setText(Html.fromHtml(str));


然而,有一种场合,默认支持的标签可能不够用。比如,我们需要在textView中点击某种链接,返回到应用中的某个界面,而不仅仅是网络连接,如何实现?


经过几个小时对android中的Html类源代码的研究,找到了解决办法,并且测试通过。


先看Html类的源代码中有这样一段:

  1. /**

  2.    * Is notified when HTML tags are encountered that the parser does

  3.    * not know how to interpret.

  4.    */

  5. publicstaticinterface TagHandler {  

  6. /**

  7.        * This method will be called whenn the HTML parser encounters

  8.        * a tag that it does not know how to interpret.

  9.        */

  10. publicvoid handleTag(boolean opening, String tag,  

  11.                                Editable output, XMLReader xmlReader);  


这里定义了一个接口,接口用于什么呢?


再继续看代码,看到对Html的tag进行解析部分的代码:


  1. privatevoid handleStartTag(String tag, Attributes attributes) {  

  2. if (tag.equalsIgnoreCase("br")) {  

  3. // We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>

  4. // so we can safely emite the linebreaks when we handle the close tag.

  5.        } elseif (tag.equalsIgnoreCase("p")) {  

  6.            handleP(mSpannableStringBuilder);  

  7.        } elseif (tag.equalsIgnoreCase("div")) {  

  8.            handleP(mSpannableStringBuilder);  

  9.        } elseif (tag.equalsIgnoreCase("em")) {  

  10.            start(mSpannableStringBuilder, new Bold());  

  11.        } elseif (tag.equalsIgnoreCase("b")) {  

  12.            start(mSpannableStringBuilder, new Bold());  

  13.        } elseif (tag.equalsIgnoreCase("strong")) {  

  14.            start(mSpannableStringBuilder, new Italic());  

  15.        } elseif (tag.equalsIgnoreCase("cite")) {  

  16.            start(mSpannableStringBuilder, new Italic());  

  17.        } elseif (tag.equalsIgnoreCase("dfn")) {  

  18.            start(mSpannableStringBuilder, new Italic());  

  19.        } elseif (tag.equalsIgnoreCase("i")) {  

  20.            start(mSpannableStringBuilder, new Italic());  

  21.        } elseif (tag.equalsIgnoreCase("big")) {  

  22.            start(mSpannableStringBuilder, new Big());  

  23.        } elseif (tag.equalsIgnoreCase("small")) {  

  24.            start(mSpannableStringBuilder, new Small());  

  25.        } elseif (tag.equalsIgnoreCase("font")) {  

  26.            startFont(mSpannableStringBuilder, attributes);  

  27.        } elseif (tag.equalsIgnoreCase("blockquote")) {  

  28.            handleP(mSpannableStringBuilder);  

  29.            start(mSpannableStringBuilder, new Blockquote());  

  30.        } elseif (tag.equalsIgnoreCase("tt")) {  

  31.            start(mSpannableStringBuilder, new Monospace());  

  32.        } elseif (tag.equalsIgnoreCase("a")) {  

  33.            startA(mSpannableStringBuilder, attributes);  

  34.        } elseif (tag.equalsIgnoreCase("u")) {  

  35.            start(mSpannableStringBuilder, new Underline());  

  36.        } elseif (tag.equalsIgnoreCase("sup")) {  

  37.            start(mSpannableStringBuilder, new Super());  

  38.        } elseif (tag.equalsIgnoreCase("sub")) {  

  39.            start(mSpannableStringBuilder, new Sub());  

  40.        } elseif (tag.length() == 2 &&  

  41.                   Character.toLowerCase(tag.charAt(0)) == 'h' &&  

  42.                   tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {  

  43.            handleP(mSpannableStringBuilder);  

  44.            start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));  

  45.        } elseif (tag.equalsIgnoreCase("img")) {  

  46.            startImg(mSpannableStringBuilder, attributes, mImageGetter);  

  47.        } elseif (mTagHandler != null) {  

  48.            mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);  

  49.        }  

  50.    }  

  51. privatevoid handleEndTag(String tag) {  

  52. if (tag.equalsIgnoreCase("br")) {  

  53.            handleBr(mSpannableStringBuilder);  

  54.        } elseif (tag.equalsIgnoreCase("p")) {  

  55.            handleP(mSpannableStringBuilder);  

  56.        } elseif (tag.equalsIgnoreCase("div")) {  

  57.            handleP(mSpannableStringBuilder);  

  58.        } elseif (tag.equalsIgnoreCase("em")) {  

  59.            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));  

  60.        } elseif (tag.equalsIgnoreCase("b")) {  

  61.            end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));  

  62.        } elseif (tag.equalsIgnoreCase("strong")) {  

  63.            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));  

  64.        } elseif (tag.equalsIgnoreCase("cite")) {  

  65.            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));  

  66.        } elseif (tag.equalsIgnoreCase("dfn")) {  

  67.            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));  

  68.        } elseif (tag.equalsIgnoreCase("i")) {  

  69.            end(mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC));  

  70.        } elseif (tag.equalsIgnoreCase("big")) {  

  71.            end(mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f));  

  72.        } elseif (tag.equalsIgnoreCase("small")) {  

  73.            end(mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f));  

  74.        } elseif (tag.equalsIgnoreCase("font")) {  

  75.            endFont(mSpannableStringBuilder);  

  76.        } elseif (tag.equalsIgnoreCase("blockquote")) {  

  77.            handleP(mSpannableStringBuilder);  

  78.            end(mSpannableStringBuilder, Blockquote.class, new QuoteSpan());  

  79.        } elseif (tag.equalsIgnoreCase("tt")) {  

  80.            end(mSpannableStringBuilder, Monospace.class,  

  81. new TypefaceSpan("monospace"));  

  82.        } elseif (tag.equalsIgnoreCase("a")) {  

  83.            endA(mSpannableStringBuilder);  

  84.        } elseif (tag.equalsIgnoreCase("u")) {  

  85.            end(mSpannableStringBuilder, Underline.class, new UnderlineSpan());  

  86.        } elseif (tag.equalsIgnoreCase("sup")) {  

  87.            end(mSpannableStringBuilder, Super.class, new SuperscriptSpan());  

  88.        } elseif (tag.equalsIgnoreCase("sub")) {  

  89.            end(mSpannableStringBuilder, Sub.class, new SubscriptSpan());  

  90.        } elseif (tag.length() == 2 &&  

  91.                Character.toLowerCase(tag.charAt(0)) == 'h' &&  

  92.                tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {  

  93.            handleP(mSpannableStringBuilder);  

  94.            endHeader(mSpannableStringBuilder);  

  95.        } elseif (mTagHandler != null) {  

  96.            mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);  

  97.        }  

  98.    }  


可以看到,如果不是默认的标签,会调用mTagHandler的handleTag方法。所以,我们可以实现此接口,来解析自己定义的标签类型。


再看一段我实现的对<game>标签进行解析的示例代码:

  1. publicclass GameTagHandler implements TagHandler {  

  2. privateint startIndex = 0;  

  3. privateint stopIndex = 0;  

  4. @Override

  5. publicvoid handleTag(boolean opening, String tag, Editable output,  

  6.            XMLReader xmlReader) {  

  7. if (tag.toLowerCase().equals("game")) {  

  8. if (opening) {  

  9.                startGame(tag, output, xmlReader);  

  10.            } else {  

  11.                endGame(tag, output, xmlReader);  

  12.            }  

  13.        }  

  14.    }  

  15. publicvoid startGame(String tag, Editable output, XMLReader xmlReader) {  

  16.        startIndex = output.length();  

  17.    }  

  18. publicvoid endGame(String tag, Editable output, XMLReader xmlReader) {  

  19.        stopIndex = output.length();  

  20.        output.setSpan(new GameSpan(), startIndex, stopIndex,  

  21.                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);  

  22.    }  

  23. privateclass GameSpan extends ClickableSpan implements OnClickListener {  

  24. @Override

  25. publicvoid onClick(View v) {  

  26. // 跳转某页面

  27.        }  

  28.    }  


上面这段代码,是对<game>…</game>的自定义标签进行解析。


具体调用方法:

      textView.setText(Html.fromHtml(“点击<game>这里</game>跳转到游戏”,

             null, new GameTagHandler()));

      textView.setClickable(true);

      textView.setMovementMethod(LinkMovementMethod.getInstance());


运行后,能够看到文本中的字符串“这里”带了超链接,点击链接后,GameSpan类的onClick()方法被调用。就可以在这个方法中进行跳转了。