Html能够通过Html标签来为文字设置样式,让TextView显示富文本信息,其只支持部分标签不是全部,具体支持哪些标签将分析中揭晓。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.tv_html);
String htmlString =
"颜色
" +
"br/>" +
"大字体
"+
"小字体
"+
"加粗
"+
"斜体
" +
"标题一
" +
"标题二
" +
"
" +
"引用
" +
"块
" +
"下划线
" +
"上标正常字体下标
" +
"组合样式字体";
textView.setText(Html.fromHtml(htmlString));
由此可以看出Html还是比较强大的一个东西呀!
使用Html.toHtml方法能够将带有样式效果的Spanned文本对象生成对应的Html格式,标签内的字符会被转译成,下面为WebView显示效果,部分效果与上面TextView显示的效果有差异,代码如下:
webView.loadData(Html.toHtml(Html.fromHtml(htmlString)),"text/html", "utf-8");
显示效果还是有点差距的,用的是安卓4.0.3的手机系统,所以可能显示上有点问题,不过应该不影响大家区分。重点毕竟不在这里,大家继续往下看原理吧!
首先我们先看看html类:
/*** 该类将HTML处理成带样式的文本,但不支持所有的HTML标签
*/
public class Html {
* 为
标签提供图片检索功能
*/public static interface ImageGetter { /**
* 当HTML解析器解析到
标签时,source参数为标签中的src的属性值,
* 返回值必须为Drawable;如果返回null则会使用小方块来显示,如前面所见,
* 并需要调用Drawable.setBounds()方法来设置大小,否则无法显示图片。
* @param source:
public Drawable getDrawable(String source);
* HTML标签解析扩展接口
*/public static interface TagHandler { /**
* 当解析器解析到本身不支持或用户自定义的标签时,该方法会被调用
* @param opening:标签是否打开
* @param tag:标签名
* @param output:截止到当前标签,解析到的文本内容
* @param xmlReader:解析器对象
public void handleTag(boolean opening, String tag,
Editable output, XMLReader xmlReader);
}private Html() { }/**
* 返回样式文本,所有
标签都会显示为一个小方块
* 使用TagSoup库处理HTML
* @param source:带有html标签字符串
*/public static Spanned fromHtml(String source) { return fromHtml(source, null, null);
* 可传入ImageGetter来获取图片源,TagHandler添加支持其他标签
*/public static Spanned fromHtml(String source, ImageGetter imageGetter,
TagHandler tagHandler) {
.....
* 将带样式文本反向解析成带Html的字符串,注意这个方法并不是还原成fromHtml接收的带Html标签文本
*/public static String toHtml(Spanned text) {
StringBuilder out = new StringBuilder();
withinHtml(out, text); return out.toString();
* 返回转译标签后的字符串
*/public static String escapeHtml(CharSequence text) {
StringBuilder out = new StringBuilder();
withinStyle(out, text, 0, text.length()); return out.toString();
* 懒加载HTML解析器的Holder
* a) zygote对其进行预加载
* b) 直到需要的时候才加载
*/private static class HtmlParser { private static final HTMLSchema schema = new HTMLSchema();
fromHtml(String source, ImageGetter imageGetter,TagHandler tagHandler):
Html类主要方法就4个,功能也简单,生成带样式的fromHtml方法最终都是调用重载3个参数的方法。
public static Spanned fromHtml(String source, ImageGetter imageGetter,
TagHandler tagHandler) { //初始化解析器
Parser parser = new Parser(); try { //配置解析Html模式
parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
} catch (org.xml.sax.SAXNotRecognizedException e) { throw new RuntimeException(e);
} catch (org.xml.sax.SAXNotSupportedException e) { throw new RuntimeException(e);
} //初始化真正的解析器
HtmlToSpannedConverter converter = new HtmlToSpannedConverter(source, imageGetter, tagHandler,parser); return converter.convert();
源代码中并没有包含Parser对象,而是必须导入org.ccil.cowan.tagsoup.Parser,HTML解析器是使用Tagsoup库来解析HTML标签,Tagsoup是兼容SAX的解析器,我们知道对XML常见的的解析方式还有DOM、Android系统中还使用PULL解析与SAX同样是基于事件驱动模型,使用tagsoup是因为该库可以将HTML转化为XML,我们都知道HTML有时候并不像XML那样标签都需要闭合,例如
也是一个有效的标签,但是在XML中则是不良格式。详情可见官方网站,但是好像没有开发文档,这里就不详细说明,只关注SAX解析过程。
HtmlToSpannedConverter原理
class HtmlToSpannedConverter implements ContentHandler { private static final float[] HEADER_SIZES = { 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f,
private String mSource; private XMLReader mReader; private SpannableStringBuilder mSpannableStringBuilder; private Html.ImageGetter mImageGetter; private Html.TagHandler mTagHandler;
public HtmlToSpannedConverter(
String source, Html.ImageGetter imageGetter, Html.TagHandler tagHandler,
Parser parser) {
mSource = source;//html文本
mSpannableStringBuilder = new SpannableStringBuilder();//用于存放标签中的字符串
mImageGetter = imageGetter;//图片加载器
mTagHandler = tagHandler;//自定义标签器
mReader = parser;//解析器
public Spanned 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);
} //省略
... return mSpannableStringBuilder;
通过上面代码可以发现,SpannableStringBuilder是用来存放解析html标签中的字符串,类似StringBuilder,但它附带有样式的字符串。重点关注convert里面的setContentHandler方法,该方法接收的是ContentHandler接口,使用过SAX解析的读者应该不陌生,该接口定义了一系列SAX解析事件的方法。
public interface ContentHandler{ //设置文档定位器
public void setDocumentLocator (Locator locator); //文档开始解析事件
public void startDocument ()
throws SAXException; //文档结束解析事件
public void endDocument()
throws SAXException; //解析到命名空间前缀事件
public void startPrefixMapping (String prefix, String uri)
throws SAXException; //结束命名空间事件
public void endPrefixMapping (String prefix)
throws SAXException; //解析到标签事件
public void startElement (String uri, String localName,
String qName, Attributes atts)
throws SAXException; //标签结束事件
public void endElement (String uri, String localName,
String qName)
throws SAXException; //标签中内容事件
public void characters (char ch[], int start, int length)
throws SAXException; //可忽略的空格事件
public void ignorableWhitespace (char ch[], int start, int length)
throws SAXException; //处理指令事件
public void processingInstruction (String target, String data)
throws SAXException; //忽略标签事件
public void skippedEntity (String name)
throws SAXException;
对应HtmlToSpannedConverter中的实现。
public void setDocumentLocator(Locator locator) {}public void startDocument() throws SAXException {}public void endDocument() throws SAXException {}public void startPrefixMapping(String prefix, String uri) throws SAXException {}public void endPrefixMapping(String prefix) throws SAXException {}public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
handleStartTag(localName, attributes);
}public void endElement(String uri, String localName, String qName) throws SAXException {
handleEndTag(localName);
}public void characters(char ch[], int start, int length) throws SAXException { //忽略
}public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {}public void processingInstruction(String target, String data) throws SAXException {}public void skippedEntity(String name) throws SAXException {}
我们发现该类中只实现了startElement,endElement,characters这三个方法,所以只关心标签的类型和标签里的字符。然后调用mReader.parse方法,开始对HTML进行解析。解析的事件流如下: startElement -> characters -> endElement startElemnt里面调用的是handleStartTag方法,endElement则是调用handleEndTag方法。
* @param tag:标签类型
* @param attributes:属性值
* 例如遇到标签,tag="font",attributes={"color":"#FFFFFF"}
*/private void handleStartTag(String tag, Attributes attributes) { if (tag.equalsIgnoreCase("br")) { // 我们不需要关心br标签是否有闭合,因为Tagsoup会帮我们处理
} 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) = '1' && tag.charAt(1) 从上面方法中我们可以总结出支持的HTML标签列表
strong
small
blockquote
monospace
h1-h6
标签是如何处理的
这里分析如何处理
标签,在handleStartTag方法中可以发现br标签直接被忽略了,在handleEndTag方法中才被真正处理。
private void handleEndTag(String tag) {
... if (tag.equalsIgnoreCase("br")) {
handleBr(mSpannableStringBuilder);
}//代码很简单,直接加换行符private static void handleBr(SpannableStringBuilder text) {
text.append("n");
p标签为段落,其作用是给p标签中的文字前后换行,在handleStartTag和handleEndTag遇到p标签都是调用handleP方法,characters则添加p标签之间的字符串。
private void handleStartTag(String tag, Attributes attributes) {
... else if (tag.equalsIgnoreCase("p")) {
handleP(mSpannableStringBuilder);
}private void handleEndTag(String tag) {
... else if (tag.equalsIgnoreCase("p")) {
handleP(mSpannableStringBuilder);
}private static void handleP(SpannableStringBuilder text) { int len = text.length();
if (len >= 1 && text.charAt(len - 1) == 'n') { if (len >= 2 && text.charAt(len - 2) == 'n') { //如果前面两个字符都为换行符,则忽略
return;
} //否则添加一个换行符
text.append("n"); return;
} //其他情况添加两个换行符
if (len != 0) {
text.append("nn");
strong标签
该标签作用是为加粗字体,在handleStartTag和handleEndTag分别调用start和end方法。
private void handleStartTag(String tag, Attributes attributes) {
... else if (tag.equalsIgnoreCase("strong")) {
start(mSpannableStringBuilder, new Bold());
}private static class Bold { }//什么都没有private void handleEndTag(String tag) {
... else if (tag.equalsIgnoreCase("strong")) {
end(mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD));
}private static void start(SpannableStringBuilder text, Object mark) { int len = text.length(); //mark作为类型标记并没有实际功能,指明开始的位置,
//结束位置延迟到`end`方法中处理,
//Spannable.SPAN_MARK_MARK表示当文本插入偏移时,它们仍然保持在它们的原始偏移量上。从概念上讲,文本是在标记之后添加的。
text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
private static void end(SpannableStringBuilder text, Class kind,Object repl) { //当前字符长度
int len = text.length(); //根据kind获取最后一个set进去的对象
Object obj = getLast(text, kind); //获取标签起始位置
int where = text.getSpanStart(obj); //去除标记对象
text.removeSpan(obj);
if (where != len) { //len则为结束的位置,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE是设置样式文字区间为闭区间
//将真正的样式对象repl设置进去,Bold对应StyleSpan类型,Typeface.BOLD 加粗样式
text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}private static Object getLast(Spanned text, Class kind) { /*
* 获取最后一个类型为king,在setSpan传入的对象
* 例如kind类型为Bold.class,则会返回在start中set进去的Bold对象
Object[] objs = text.getSpans(0, text.length(), kind);
if (objs.length == 0) { return null;
} else { //如果有期间有多个,则获取最后一个
return objs[objs.length - 1];
经过start和end方法处理后,strong标签中的文本就被加粗,具体的样式类型这里不做详解,后续可以参考Spannable源码解析这篇目前还没人认领文章,其他为字体设置不同的样式过程一致,在handleStartTag根据不同标签类型调用start时方法传入不同对象给mark,并在handleEndTag中不同标签调用end并传入不同样式。
font标签
font标签可以给字符串指定颜色和字体。
private void handleStartTag(String tag, Attributes attributes) {
... else if (tag.equalsIgnoreCase("font")) { //attributes带有标签中的属性
//例如,属性将以key-value的形式存在,{"color":"#FFFFFF"}。
startFont(mSpannableStringBuilder, attributes);
}private static void startFont(SpannableStringBuilder text,Attributes attributes) {
String color = attributes.getValue("", "color");//获取color属性
String face = attributes.getValue("", "face");//获取face属性
int len = text.length(); //Font同样是一个用来标记属性的对象,没有实际功能
text.setSpan(new Font(color, face), len, len, Spannable.SPAN_MARK_MARK);
}//保存颜色值和字体类型private static class Font { public String mColor; public String mFace; public Font(String color, String face) {
mColor = color;
mFace = face;
}private void handleEndTag(String tag) {
... else if (tag.equalsIgnoreCase("font")) {
endFont(mSpannableStringBuilder);
}private static void endFont(SpannableStringBuilder text) { int len = text.length();
Object obj = getLast(text, Font.class); int where = text.getSpanStart(obj);
text.removeSpan(obj); if (where != len) {
Font f = (Font) obj; //前面与strong标签解析过程相似,多了下面处理颜色和字体的逻辑
if (!TextUtils.isEmpty(f.mColor)) { //如果color属性中以"@"开头,则是获取colorId对应的颜色值
//注意:只能支持android.R的资源
if (f.mColor.startsWith("@")) {
Resources res = Resources.getSystem();
String name = f.mColor.substring(1); int colorRes = res.getIdentifier(name, "color", "android"); if (colorRes != 0) { //也可以是color selector,则会根据不同状态显示不同颜色
ColorStateList colors = res.getColorStateList(colorRes, null); //1、通过TextAppearanceSpan设置颜色
text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null),
where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else { //如果为"#"开头则解析颜色值
int c = Color.getHtmlColor(f.mColor); if (c != -1) { //2、通过ForegroundColorSpan直接设置字体的rgb值
text.setSpan(new ForegroundColorSpan(c | 0xFF000000),
where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} if (f.mFace != null) { //如果有face参数则通过TypefaceSpan设置字体
text.setSpan(new TypefaceSpan(f.mFace), where, len,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
具体支持哪些字体,在TypefaceSpan的apply方法中会先去解析对应的字体,然后绘制出来,源码如下。
private static void apply(Paint paint, String family) {
... //解析字体
Typeface tf = Typeface.create(family, oldStyle);
Typeface源码
* 根据字体名称获取字体对象,如果familyName为null,则返回默认字体对象
* 调用getStyle可查看该字体style属性
* @param 字体名称,可能为null
* @param style NORMAL(标准), BOLD(粗体), ITALIC(斜体), BOLD_ITALIC(粗斜)
* @return 匹配的字体
*/public static Typeface create(String familyName, int style) { if (sSystemFontMap != null) { //字体缓存在sSystemFontMap中
return create(sSystemFontMap.get(familyName), style);
} return null;
} //init方法中初始化sSystemFontMap
private static void init() { // 获取字体配置文件目录
//private static File getSystemFontConfigLocation() {
//return new File("/system/etc/");
File systemFontConfigLocation = getSystemFontConfigLocation(); //获取字体配置文件
//static final String FONTS_CONFIG = "fonts.xml";
File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG); try { //将字体名称更Typeface对象缓存在map中
//具体解析过程忽略,有兴趣可自行翻阅源码
sSystemFontMap = systemFonts;
} catch (RuntimeException e) {
img标签
//img标签只有在标签开始时处理
private void handleStartTag(String tag, Attributes attributes) {
...else if (tag.equalsIgnoreCase("img")) {
startImg(mSpannableStringBuilder, attributes, mImageGetter);
//与其他标签处理过程多了Attributes标签属性,Html.ImageGetter 自定义图片获取
private static void startImg(SpannableStringBuilder text,
Attributes attributes, Html.ImageGetter img) { //获取src属性
String src = attributes.getValue("", "src");
Drawable d = null; if (img != null) { //调用自定义的图片获取方式,并传入src属性值
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"); //通过使用ImageSpan设置图片效果
text.setSpan(new ImageSpan(d, src), len, text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
自定义标签
private void handleStartTag(String tag, Attributes attributes) {
... else if (mTagHandler != null) {//通过自定义标签处理器来扩展自定义标签
mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);
}private void handleEndTag(String tag) {
... else if (mTagHandler != null) { //闭合标签
mTagHandler.handleTag(false, tag, mSpannableStringBuilder, mReader);
关于自定义标签有个小问题是,handleTag并没有传入Attributes标签属性,所以无法直接获取自定义标签的属性值,下面给出两种方案解决这个问题:
1.通过某一部分标签名作为属性值,例如标签,我们想加入id的参数,则可将标签名变为,然后在handleTag中自行解析。
2.通过反射XMLReader来获取属性值,具体例子可参考stackoverflow:How to get an attribute from an XMLReader
convert方法剩下部分
不要忽略了parse之后还有一部分代码。
// 修正段落标记范围
//ParagraphStyle为段落级别样式Object[] obj = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class);for (int i = 0; i = 0) { if (mSpannableStringBuilder.charAt(end - 1) == 'n' &&
mSpannableStringBuilder.charAt(end - 2) == 'n') {
end--;
} if (end == start) { //除去没有显示的样式
mSpannableStringBuilder.removeSpan(obj[i]);
} else { //Spannable.SPAN_PARAGRAPH以换行符为起始点和终点
mSpannableStringBuilder.setSpan(obj[i], start, end, Spannable.SPAN_PARAGRAPH);
}return mSpannableStringBuilder;
toHtml解析
样式级别主要分为两类:一类是段落级别,另一类是字符级别。 toHtml方法是从大范围级别到小范围级别解析,即先解析段落样式再解析字符样式。
public static String toHtml(Spanned text) {
StringBuilder out = new StringBuilder();
withinHtml(out, text); return out.toString();
private static void withinHtml(StringBuilder out, Spanned text) {int len = text.length();//遍历段落级别样式int next;for (int i = 0; i 标签,align属性代表段落中字符对齐方向
for(int j = 0; j ");
} //解析段落样式中其他样式
withinDiv(out, text, i, next); //调用层次较深,先忽略后面
//start为段落起点,end为结尾private static void withinDiv(StringBuilder out, Spanned text,int start, int end) { int next; for (int i = start; i ");
} //解析里面的样式
withinBlockquote(out, text, i, next); //忽略
private static void withinBlockquote(StringBuilder out, Spanned text,int start, int end) {//解析文本显示方向out.append(getOpenParaTagWithDirection(text, start, end));
...int next;for (int i = start; i private static String getOpenParaTagWithDirection(Spanned text, int start, int end) { final int len = end - start; final byte[] levels = ArrayUtils.newUnpaddedByteArray(len);//通过VMRuntime创建byte数组
final char[] buffer = TextUtils.obtain(len);//底层通过ArrayUtils.newUnpaddedCharArray(len);创建char数组
TextUtils.getChars(text, start, end, buffer, 0);//将字符缓存到buffer数组中
//通过bidi来解析文字方向,作用是兼容多国语言的排列方向,例如阿利伯文字排列是自右向左。
//里面主要调用native方法,具体可参考系统源码。
int paraDir = AndroidBidi.bidi(Layout.DIR_REQUEST_DEFAULT_LTR, buffer, levels, len,false /* no info */); //使用p标签加dir属性修饰文本方向
switch(paraDir) { case Layout.DIR_RIGHT_TO_LEFT: return ""; case Layout.DIR_LEFT_TO_RIGHT: default: return "
";
}
private static void withinBlockquote(StringBuilder out, Spanned text,int start, int end) { //解析文本显示方向
out.append(getOpenParaTagWithDirection(text, start, end)); //回来
int next; for (int i = start; i //代码较多,截取前部分//start为起始位置,end为除去换行符文本结尾位置,nl换行符个数,last是否为文本末尾private static boolean withinParagraph(StringBuilder out, Spanned text, int start, int end, int nl,
boolean last) { int next; for (int i = start; i ");
} //斜体使用i标签包裹
if ((s & Typeface.ITALIC) != 0) { out.append("");
} //TypefaceSpan样式
if (style[j] instanceof TypefaceSpan) { //获取字体类型
String s = ((TypefaceSpan) style[j]).getFamily(); //monospace使用tt标签包裹
if ("monospace".equals(s)) { out.append("");
} //上标样式使用sup标签包裹
if (style[j] instanceof SuperscriptSpan) { out.append("");
} //小标使用sub标签包裹
if (style[j] instanceof SubscriptSpan) { out.append("");
} //下划线使用u标签变过
if (style[j] instanceof UnderlineSpan) { out.append("");
} //删除线使用strike标签
if (style[j] instanceof StrikethroughSpan) { out.append("");
} //urlspan使用a标签加href属性的标签包裹
if (style[j] instanceof URLSpan) { out.append("");
} //图片使用img加src属性的标签包裹
if (style[j] instanceof ImageSpan) { out.append("
"); // Don't output the dummy character underlying the image.
i = next;
} //绝对大小样式使用font加size属性标签包裹
if (style[j] instanceof AbsoluteSizeSpan) { out.append("");
} //前景色使用font加color属性标签,只支持#
//不支持@描述的资源颜色,因为@使用TextAppearanceSpan类型
if (style[j] instanceof ForegroundColorSpan) { out.append("");
withinStyle(out, text, i, next);
}
//将字符进行转码private static void withinStyle(StringBuilder out, CharSequence text, int start, int end) { for (int i = start; i & '空格' 变成转译字符
if (c == '') { out.append(">");
} else if (c == '&') { out.append("&");
} else if (c >= 0xD800 && c = 0xDC00 && d 0x7E || c //跳出withinStyle接着回到withinParagraph方法private static boolean withinParagraph(StringBuilder out, Spanned text, int start, int end, int nl,
boolean last) {
...//接下来很好理解,把上面包裹的标签闭合for (int j = style.length - 1; j >= 0; j--) { if (style[j] instanceof ForegroundColorSpan) { out.append("
");
} if (style[j] instanceof AbsoluteSizeSpan) { out.append("");
} if (style[j] instanceof URLSpan) { out.append("");
} if (style[j] instanceof StrikethroughSpan) { out.append("");
} if (style[j] instanceof UnderlineSpan) { out.append("");
} if (style[j] instanceof SubscriptSpan) { out.append("");
} if (style[j] instanceof SuperscriptSpan) { out.append("");
} if (style[j] instanceof TypefaceSpan) {
String s = ((TypefaceSpan) style[j]).getFamily(); if (s.equals("monospace")) { out.append("");
} if (style[j] instanceof StyleSpan) { int s = ((StyleSpan) style[j]).getStyle(); if ((s & Typeface.BOLD) != 0) { out.append("");
} if ((s & Typeface.ITALIC) != 0) { out.append("");
} //添加br标签
if (nl == 1) { out.append("
n"); return false;
} else { for (int i = 2; i ");
} return !last;
//跳出withinParagraph回到withinBlockquote剩下部分private static void withinBlockquote(StringBuilder out, Spanned text,int start, int end) {
... if (withinParagraph(out, text, i, next - nl, nl, next == end)) { /* 闭合p标签 */
out.append("n"); out.append(getOpenParaTagWithDirection(text, next, end));//判断字符排列顺序,添加p标签
} //闭合p标签
out.append("n");
private static void withinDiv(StringBuilder out, Spanned text, int start, int end) {
withinBlockquote(out, text, i, next); //逐个闭合
for (QuoteSpan quote : quotes) { out.append("n");
private static void withinHtml(StringBuilder out, Spanned text) {
withinDiv(out, text, i, next); //按需要添加闭合div标签,即有AlignmentSpan类型样式
if (needDiv) { out.append("
}escapeHtml解析
public static String escapeHtml(CharSequence text) {
StringBuilder out = new StringBuilder(); //直接调用withinStyle方法进行转译
withinStyle(out, text, 0, text.length()); return out.toString();
原文链接:http://www.apkbus.com/blog-902332-68287.html
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/3705/viewspace-2814182/,如需转载,请注明出处,否则将追究法律责任。
广播电视节目制作经营许可证(京) 字第1234号 中国互联网协会会员