Andoid TextView显示富文本html内容及问题处理

目录

  • 富文本内容与效果
  • 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的颜色、粗体、斜体都能显示了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558206.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java错误:找不到或无法加载主类

目录前言javac xxx.java 编译需要相对物理路径java xxx 执行需要虚拟路径总结前言 一般情况下&#xff0c;我们都使用工具进行代码的编辑和调试&#xff0c;例如eclipse 、Manven、Android Studio、sublime、vim、notepad、记事本等。 当我们用eclipse android studio等创建的p…

步苹果iOS的后尘,谷歌Android12“翻车”,更新需谨慎?

苹果不论电脑还是移动设备&#xff0c;都是一如既往的“强硬”。说实话&#xff0c;忽视“兼容”&#xff0c;体验极低。 有小伙伴调侃&#xff1a;人家就是要你买新机器。也有小伙伴&#xff08;包括我在内&#xff09;&#xff0c;直接关闭系统自动更新。 开发者&#xff1a…

Android Studio无线连接设备调试,比数据线更方便

前言 一般情况下&#xff0c;多数移动开发者使用的是数据线连接电脑&#xff0c;进行各种移动设备的调试&#xff0c;更有胜者&#xff0c;非常迷恋模拟器&#xff0c;模拟器它好不好&#xff0c;答案是好&#xff0c;因为直接运行在电脑上&#xff0c;直接操作&#xff0c;调试…

GenseeSDK 使用Kotlin要注意TODOAndroid Studio关闭TODO

目录一、Kotlin的TODO二、GenseeSDK与TODO 请注意三、Android studio关闭TODO一、Kotlin的TODO 在实现一些接口时候&#xff0c;工具自动将所有的接口函数"空"实现&#xff0c;并在函数体中增加一行代码&#xff1a; TODO或TODO(“not implemented”) 作为提醒催促…

OpenCV Mat基础认知感

OpenCV是一个开源的供开发的计算机视觉处理库&#xff0c;涵盖的内容包括图像处理&#xff0c;机器学习。由c到c &#xff0c;再到各平台的跨平台框架。 Mat - 图像容器 Mat类用于表示一个多维的单通道或者多通道的稠密数组。能够用来保存实数或复数的向量、矩阵&#xff0c;…

C++期末实践程序设计与数组作为参数的注意事项

目录小表弟发来的求助信号要点代码文件头文件Student.h源文件Student.cppmain.c执行结果c数组特性以及数组做形参的特点数组试验数组特殊性质不允许拷贝和赋值数组是通常被转化成指针使用数组形参多种写法代理模式MVC模式小表弟发来的求助信号 并补充说要5种写法才算过关。 要…

windows 7远程桌面和被远程连接电脑启动远程桌面服务

目录远程桌面连接开启远程桌面服务&#xff08;被连电脑&#xff09;配置启动远程桌面服务远程桌面连接 这部分主要讲解如何通过一台windows 电脑的远程桌面程序连接"远程"电脑桌面。前提是被连的那台电脑已开启远程桌面服务。远程桌面服务开启之后&#xff0c;可以…

表单和字都居中_APP 分享 | 6 款黑科技工具,低调收藏,每一款都很强大!

iSlide 简单、高效地制作PPT使用环境: Windows使用要求: Office 2010 及以上版本授权状况: 无广告 有付费功能官网地址: www.islide.ccSlide是一款基于PowerPoint的插件工具,功能十分强大,高效做PPT必备利器一键优化:将PPT中不规则的字体,段落,色彩,参考线…

Android studio gradle task list 不显示问题

问题描述 新电脑安装android studio后&#xff0c;导入工程&#xff0c;各种配置都弄好变更好&#xff0c;出现了如下情况&#xff1a; 之前习惯的gradle task 不在列表中&#xff0c;好不习惯。 正常期望如下&#xff1a; 处理方法 快捷键Ctrl Alt S或者 file -> se…

Android Studio Gradle输出信息乱码

在更换android studio 版本之后&#xff0c;一般windows 版本在项目构建过程中&#xff0c;gradle 相关的信息输出&#xff0c;会出现乱码&#xff0c;基本上明知是字符编码的问题&#xff0c;但看着就是不爽&#xff0c;例如下面这的情形&#xff1a; ע: ijЩ&#xfffd;&am…

Edge 修改字符编码(详细图文)

Microsoft Edge 版本 97.0.1072.62 (官方内部版本) (64 位) 前言 如下图&#xff0c;在访问页面时出现乱码&#xff0c;而且一直返回的内容编码是UTF-8&#xff0c;但Edge没找快捷的编码方式选择 方法一 Internet Explorer 模式加载 打开Edge浏览器 点击Edge右上角三点 点…

【Android】Unresolved reference: databinding

检查配置 对应module的build.gradle是否添加了databinding配置 buildFeatures {viewBinding true } // 或者 dataBinding {enabled true }检查包名(特别注意) 报错的包名是否和app包名一致。 例如app包名是 com.xxx.app 而报错的代码中binding的包名路径不在app包名下&…

【Android】ConstraintLayout中RecyclerView 的底部数据显示不全以及覆盖其他View

文章目录xml布局内容预览画面(看着没毛病)&#xff1a;实际画面&#xff1a;解决办法说明xml布局内容 <?xml version"1.0" encoding"utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android"http://schemas.android.com/a…

Charles抓取HTTPS Windows Android iOS 图文详细

文章目录背景操作原理windows 安装CharlesCharles配置第一步&#xff1a;配置HTTP代理&#xff0c;这步与抓取HTTP请求是一样第二步&#xff1a;配置SSL代理第三步 &#xff1a;为手机配置代理iPhone 代理配置Android 代理配置第四步&#xff1a;安装根证书iPhone 安装charles证…

Android代码安装apk程序

文章目录一、高低版本兼容代码及配置1.java代码2.AndroidManifest.xml 配置ContentProvider3.paths xml 配置二、老版本代码三、可能出的问题及处理一、高低版本兼容代码及配置 高版本提高了sdcard、 app文件空间的访问权限&#xff0c;高低版本的系统api有一定区别&#xff0…

Android adb 安装apk程序

文章目录ADB简单介绍安装命令安装失败情况adb 安装测试包adb 安装卸载的所有命令摘要前言多数情况下&#xff0c;用户都是从应用商店或某网页下载apk后从手机上直接安装apk文件&#xff0c;在调试或下载到电脑上时候&#xff0c;我们就可以直接通过adb安装到手机。在测试小伙伴…

XML十六进制无效的字符(0x0b 0x0c 0x02)

文章目录解决方法ASCII“字符”1、ASCII 打印字符2、ASCII 非打印控制字符3、扩展 ASCII 打印字符4、ASCII 之外ASCII 字符输入方法与常用地方1、在文档中插入 ASCII 字符2、输入方法前言xml 中包含了非打印字符的情况下&#xff0c;即使是放在cdata 中&#xff0c;在不同平台上…

Android OpenGLES2.0(一)OpenGLES2.0基础介绍

文章目录什么是OpenGL ES&#xff1f;OpenGL ES可以做什么&#xff1f;OpenGL ES版本及Android支持情况OpenGL ES 2.0的优点OpenGL ES 2.0中基本概念顶点着色器片元着色器着色器语言坐标系图形的绘制投影光照纹理映射其它OpenGL ES 2.0过程及理解什么是OpenGL ES&#xff1f; O…

Windows ‘ls‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件

文章目录前言原因解决办法前言 Windows和mac混用或Windows与linux混用的情况下&#xff0c;在windows dos窗口随手一个ls&#xff0c;出现了 ‘ls’ 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 原因 ls是linux的命令&#xff0c;不是windows的命令,win…

Linux ls 命令使用介绍

文章目录命令格式命令功能常用参数常用示例示例1 列出文件夹下的所有文件和目录的详细资料示例2 列出当前目录中所有以“t”开头的目录的详细内容&#xff0c;示例3 只列出文件下的子目录命令&#xff1a;示例4 列出目前工作目录下所有名称是以s开头的档案&#xff0c;越新的排…