700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 如何扩展Android富文本之Html标签

如何扩展Android富文本之Html标签

时间:2019-01-18 09:09:41

相关推荐

如何扩展Android富文本之Html标签

前言

大家都知道Android 富文本其实就是HTML标签那些东西,但Android本身对其支持有限,今天就说说如何对其进行扩展

富文本

在Android设置富文本一般如下

String txt = "<strong>Hello World</strong>";textView.setText(HtmlCompat.fromHtml(txt,HtmlCompat.FROM_HTML_MODE_LEGACY));

这样就可以达到加粗的效果;如果要调整字体大小以及颜色呢?有人说很简单把富文本修改成

<span style='font-size:11px;color:#FF1A1A'>Hello World</span>

其实Android中的富文本中span标签中支持的属性有限,运行后你会发现上面写法其实并不生效,那有没办法让其生效呢? 答案是可以的。

我们先从源码角度来大体梳理下fromHtml的执行流程;

fromHtml流程

Html.java

//Html.javapublic static Spanned fromHtml(String source, int flags) {return fromHtml(source, flags, null, null);}public static Spanned fromHtml(String source, int flags, android.text.Html.ImageGetter imageGetter,android.text.Html.TagHandler tagHandler) {//1、创建解析器 Parser parser = new Parser();try {parser.setProperty(Parser.schemaProperty, HtmlParser.schema);} catch (org.xml.sax.SAXNotRecognizedException e) {...}//2、构建一个转换器,将html格式转化为原生的SpannedHtmlToSpannedConverter converter =new HtmlToSpannedConverter(source, imageGetter, tagHandler, parser, flags);return converter.convert();}

从代码可以看出非常简单,其实就是将Html的格式转化为Android可以认识的Spanned对象,这样就达到了Android支持富文本的效果了,这里面核心类就是HtmlToSpannedConverter

先看convert方法

public HtmlToSpannedConverter( String source, Html.ImageGetter imageGetter,Html.TagHandler tagHandler, Parser parser, int flags) {mSource = source;mSpannableStringBuilder = new SpannableStringBuilder();mImageGetter = imageGetter;mTagHandler = tagHandler;mReader = parser;mFlags = flags;}public Spanned convert() {//1、mReader就是上面的解析器Parser,并绑定了当前对象mReader.setContentHandler(this);try {//2、解析富文本mReader.parse(new InputSource(new StringReader(mSource)));} catch (IOException e) {...}...//3、返回了构造器中创建的成员变量return mSpannableStringBuilder;}

我们来看下ContentHandler接口有那些方法

重点关注下startElement方法,从字面意思上我们可以猜测出它是负责标签元素的解析处理的,而Parser.parse方法最终会调用到HtmlToSpannedConverter.startElement方法,

public void startElement(String uri, String localName, String qName, Attributes attributes)throws SAXException {handleStartTag(localName, attributes);}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 emit the linebreaks when we handle the close tag.} else if (tag.equalsIgnoreCase("p")) {startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph());startCssStyle(mSpannableStringBuilder, attributes);} else if (tag.equalsIgnoreCase("span")) {startCssStyle(mSpannableStringBuilder, attributes);} else if (tag.equalsIgnoreCase("strong")) {start(mSpannableStringBuilder, new Bold());} ...else if (mTagHandler != null) {mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);}}private void startCssStyle(Editable text, Attributes attributes) {String style = attributes.getValue("", "style");if (style != null) {Matcher m = getForegroundColorPattern().matcher(style);if (m.find()) {int c = getHtmlColor(m.group(1));if (c != -1) {start(text, new Foreground(c | 0xFF000000));}}m = getBackgroundColorPattern().matcher(style);if (m.find()) {int c = getHtmlColor(m.group(1));if (c != -1) {start(text, new Background(c | 0xFF000000));}}m = getTextDecorationPattern().matcher(style);if (m.find()) {String textDecoration = m.group(1);if (textDecoration.equalsIgnoreCase("line-through")) {start(text, new Strikethrough());}}}}private static void start(Editable text, Object mark) {int len = text.length();text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);}

从上面代码看出,handleStartTag方法就是解析富文本中的各种类型标签,从代码看支持有

pullidivspanstrongbemcitedfnibigsmallfontblockquotettaudelsstrikesupsubimg

真正解析标span标签的其实就是startCssStyle方法,从代码看该方法支持的属性有限,所以扩展span标签中属性其实一大部分就是考虑如何改写startCssStyle方法,其实类中除了startXxx方法还有endXxx方法,endCssStyle方法就是将startXxx方法中解析出的数据转变为原生的可识别数据并设置到mSpannableStringBuilder中

private static void endCssStyle(Editable text) {...Foreground f = getLast(text, Foreground.class);if (f != null) {setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor));}}private static void setSpanFromMark(Spannable text, Object mark, Object... spans) {int where = text.getSpanStart(mark);text.removeSpan(mark);int len = text.length();if (where != len) {for (Object span : spans) {text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);}}}

所以扩展span标签的思路很明确了,第一步在startCssStyle方法中解析出style标签中的属性集合,第二部在endCssStyle中对上一步解析的数据进行转化;

扩展

1. 类拷贝

因为startCssStyle方法都是私有我们无法复写,所以我们可以考虑把新建二个类来替代HtmlCompat、Html;先把Android原生的二个类拷贝到自己新建的二个类中,最后你会发现编译会失败,需要稍微调整下源码

调整一

Html.java

Application application = ActivityThread.currentApplication();

可以把它替换成

public static Application getCurrentApplication() {try {Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");Method method = activityThreadClass.getMethod("currentApplication");return (Application) method.invoke(null, (Object[]) null);} catch (Exception e) {e.printStackTrace();}return null;}

调整二

private static void startImg(Editable 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);//替换成下面二句int resId = Resources.getSystem().getIdentifier("unknown_image", "drawable", "android");d = Resources.getSystem().getDrawable(resId);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);}

调整三

private int getHtmlColor(String color) {if ((mFlags & android.text.Html.FROM_HTML_OPTION_USE_CSS_COLORS)== android.text.Html.FROM_HTML_OPTION_USE_CSS_COLORS) {Integer i = sColorMap.get(color.toLowerCase(Locale.US));if (i != null) {return i;}}// return Color.getHtmlColor(color);//替换下面try {return convertValueToInt(color, -1);} catch (NumberFormatException nfe) {return -1;}}public static final int convertValueToInt(CharSequence charSeq, int defaultValue){if (null == charSeq)return defaultValue;String nm = charSeq.toString();// XXX This code is copied from Integer.decode() so we don't// have to instantiate an Integer!int value;int sign = 1;int index = 0;int len = nm.length();int base = 10;if ('-' == nm.charAt(0)) {sign = -1;index++;}if ('0' == nm.charAt(index)) {// Quick check for a zero by itselfif (index == (len - 1))return 0;char c = nm.charAt(index + 1);if ('x' == c || 'X' == c) {index += 2;base = 16;} else {index++;base = 8;}}else if ('#' == nm.charAt(index)){index++;base = 16;}return Integer.parseInt(nm.substring(index), base) * sign;}

调整四

Parser类为系统自带的tagsoup库,我们为确保编译成功需在build.gradle文件添加

dependencies {compileOnly 'il.cowan.tagsoup:tagsoup:1.2.1'}

2. 改写方法

private void startCssStyle(Editable text, Attributes attributes) {String style = attributes.getValue("", "style");if (style != null) {String[] entryArray = style.split(";");if (entryArray != null) {for (String entry : entryArray) {String[] kv = entry.split(":");if (kv == null|| kv.length < 2|| TextUtils.isEmpty(kv[0])|| TextUtils.isEmpty(kv[1])) {continue;}String key = kv[0];String value = kv[1];/*** support font-size*/if ("font-size".equalsIgnoreCase(key)) {if (!TextUtils.isEmpty(value)) {if (value.endsWith("px")) {int size = (int) Float.parseFloat(value.substring(0, value.length() - 2));start(text, new Size(size));}}}//support colorif ("color".equalsIgnoreCase(key)) {if (!TextUtils.isEmpty(value)) {int c = getHtmlColor(value);if (c != -1) {start(text, new Foreground(c | 0xFF000000));}}}}}}//Android Origin Code// if (style != null) {// Matcher m = getForegroundColorPattern().matcher(style);// if (m.find()) {//int c = getHtmlColor(m.group(1));//if (c != -1) {//start(text, new Foreground(c | 0xFF000000));//}// }//// m = getBackgroundColorPattern().matcher(style);// if (m.find()) {//int c = getHtmlColor(m.group(1));//if (c != -1) {//start(text, new Background(c | 0xFF000000));//}// }//// m = getTextDecorationPattern().matcher(style);// if (m.find()) {//String textDecoration = m.group(1);//if (textDecoration.equalsIgnoreCase("line-through")) {//start(text, new Strikethrough());//}// }// }}private static class Size {public int mSize;public Size(int size) {mSize = size;}}

private static void endCssStyle(Editable text) {Strikethrough s = getLast(text, Strikethrough.class);if (s != null) {setSpanFromMark(text, s, new StrikethroughSpan());}Background b = getLast(text, Background.class);if (b != null) {setSpanFromMark(text, b, new BackgroundColorSpan(b.mBackgroundColor));}Foreground f = getLast(text, Foreground.class);if (f != null) {setSpanFromMark(text, f, new ForegroundColorSpan(f.mForegroundColor));}/*** support font-size*/Size size = getLast(text, Size.class);if (size != null) {setSpanFromMark(text, size, new AbsoluteSizeSpan(size.mSize, true));}}

这样就使Android支持下面富文本样式,当然我们可以参照上述操作可以继续扩展支持其他属性等。。。。

<span style='font-size:11px;color:#FF1A1A'>Hello World</span>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。