如何扩展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.java public 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方法,
從上面代碼看出,handleStartTag方法就是解析富文本中的各種類型標簽,從代碼看支持有
p ul li div span strong b em cite dfn i big small font blockquote tt a u del s strike sup sub img真正解析標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 'org.ccil.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>總結
以上是生活随笔為你收集整理的如何扩展Android富文本之Html标签的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python Excel表格操作总结
- 下一篇: Android-JSNative交互的几