上一篇文章简单介绍了Html.fromHtml的用法,继续分析其中的实现,该方法的第三个参数TagHandler
,该参数为Html里的一个内部静态接口,该接口里边定义了一个方法
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader);
该方法接收四个参数,我们重点看一下最后一个参数XMLReader xmlReader
,是不是很眼熟?在XML解析中我们遇到过。我们再来看一下fromHtml
方法里的实现:
public static Spanned fromHtml(String source, ImageGetter imageGetter,
TagHandler tagHandler) {
Parser parser = new Parser();
try {
parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
} catch (org.xml.sax.SAXNotRecognizedException e) {
// Should not happen.
throw new RuntimeException(e);
} catch (org.xml.sax.SAXNotSupportedException e) {
// Should not happen.
throw new RuntimeException(e);
}
HtmlToSpannedConverter converter =
new HtmlToSpannedConverter(source, imageGetter, tagHandler,
parser);
return converter.convert();
}
第一行Parser parser = new Parser();
这里提供一个关于该类的链接,有兴趣的同学可以自己查看源码,源码比较多:org.ccil.cowan.tagsoup.Parser,这里我们重点看一下converter.convert()
这个方法:
mReader.setContentHandler(this);
try {
mReader.parse(new InputSource(new StringReader(mSource)));
} catch (IOException e) {
// We are reading from a string. There should not be IO problems.
throw new RuntimeException(e);
} catch (SAXException e) {
// TagSoup doesn't throw parse exceptions.
throw new RuntimeException(e);
}
这里贴该方法的代码片段,其中mReader
就是在构建HtmlToSpannedConverter
对象的时候传过来的Parser对象。HtmlToSpannedConverter
类实现了ContentHandler
接口,convert
方法的第一行就是mReader.setContentHandler(this);
此时执行mReader.parse(new InputSource(new StringReader(mSource)));
就可以对传入的Html格式的字符串进行解析了。再来看
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
handleStartTag(localName, attributes);
}
在解析到每一个头结点的时候,都会调用一个叫handleStartTag
的方法,继续进入该方法看源码
private void handleStartTag(String tag, Attributes attributes) {
if (tag.equalsIgnoreCase("br")) {
// We don't need to handle this. TagSoup will ensure that there's a </br> for each <br>
// so we can safely emite the linebreaks when we handle the close tag.
} else if (tag.equalsIgnoreCase("p")) {
handleP(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("div")) {
handleP(mSpannableStringBuilder);
} else if (tag.equalsIgnoreCase("strong")) {
start(mSpannableStringBuilder, new Bold());
} else if (tag.equalsIgnoreCase("b")) {
start(mSpannableStringBuilder, new Bold());
} else if (tag.equalsIgnoreCase("em")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("cite")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("dfn")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("i")) {
start(mSpannableStringBuilder, new Italic());
} else if (tag.equalsIgnoreCase("big")) {
start(mSpannableStringBuilder, new Big());
} else if (tag.equalsIgnoreCase("small")) {
start(mSpannableStringBuilder, new Small());
} else if (tag.equalsIgnoreCase("font")) {
startFont(mSpannableStringBuilder, attributes);
} else if (tag.equalsIgnoreCase("blockquote")) {
handleP(mSpannableStringBuilder);
start(mSpannableStringBuilder, new Blockquote());
} else if (tag.equalsIgnoreCase("tt")) {
start(mSpannableStringBuilder, new Monospace());
} else if (tag.equalsIgnoreCase("a")) {
startA(mSpannableStringBuilder, attributes);
} else if (tag.equalsIgnoreCase("u")) {
start(mSpannableStringBuilder, new Underline());
} else if (tag.equalsIgnoreCase("sup")) {
start(mSpannableStringBuilder, new Super());
} else if (tag.equalsIgnoreCase("sub")) {
start(mSpannableStringBuilder, new Sub());
} else if (tag.length() == 2 &&
Character.toLowerCase(tag.charAt(0)) == 'h' &&
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
handleP(mSpannableStringBuilder);
start(mSpannableStringBuilder, new Header(tag.charAt(1) - '1'));
} else if (tag.equalsIgnoreCase("img")) {
startImg(mSpannableStringBuilder, attributes, mImageGetter);
} else if (mTagHandler != null) {
mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
}
}
这段代码比较长,先简单分析一下,里边很多都是我们比较熟悉的各种Html标签(不是所有Html标签),之前我们说过Html.fromHtm方法并不是支持所有标签的。里面绝大多数标签都会执行handleP
和start
,这个就不具体分析了,因为这些标签我们使用的时候都能正常显示,我们重点看一下
else if (tag.equalsIgnoreCase("img")) {
startImg(mSpannableStringBuilder, attributes, mImageGetter);
}
我们还是进入startImg
的内部来看看具体实现
private static void startImg(SpannableStringBuilder text,
Attributes attributes, Html.ImageGetter img) {
String src = attributes.getValue("", "src");
Drawable d = null;
if (img != null) {
d = img.getDrawable(src);
}
if (d == null) {
d = Resources.getSystem().
getDrawable(com.android.internal.R.drawable.unknown_image);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
}
int len = text.length();
text.append("\uFFFC");
text.setSpan(new ImageSpan(d, src), len, text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
这段代码很简单,首先获取img
标签里src
属性的值,这个属性放的就是图片的URL,然后判断img
s是否为空,这个img是Html.ImageGetter
的一个对象,如果img不为空的话,就会调用Html.ImageGetter
的 getDrawable
方法来获取一个Drawable
对象。看到这里我们就会很熟悉了,之前的文章里,我们已经实现了自己的Html.ImageGetter
类,并在getDrawabl
里通过网络加载了图片。如果img
为空的话,就会加载一个系统内置的Drawable对象:
if (d == null) {
d = Resources.getSystem().
getDrawable(com.android.internal.R.drawable.unknown_image);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
}
这就可以解释,如果我们在调用一个参数的Html.fromHtml
方式时,所有的img
标签处只是显示一个很小的正方形图片,而不是真正我们需要的图片。至此我们就完成了图文的正常显示。
最后呢,如果传过来的标签并不包含在我们上述代码的判断中,则会调用
else if (mTagHandler != null) {
mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
}
这里也很眼熟吧,mTagHandler.handleTag
就是第三个参数,这里我们可以通过自己实现该类来自行定制想显示的样式,会比较灵活。
整个Html.fromHtml
的实现就简单分析完了,一般我们定制一个TextView上显示不同的样式文字的时候是通过定义SpannedString
和 Html.fromHtml
方式。SpannedString
实现了Spanned
接口,而Html.fromHtml
返回一个Spanned
接口的对象,所以两个的原理都一样,只是在定制文字的方式上有区别。
这篇文章也可以告一段落了,文章中如果有错误之处呢,还希望大家能指出!