目录

  • 富文本内容与效果
  • TextView + Html
  • ImageGetter 处理图片(表情)
  • TagHandler 处理html内容的节点
  • Html的转换过程
    • HtmlToSpannedConverter
      • handleStartTag
      • startCssStyle(mSpannableStringBuilder, attributes)字体无效果实现
      • getForegroundColorPattern颜色不显示的坑
  • 处理办法
    • 颜色修改
    • 粗体支持
    • 斜体支持

来了来了,
html页面内容不是用用webview的吗,TextView显示html什么鬼?
老铁莫急,没错,html页面内容多数情况下都是用webview来显示的,尤其是app里面常见的"关于"、“隐私政策”等,这都是单一的显示,或者整个页面就显示这么一个page页面,自然也就选择webview。

当遇到富文本这样的html内容片段,而且是以列表方式显示多段内容不一样的内容时候,怎么办呢。基于源生的习惯,自然就是TextView + Html了。
有坑,但问题不大。

富文本内容与效果

如下一段html,粗体、斜体、橘色和带了一个表情:

<SPAN style="FONT-SIZE: 10pt; FONT-WEIGHT: bold; COLOR: #ff8000; FONT-STYLE: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy
</SPAN>

效果:

TextView + Html

TextView 就不介绍了,主要介绍Html这个类。
Html.java在android.text这个package下,包名也就看出是和文字相关的类。其核心本质是解析html内容,根据html的style给构造出Spanned,Spanned又是什么东西?Spanned是CharSequence的孩子。
平常给TextView设置文字 setText(CharSequence text)这个函数的参数就是CharSequence ,只不过实际传递的是CharSequence 的另外一个孩子String。

Html.java 提供html内容和Spanned的相互转换:
html->Spanned:

public static Spanned fromHtml(String source, int flags)
public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter,TagHandler tagHandler)

Spanned->html:

public static String toHtml(Spanned text, int option)

但实际上上述内容出表情外,其他的效果完全没有,包括颜色。具体请往后看

ImageGetter 处理图片(表情)

当遇到<img>标签的时候就会回调, 对应的返回一个Drawable对象。source 参数就是<img>的src属性的内容,也就是图片路径。根据上文给出的内容,这里source是“emotion\emotion.rb.gif”。同时注意返回的Drawable对象一定要给定边界,也就是drawable.setBounds(),不然不会显示图片。如果这里返回一个null,一般出现一个小矩形。

/*** Retrieves images for HTML &lt;img&gt; tags.*/public static interface ImageGetter {/*** This method is called when the HTML parser encounters an* &lt;img&gt; tag.  The <code>source</code> argument is the* string from the "src" attribute; the return value should be* a Drawable representation of the image or <code>null</code>* for a generic replacement image.  Make sure you call* setBounds() on your Drawable if it doesn't already have* its bounds set.*/public Drawable getDrawable(String source);}

TagHandler 处理html内容的节点

这个标签捕获是有条件的,如果html内容的标签没有在Html中定义捕才回调出来,在显示效果上是没效的,需要自行处理这个标签,对应的编辑output。

 /*** 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);}

注释讲的清楚,parser 识别不了的就会回调通知。

Html的转换过程

从fromHtml入口可以看出,是HtmlToSpannedConverter 在工作,执行convert

    public static Spanned fromHtml(String source, int flags, ImageGetter imageGetter,TagHandler tagHandler) {Parser parser = new Parser();try {parser.setProperty(Parser.schemaProperty, HtmlParser.schema);} catch (org.xml.sax.SAXNotRecognizedException e) {// Should not happen.throw new RuntimeException(e);} catch (org.xml.sax.SAXNotSupportedException e) {// Should not happen.throw new RuntimeException(e);}HtmlToSpannedConverter converter =new HtmlToSpannedConverter(source, imageGetter, tagHandler, parser, flags);return converter.convert();}

HtmlToSpannedConverter

从代码上看HtmlToSpannedConverter 还不是内部类,是和Html平行定义的。且实现了ContentHandler,ContentHandler就是xml解析回调接口,而该类主要处理了3个回调,其余空实现:

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 {StringBuilder sb = new StringBuilder();/** Ignore whitespace that immediately follows other whitespace;* newlines count as spaces.*/for (int i = 0; i < length; i++) {char c = ch[i + start];if (c == ' ' || c == '\n') {char pred;int len = sb.length();if (len == 0) {len = mSpannableStringBuilder.length();if (len == 0) {pred = '\n';} else {pred = mSpannableStringBuilder.charAt(len - 1);}} else {pred = sb.charAt(len - 1);}if (pred != ' ' && pred != '\n') {sb.append(' ');}} else {sb.append(c);}}mSpannableStringBuilder.append(sb);}

当开始一个标签时调用 handleStartTag
结束一个标签时调用handleEndTag
解析到文本的时候就追加到mSpannableStringBuilder

handleStartTag

这里能看出Html类处理了多少标签,同时也应证没有定义的标签都抛给外部处理
注意这里标签的匹配不区分大小写 (equalsIgnoreCase)

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("ul")) {startBlockElement(mSpannableStringBuilder, attributes, getMarginList());} else if (tag.equalsIgnoreCase("li")) {startLi(mSpannableStringBuilder, attributes);} else if (tag.equalsIgnoreCase("div")) {startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv());} else if (tag.equalsIgnoreCase("span")) {startCssStyle(mSpannableStringBuilder, attributes);} 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")) {startBlockquote(mSpannableStringBuilder, attributes);} 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("del")) {start(mSpannableStringBuilder, new Strikethrough());} else if (tag.equalsIgnoreCase("s")) {start(mSpannableStringBuilder, new Strikethrough());} else if (tag.equalsIgnoreCase("strike")) {start(mSpannableStringBuilder, new Strikethrough());} 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) <= '6') {startHeading(mSpannableStringBuilder, attributes, tag.charAt(1) - '1');} else if (tag.equalsIgnoreCase("img")) {startImg(mSpannableStringBuilder, attributes, mImageGetter);} else if (mTagHandler != null) {//除以上标签以外,都回调给外部处理mTagHandler.handleTag(true, tag, mSpannableStringBuilder, mReader);}}

明明<SPAN> 是被解析的(tag.equalsIgnoreCase(“span”),就是没有颜色和粗体、斜体呢?再看startCssStyle(mSpannableStringBuilder, attributes)

startCssStyle(mSpannableStringBuilder, attributes)字体无效果实现

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());}}}}

取出style 属性,且此处指处理颜色,并没有处理字体,字体肯定是不会有效果的了。接着看getForegroundColorPattern。

getForegroundColorPattern颜色不显示的坑

再看取属性里面的颜色是通过正则表达式来取的,前景色正则表达式: “(?:\s+|\A)color\s*:\s*(\S*)\b”)

private static Pattern getForegroundColorPattern() {if (sForegroundColorPattern == null) {sForegroundColorPattern = Pattern.compile("(?:\\s+|\\A)color\\s*:\\s*(\\S*)\\b");}return sForegroundColorPattern;}

这里是个坑,color是小写,内容中的是COLOR,没有匹配到颜色。同时背景色也是小写的color。

处理办法

调用还是不变,但要对原始内容进行修改

颜色修改

直接将style 属性的值修改为小写

<!--这样就可以显示颜色了-->
<SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy</SPAN>

粗体支持

从代码上看是明确不处理style 属性中的粗体,但从handleStartTag解析中有如下片段

 else if (tag.equalsIgnoreCase("strong")) {start(mSpannableStringBuilder, new Bold());} else if (tag.equalsIgnoreCase("b")) {start(mSpannableStringBuilder, new Bold());}

基于这个片段,判断style中属性中的font-weight如果是bold值,那么直接在原内容基础上包裹标签<strong>或<b>。
具体如下:

<b><SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy</SPAN>
</b>//或
<strong><SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy</SPAN>
</strong>

斜体支持

思路和粗体一样,选择多一点
代码片段:

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());}

基于这个片段,判断style中属性中的font-style如果是italic值,那么直接在原内容基础上包裹标签<em>,<cite>,<dfn>,<i>之一
具体如下:

<em><b><SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy</SPAN></b>
</em>
//或
<cite><b><SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy</SPAN></b>
</cite>//或
<dfn><b><SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy</SPAN></b>
</dfn>
//或
<i><b><SPAN style="font-size: 10pt; font-weight: bold; color: #ff8000; font-style: italic">hello<IMG src="emotion\emotion.rb.gif" thePath custom="false">boy</SPAN></b>
</i>

至此,这段html的颜色、粗体、斜体都能显示了。

Andoid TextView显示富文本html内容及问题处理相关推荐

  1. 微信小程序加载并且编译显示富文本编辑器内容

    微信小程序如何加载并且显示百度编辑器中的内容 一. 下载wxParse文件夹放在根目录下(可以随意更改位置,只要后续能引入成功即可) 二. 在js文件中引入wxParse.js var WxParse ...

  2. jsp页面显示富文本框内容

    2019独角兽企业重金招聘Python工程师标准>>> 1.未处理 2.处理后 相关代码: (1)jar包  (2)前端代码 <td>${fns:removeHtml(a ...

  3. android 加载显示富文本——TextView显示富文本和WebView显示富文本,WebView显示图片适配屏幕宽度

    TextView加载显示 添加依赖 implementation 'com.zzhoujay.richtext:richtext:3.0.8' implementation 'com.zzhoujay ...

  4. 保存富文本编辑器内容

    在这里我使用的是layUI的layedit模块,layUI中的富文本编辑器模块. 第一步我们先将页面搭建好,引入layui.layedit模块和layui.form模块.form模块可用于表单的数据验 ...

  5. 使用UIWebView中html标签显示富文本

    使用UIWebView中html标签显示富文本 用UIWebView来渲染文本并期望达到富文本的效果开销很大哦! Work 本人此处直接加载自定义字体"新蒂小丸子体",源码不公开, ...

  6. uniapp显示富文本效果demo(整理)

    uniapp显示富文本: <template><view class="rtfBox"><view class="margin-left30 ...

  7. 本节作业之显示不同问候语、显示密码、关闭二维码、循环精灵图背景、显示隐藏文本框内容、密码框格式提示错误、京东关闭广告、新浪下拉菜单、开关灯、换肤、表格隔行变色、表单取消全选、tab栏切换、发布删除留言

    本节作业之显示不同问候语.显示密码.关闭二维码.循环精灵图背景.显示隐藏文本框内容.密码框格式提示错误.京东关闭广告.新浪下拉菜单.开关灯.换肤.表格隔行变色.表单取消全选.tab栏切换.发布删除留言 ...

  8. 富文本的内容怎么转换格式

    大佬们,富文本的内容怎么转为这种格式的 现在是这种格式:<p style="text-align: center;"><img class="wscnp ...

  9. 小程序显示富文本内容格式混乱问题解决

    今天遇到一个小程序富文本显示格式混乱的问题 简单点来说就是在不该换行的地方他换行了. 开始以为是标签的问题 , 因为有些h5 标签小程序不支持 富文本内容源码: <p><span s ...

最新文章

  1. django模板的导入
  2. HTTPS_SSL配置的步骤以及原理说明
  3. 常用Sqlserver中的查询语句
  4. 区块链共识算法Proof-of-Stake (PoS/权益证明) 常见问题解答 (1)
  5. ArrayList与LinkedList、Vector的区别 HashMap与HashTable、HashSet的区别
  6. boost::geometry:::detail::overlay::get_clusters用法的测试程序
  7. 用例设计:判定表驱动法
  8. iview 远程搜索选择器方法使用,选择之后清空选择的项
  9. timeshift 安装使用说明
  10. 581. Shortest Unsorted Continuous Subarray
  11. 台式电脑主机前面耳机插孔没声音的解决方法
  12. spss数据预处理步骤_2. SPSS基本使用:数据清洗
  13. 批量查询域名是否注册
  14. 实验室主机Ubuntu远程控制+自动开关机
  15. uniapp 离线安卓本地打包(利用保利威视的打包工程打包)
  16. OpenCV学习之Canny算法自实现
  17. 公网SSH远程连接Ubuntu【免费内网穿透】
  18. 证券公司信息化1-证券行业的本质是什么?什么是资本市场?什么又是一级市场和二级市场?
  19. 《基于微信小程序的美食推荐系统》硕士论文
  20. LiveNVR安防摄像头Web无插件直播平台页面的快速集成方法

热门文章

  1. uniapp接收服务器消息,【教程】uniapp websocket实现消息推送
  2. 块元素、行内块和内联元素_如何删除内联块元素之间的空间?
  3. Java SecurityManager checkAwtEventQueueAccess()方法与示例
  4. MyBatis 的执行流程,学废了!
  5. Spring Boot(六)集成 MyBatis 操作 MySQL 8
  6. 一、详细Python3.8+PyQt5+pyqt5-tools+Pycharm配置
  7. 桂林电子科技大学计算机专业排名,桂林电子科技大学专业排名怎样
  8. springboot python 开发效率比较-2018年Java开发值得学习的10大技术
  9. mysql默认字符集和排序_MySQL字符集和排序规则
  10. foxmail怎么加入黑名单 foxmail导入黑名单邮箱地址的教程