用TextView实现富文本展示,点击断句和语音播报

前言

最近有一个需求:移动端需要展示用户在PC端做的笔记,而笔记内容是富文本形式——有图片,有文字,文字可以设置颜色、加粗、倾斜等等。同时,用户点击的时候能够语音朗读所点击的当前整句的内容。

第一反应就是富文本!PC端生成的就是html文件,创给我,直接用WebView展示不就ok了嘛!

但是,还有一需求:点击断句——我们需要判断用户的点击,定位到所点击的整句话,然后再将整句内容实现语音播报。

这样的话WebView似乎就不满足要求了,所以最终决定使用TextView来实现。

github地址 https://github.com/shuaijia/RichTextView 欢迎star

先看效果

静态展示:

点击断句

语音合成播报 这个就不展示了,大家可以下载实例代码运行体验。

特别地:我还实现了断点语音播报和循环播报。

技术点

在实现上述需要求,我们需要以下技术点为基础:

Html.fromHtml()

fromHtml重载两个方法,分别是:

1、Spanned android.text.Html.fromHtml(String source) //输入的参数为(html格式的文本)

目前android不支持全部的html的标签,目前只支持与文本显示和段落等标签,对于图片和其他的多媒体,还有一些自定义标签不能识别

例子:

2 、Spanned android.text.Html.fromHtml(String source, ImageGetter imageGetter, TagHandler tagHandler)

  • source: 需处理的html文本
  • imageGetter :对图片处理(处理html中的图片标签)
  • tagHandler :对标签进行处理(相当于自定义的标签处理,在这里面可以处理自定义的标签)

也就是说,我们完全可以使用Html.fromHtml方法,传入html代码,最后返回Spanned 对象,在使用setText方法既可实现用TextView展示html类型的富文本。

图片处理

上一部分也说了,使用Html.fromHtml( )方法展示富文本的时候,某些自定义的标签和图片识别不了,也就是加载不出来。而我们的项目中没有自定义的特殊标签,最关键的就是图片的加载!

翻过头我们再看下fromHtml的三个参数的方法:

  • source: 需处理的html文本
  • imageGetter :对图片处理(处理html中的图片标签)
  • tagHandler :对标签进行处理(相当于自定义的标签处理,在这里面可以处理自定义的标签)

source是html文本这个不用说了,第二个参数imageGetter 负责图片的加载,tagHandler 是在加载时获取各标签。

想到这里,图片加载使用自定义ImageGetter就可以了啊,于是乎:

1、 创建图片请求工具方法:

html标签中的图片全是在img标签中,而且都是图片链接,所以简单写一方法来实现加载网络图片:

我这里简单使用HttpUrlConnection来实现加载网络图片,大家可以根据自己项目换成Glide等框架。

2、自定义ImageLoader:

getDrawable方法中的参数source通过打log看出就是在加载html文本时,需要加载的网络图片的地址url;

那似乎很简单啊,加载网络图片返回(需要注意的是:加载到的是Bitmap对象,需要转成Drawable对象再返回;再者就是需要考虑子线程去加载,我这里只是简单展示原理,没有开启子线程加载图片)。

然后创建NetWorkImageGetter 对象,在fromHtml时传入既可。

但是!

3、存在的问题及优化

这样存在一个问题,我们使用fromHtml加载html文本时,图片是同步加载,而加载网络图片和加载html是异步的,也就是说:在加载到图片之前,其他文本已经显示到界面上,所以需要我们再次设置html文本。

那我们考虑下,是不是每加载完一张图片就刷新一下呢?这样会导致界面刷新好多次,用户可能刚滑到底部查看内容,这时加载到第一张图片,界面就会立马刷新到最上方,这样的用户体验会不会很不好~

所以,我的思路是当所有图片全部加载完成后,再刷新界面,也就是重新setText

但我怎么会知道什么时候就全部加载完图片了呢?或者说我怎么能够知道一共需要加载多少张图片呢?

此时就用到了第三个参数:TagHandler

先了解下TagHandler

结果呢:

突然发现,s变量就是html文本中的各个标签。同时我们也发现,每次都是先加载图片,然后才弹回img的tag。

这样就好办了,

在TagHandler中计算img标签的个数,在ImageGetter中等加载图片个数全部完成时,再次刷新界面(重新调用setText方法)。

在全部图片加载完成后在刷新textview内容(这里的setText是稍后会讲到的封装的设置html代码,大家可简单的理解成setText(Html.fromHtml(… )))。

点击断句

这里就用到了SpannableStringBuilder

我的思路是这样的:

  1. 从TextView获取展示的内容。我们认为! 。 ? @ … ···等符号是一句话结束的标志,所以通过它们将完整语句分割,存入数组;
  2. 创建一int类型数组,存放每句话在全文中开始的位置;
  3. 使用循环将每一句都设置对应的点击;
  4. 注意setMovementMethod(LinkMovementMethod.getInstance());必须设置,否则无效果。

看下TextViewURLSpan代码:

我们将每句对应数组中的下标传入,方便语音合成时从数组中获取文本内容。

因为循环播放是使用handler发消息进行通知的,所以重新开始播放时,先移出之前的消息。

语音合成

语音合成就不再啰嗦了,不清楚的查看讯飞开发文档就ok了,挺简单的。

因为需求要求是点击每句要变颜色,所以进行了一次循环,给每句话都设置了ForegroundColorSpan,给文字更改颜色。

这样就结束了哦!

原文发布于微信公众号 - Android机动车(JsAndroidClub)

原文发表时间:2017-10-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏企鹅号快讯

坚定地使用 CSS Custom Properties

好久没译文,最近看到这篇 Getting Hardboiled with CSS Custom Properties。觉得不错,翻译出来给大家。现在 CSS 新...

1937
来自专栏用户2442861的专栏

Markdown,你只需要掌握这几个

http://www.cnblogs.com/crazyant007/p/4220066.html

1311
来自专栏更流畅、简洁的软件开发方式

从吉日嘎拉那里学到的……

多个列表框联动,不算是啥大问题,但是却挺麻烦,那么怎么才能够尽量方便一点呢?网上搜了一下,没发现太好用的,于是就自己写了一个。基于jQuery,无限级联动,支持...

2196
来自专栏iKcamp

追溯 React Hot Loader 的实现

文:萝卜(沪江金融前端开发工程师) 本文原创,转载请注明作者及出处 如果你使用 React ,你可以在各个工程里面看到 Dan Abramov 的身影。他于...

47414
来自专栏张善友的专栏

初探ReactJS.NET 开发

ReactJS通常也被称为"React",是一个刚刚在这场游戏中登场的新手。它由Facebook创建,并在2013年首次发布。Facebook认为React在处...

2285
来自专栏老付的网络博客

Vuejs template快速入门

一直想写一个Vuejs的系列的博客,苦于一直没有时间,今天的这个博客也是思考了很久,打算随便写点东西,今天说说Vuejs的模版的使用。

9942
来自专栏CRPER折腾记

Vue 折腾记 - (13) Nuxt.js写一个常规音频的播放组件,动态注入微信,新浪微博的js-sdk

全部耦合到组件内,虽然可以正常播放(包括微信和微博) 且不是单例模式,对于多音频页面,有毒

5292
来自专栏hightopo

原 Web SCADA 电力接线图工控组态

3296
来自专栏更流畅、简洁的软件开发方式

【分享】纯js的n级联动列表框 —— 基于jQuery,支持下拉列表框和列表框,最重要的是n级,当然还有更重要的

多个列表框联动,不算是啥大问题,但是却挺麻烦,那么怎么才能够尽量方便一点呢?网上搜了一下,没发现太好用的,于是就自己写了一个。基于jQuery,无限级联动,支持...

4138
来自专栏hightopo

原 基于 HTML5 WebGL 的 3D

2916

扫码关注云+社区

领取腾讯云代金券