前言

大家都知道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方法,

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方法就是解析富文本中的各种类型标签,从代码看支持有

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标签相关推荐

  1. 安卓文本编辑器php cpp,开源的Android富文本编辑器

    RichEditor 基于原生EditText+span实现的Android富文本编辑器 github地址:https://github.com/yuruiyin/RichEditor 组件描述 该组 ...

  2. Android 富文本编辑器 图文混排

    富文本编辑器,如图:    Android 富文本编辑器实现思路: 默认状态下编辑器显示一个EditText,点击图片,选择插入本地图片或者拍照图片. 插入图片时,如果当前位置后面没有文字,则直接插入 ...

  3. Android富文本编辑器附源码

    Android富文本编辑器附源码 1,源码分析 本软件是Android端创建富文本数据,向服务器发送;安卓端创建数据可以是文字,图片,语音,文件,还可以录音,可以拍照.录音完毕保存的时候应该提交给服务 ...

  4. Android富文本编辑器(一)

    本项目参考了Android富文本开发_杨充-CSDN博客_android 富文本,做了大量精简工作,实现一个最基本富文本编辑器,具有文字和图片混合编排的功能,图片能够自适应屏幕大小. 效果展示 图片自 ...

  5. java 接收前台富文本_java 解析富文本处理 img 标签

    很多项目都需要到富文本来添加内容,就好比新闻啊,旅游景点之类的,都需要使用富文本去添加数据,然而怎么我这边就发现了两个问题 1)怎样将富文本的图片的 src 获取出来? 2)后台上传的时候用的是相对路 ...

  6. Android富文本编辑器,图片、视频、文字混合编辑

    一.添加依赖 开源框架xrichtext 只支持图文混编,我们在之上进行扩展.另采用 jiaozivideoplayer 作为播放器. allprojects {repositories {...ma ...

  7. Android富文本开发,从0到1!

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 每日英文 Each night I put my head to my pi ...

  8. android富文本图片自适应,Android 图片混排富文本编辑器控件

    一.一个Android 图片混排富文本编辑器控件(仿兴趣部落) 1.1 图片混排富文本控件 是一种图片和文字混合在一起的控件,文本之间可以插入图片,类似于网页的排版样式. 1.2 该控件主要是仿兴趣部 ...

  9. android 富文本框架_五种JavaScript富文本编辑器,总有一款适合你

    全文共2099字,预计学习时长4分钟 也许,你时常会遇到要开发基于Web的文本编辑器的情况.有时候,只需实现一个简约且轻量级的应用程序,不必有其他任何不必要的功能.而有时候,你的首要任务是保护用户的商 ...

最新文章

  1. [ext4]07 磁盘布局 - 块/inode分配策略
  2. jQuery最佳实践
  3. sparkstreaming监听hdfs目录_flume kafka和sparkstreaming整合
  4. THUPCCTSAPIO摸鱼被$\Huge{\color{black}{\mathbf{z}}\color{red}{\mathbf{zh}}}$爆踩记
  5. 对数函数定义域和值域为r_对数函数
  6. 人工智能急需网络安全“背书”
  7. Python学习路程day9
  8. html设置字体仿宋GB2312,怎么设置仿宋gb2312字体,仿宋gb2312字体设置教程
  9. 使用微 PE(U盘)安装 Windows 10 操作系统
  10. 中国情报监视和侦察行业市场供需与战略研究报告
  11. android 指纹框架,Android标准化指纹识别框架(只基于api23官方标准)
  12. 2018版本webstorm的安装与汉化!【最新,超全,超详细!】
  13. github语法使用
  14. 2012网页服务器搭建教程,服务器2012搭建vps教程
  15. 静态网页项目部署到云服务器上
  16. 【Visual c++】+【EasyX】游戏组件1 移动的小人
  17. 纽约大学坦顿学院计算机排名,纽约大学坦顿工程学院排名多少?
  18. 积分与通证有什么区别?什么是TZC积分通证?
  19. 主流 .NET界面控件套包对比介绍
  20. 赋予金融科技人性化 易通贷召开品牌重塑发布会

热门文章

  1. Android 使用ContentProvider(内容提供者)查询手机联系
  2. 【数据分析实例】6000 条倒闭企业数据分析
  3. 三十四、多线程真的比单线程快?
  4. 工程制图 (点,线,面)
  5. 转专业入门NLP,这样学就对了
  6. 开启注册 | AAAI 2022论文北京预讲会,21场报告+24个Poster等你来
  7. RoBERTa中文预训练模型:RoBERTa for Chinese
  8. 数学建模第四节2020.4.24-5.3补
  9. Java基础:HashMap的用法
  10. java处理最后一周_Java获取某年某周的最后一天