TextView显示富文本的三种方案
背景
在做Android开发的时候,大家可能会经常遇到这种需求:一个textview控件上想展示一段声明,声明的大部分内容是正常的很色字体,书名号引用的各种文件条款,使用蓝色字体显示,类似如下效果图。
这类需求一般有三种实现方式
方式一:使用多个TextView来显示
这是最为简单无脑的方法,但是很试用场景比较有限,当涉及到文字换行时,这种实现方式不方便处理了,这里就不给大家详细演示了。
方式二:SpannableString
没错,SpannableString的优点在于控制得精细,缺点也是在这。我们使用SpannableString的时候必须指定样式使用的字符下标,那如果我们的字符串不是固定长度的呢?方式二就不太适用了。
方式三:使用Html.fromHtml
方式三是今天主要讲解的一个方案,将要展示的文案写成html的格式,通过内置的Html类,使用fromHtml方法将html文本转换为可显示在textview的带有各种标记的文字。
原理:html是xml语言,fromHtml方法中,使用sax方式对html的xml进行解析,在解析到能识别的标签时,根据不同的Spanned策略来标记文案
简单的实现方式如下:
String content = "一段html风格的字符串";
textView.setText(Html.fromHtml(content, null, null));
监听自定义标签
** 原理**:当html.fromHtml方法在处理html文件时,如果开发者传递了自定义的TagHandler,在解析的过程中,会返回不能识别的标签到TagHandler的handleTag方法中,开发者在这个回调中,可以自定义处理,该接口的原生定义如下。
public static Spanned fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler)
/**
* Is notified when HTML tags are encountered that the parser does
* not know how to interpret.
*/
public static interface TagHandler {
/**
* This method will be called whenn the HTML parser encounters
* a tag that it does not know how to interpret.
*/
public void handleTag(boolean opening, String tag,
Editable output, XMLReader xmlReader);
}
监听已有标签的自定义属性
原理:在监听自定义标签的基础上,在解析html文件时,会先返回不能解析的html标签,这样,开发者在这个回调中,可以在很早的时机拿到XmlReader,XmlReader是负责解析整个html文件的,其内部有一个监听器ContentHandler,用于监听整个解析过程,我们可以将xmlReader中的原始监听器进行保存,在需要特殊处理的时候做拦截,这样,变达到了监听已有标签的自定义属性的能力
具体代码示例如下:
public class HtmlTagHandler implements Html.TagHandler {
private static final String TAG = "HtmlTagHandler";
private volatile ContentHandler mOriginalContentHandler; //原始的监听器
//监听html解析信息,接手后续处理
@Override
public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
VLog.d(TAG, "opening: " + opening + " tag: " + tag);
//获取对xmlReader解析的控制权
if (mOriginalContentHandler == null) {
mOriginalContentHandler = xmlReader.getContentHandler();
xmlReader.setContentHandler(new HtmlContentHandlerWrapper(mOriginalContentHandler));//获取控制权,让HtmlContentHandlerWrapper监听解析流程
}
}
}
public class HtmlContentHandlerWrapper implements ContentHandler {
private static final String TAG = "HtmlContentHandlerWrapper";
private volatile ContentHandler mOriginalContentHandler; //原始的监听器
private static final String ORGINAL_URI = "自定义的属性"; //标签内部原始字段
private static final String A_TAG = "a"; //需要特殊处理的标签
public HtmlContentHandlerWrapper(ContentHandler handler) {
mOriginalContentHandler = handler;
}
/**
* 举例:拦截a标签,识别自定义字段
* @param uri
* @param localName
* @param qName
* @param atts
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
if (canHandleTag(localName)) { //拦截,判断是否可以解析该标签
VLog.d(TAG, "startElement custom");
AttributesImpl a = (AttributesImpl) atts;
String orignValue = a.getValue("", ORGINAL_URI);
if (!TextUtils.isEmpty(orignValue)) { //存在自定义的属性时做该操作
//自定义的处理
}
}
}
//原流程处理
this.mOriginalContentHandler.startElement(uri, localName, qName, atts);
}
private boolean canHandleTag(String tagName) {
return A_TAG.equalsIgnoreCase(tagName);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
this.mOriginalContentHandler.endElement(uri, localName, qName);
}
@Override
public void setDocumentLocator(Locator locator) {
mOriginalContentHandler.setDocumentLocator(locator);
}
@Override
public void startDocument() throws SAXException {
mOriginalContentHandler.startDocument();
}
@Override
public void endDocument() throws SAXException {
mOriginalContentHandler.endDocument();
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
mOriginalContentHandler.startPrefixMapping(prefix, uri);
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
mOriginalContentHandler.endPrefixMapping(prefix);
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
mOriginalContentHandler.characters(ch, start, length);
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
mOriginalContentHandler.ignorableWhitespace(ch, start, length);
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
mOriginalContentHandler.processingInstruction(target, data);
}
@Override
public void skippedEntity(String name) throws SAXException {
mOriginalContentHandler.skippedEntity(name);
}
}
相关知识xml文档解析的三种方案
SAX**:(Simple API for XML****)**这种解析方式基于事件的模型。通俗的讲就是XML文件在加载的过程中,加载到不同节点会相应触发不同方法来处理。它属于一次加载。它可以处理任意大小的XML文件,它对内存的要求非常低,因为SAX采用的是读取文件的方式,也就是当它是文本文件在读,读完就完了,什么信息都没有保存。当然它也有其缺点,解析过程中无法中断,只能读取XML文件而不能修改,编码上也相对复杂与难于理解。
DOM**:(Document Object Model****)**文档对象模型,它是基于对象的,又或者基于树的。它属于两次加载,首先把文档载入内存,第二次把文档解析形成一棵树。如果文档过大对内存占用是很大的。但它也有其优点,它可以解析的过程中修改文件树,可以随便存储文件树的任意部分,相对容易理解。
**Pull****解析:**android中内置了pull解析包。这也是android程序中所推荐的xml解析方式。从它的字面上就可以看出来,其优点,pull,拉的意思。我要什么资源我就拿什么资源。我只需要xml文件中一部分,我就拉一部分。从而节省资源,提高效率。当然在J2EE中也可以使用Pull解析。Pull解析也非常易于理解。