点击上方蓝字关注我,知识会给你力量

Span是Android文本系统中一个非常重要的功能,对于它的一般使用,其实比较简单,但在处理一些复杂业务时,Span的边界问题处理就显得非常重要了,不然很容易因为边界情况没有处理好,导致一系列很麻烦的bug。

setSpan

with(binding) {val text = "我真的是被Span搞裂开了"SpannableString(text).also {it.setSpan(StyleSpan(Typeface.BOLD_ITALIC), 0, text.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE)this.textview.text = it}
}

注意这里的range,start…end,end是text.length,正好将所有文字Span化,如果start…end超过0…text.length的区间,那么就会产生IndexOutOfBoundsException,由此可知,setSpan中的range,是一个左闭右开区间。

[ start … end ) —— [ 0 … length )

getSpans

with(binding.textview) {val spannableString = SpannableString(text)val spans = spannableString.getSpans(0, length(), StyleSpan::class.java)spans.forEach { span ->val start = spannableString.getSpanStart(span)val end = spannableString.getSpanEnd(span)Log.d("xys", "getSpans: Start: $start , End: $end")}
}

与setSpan类似,我们通过getSpans来找到range里面的所有指定类型span,那么这里的start…end呢,我们先试下0…length,0…length - 1,0…length + 1,-1…length,-1…length + 1,length - 1…length + 1,-1…1这几种情况。

不出意外,这几种都可以获取出正确的Span。

再来看看length…length + 1,-1…0这两种情况。

出意外了,这时候就获取不到了。

总结一下,来张图就看清楚了。

image-20211202110043219

红色的范围是不可获取,灰色的范围是可以获取,由此可见,getSpans比setSpan的range要复杂多了。

总结一下,对于一个Span,范围是0…Length-1,那么getSpans的range,start…end能获取到Span的条件是,start…end完全落在0…Length-1的左开右闭区间里。

最常用的方式,实际上就是:

getSpans(length() - 1, length(), StyleSpan::class.java)

Span原理分析

我们借助SpannableStringInternal来分析Span具体是如何作用到Text上的。

要想把Span附加到Text上,那么肯定是对Text做了标记,在渲染时,根据标记来做特殊的渲染。

123davdzz

这是Spannable相关的类继承关系。

  • 对于SpannedString、SpannableString来说,它们是继承的SpannableStringInternal。

  • Span是否是可变,是通过Spanned(Span不能增删)和Spannable(Span可以增删)接口来区分的。

所以核心逻辑都在SpannableStringInternal中,在它的源码中,有几个重要的成员变量:

  • mSpans:用来保存具体的Span对象

  • mSpanData:用来保存每个Span的数据,start、end、flag

在mSpanData中,每个Span需要三个元素来控制,所以,mSpanData的长度是3的倍数,每3个元素代表一个Span,从下面这张图就能看的很清楚了。

img

下面继续来看SpannableStringInternal的构造函数。

SpannableStringInternal的构造函数,就是为了初始化上面的成员变量,它有两个来源,一个本身就是SpannableStringInternal,那么直接继承它内部的这些变量即可,另一个是其它类型,就需要重新创建。

private static final int START = 0;
private static final int END = 1;
private static final int FLAGS = 2;
private static final int COLUMNS = 3;int start = mSpanData[i * COLUMNS + START];
int end = mSpanData[i * COLUMNS + END];
int flag = mSpanData[i * COLUMNS + FLAGS];

在了解了Text如何保存Span及其数据后,我们来看下getSpans为什么会有上面那么奇葩的设计。

原因就在getSpans代码中的check逻辑。

public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {int count = 0;int spanCount = mSpanCount;Object[] spans = mSpans;int[] data = mSpanData;Object[] ret = null;Object ret1 = null;for (int i = 0; i < spanCount; i++) {int spanStart = data[i * COLUMNS + START];int spanEnd = data[i * COLUMNS + END];if (spanStart > queryEnd) {continue;}if (spanEnd < queryStart) {continue;}if (spanStart != spanEnd && queryStart != queryEnd) {if (spanStart == queryEnd) {continue;}if (spanEnd == queryStart) {continue;}}

就是这里的一堆判断逻辑,导致了前面略显奇葩的结果。

看到这里,应该就能明白了,我们传入的range(queryStart…queryEnd)和(spanStart…spanEnd)之间究竟是怎么比较的。

要通过check,必须依次保证下面的条件(以-1…0为例):

  • End >= SpanStart   0 >= 0  true

  • Start <= SpanEnd   -1 <= 13  true

  • SpanStart != SpanEnd && Start != End   true

  • End != SpanStart  0 != 0    false

  • SpanEnd != Start   13 != -1  true

由此可见,这些条件check的实际上是query的End和SpanStart,以及query的Start和SpanEnd之间的关系。

向大家推荐下我的网站 https://xuyisheng.top/  点击原文一键直达

专注 Android-Kotlin-Flutter 欢迎大家访问

往期推荐

  • 起点客户端精准化测试的演进之路

  • 再谈协程之suspend到底挂起了啥

  • 再谈协程之CoroutineContext我能玩一年

  • LiveData beyond the ViewModel

本文原创公众号:群英传,授权转载请联系微信(Tomcat_xu),授权后,请在原创发表24小时后转载。

< END >

作者:徐宜生

更文不易,点个“三连”支持一下

真·富文本编辑器的演进之路-富文本Span的边界探究相关推荐

  1. 真·富文本编辑器的演进之路

    Span的基础性概念分析 国际惯例,官网镇楼,这是对Span最好的全局概览. https://developer.android.com/guide/topics/text/spans Span种类 ...

  2. 真·富文本编辑器的演进之路-Span开胃菜

    Span的基础性概念分析 国际惯例,官网镇楼,这是对Span最好的全局概览. https://developer.android.com/guide/topics/text/spans Span种类 ...

  3. python文本编辑器_python最好的ide和文本编辑器

    python文本编辑器 I cannot stress enough how important the right IDE (Integrated Development Environment) ...

  4. 在mac上用文本编辑器写python_五大文本编辑器。Mac OS X最佳文本编辑器应用

    五大适用于Mac修订的文本编辑器 文本编辑是开发人员生活中非常重要的一部分.这对于习惯于面面俱到的环境的Mac开发人员而言更加明显.对于他们来说存在一系列不同的优质软件之选.以下我们为其介绍了Mac版 ...

  5. 【富文本编辑器功能】vue实现富文本编辑器Tinymce功能,保留编辑器格式文章展示在页面上【前后端代码展示,简单好用】

    前言: 这个Tinymce富文本编辑器是vue-element-admin内集成好的,使用过后体验非常不错,很简单易用.这里分享一下,同时又看到了网上帖子都没什么人写前后端同时展示的,很多人想知道编辑 ...

  6. layui富文本编辑器上传图片java_解决layui富文本编辑器图片上传无法回显的问题...

    layui富文本编辑器用起来挺方便的,但是不足的是不提供图片上传的接口,需要自己写上传接口,而且返回的数据类型要符合layui富文本编辑器图片上传插件的要求,否则图片可以上传成功,但是无法回显,这个问 ...

  7. 富文本编辑器中空格转化为a_文本编辑器题解

    文本编辑器-题解 内存限制: 256 Mb时间限制: 1000 ms 题目背景 文本编辑器是一种常见的软件,开发高效的文本编辑器是一件相当困难的事情.解决本题需求的数据结构被称为 Gap buffer ...

  8. django html文本编辑器,django xadmin 集成DjangoUeditor富文本编辑器

    #### 本文档记录自己的学习历程! #### 介绍 - Ueditor HTML编辑器是百度开源的在线HTML编辑器,功能非常强大 #### 额外功能 - 解决图片视频等无法上传显示问题 #### ...

  9. c语言实现英文文本编辑器_用flutter实现富文本编辑器(二)

    上一回咱们说到RichText是如何实现TextSpan和WidgetSpan混排的,这次我们把RichText和TextField合并起来 这是我目前修改的文件,把rich前缀去掉就是原来的名字 . ...

最新文章

  1. Ubuntu解压缩zip,tar,tar.gz,tar.bz2
  2. Beyond Compare进行内容替换的方法有哪些
  3. Sa身份登陆SQL SERVER失败的解决方案
  4. [YTU]_2633( P3 数钱是件愉快的事)
  5. FFmpeg入门之常用命令
  6. oracle报V27的错误解决办法,oracle11g ora-27154 past/wait 错误解决方法
  7. 线程的生命周期及五种基本状态介绍
  8. java学习(65):类访问static修饰的内部类
  9. 配置openldap_openldap环境搭建+集成JIRA服务
  10. About 3GPP
  11. Spring Cloud Config服务端配置细节(二)之加密解密
  12. [转]python3_unboundlocalerror报错原因
  13. Android ListView中EditView再次焦点获取
  14. C++学习之-std::make_unique 与std::unique_ptr
  15. Gimp去除图片背景色方法
  16. 知道这些网站,能让你的工作效率提高2000%!
  17. 微信企业号加密异常处理:InvalidKeyException
  18. 自动化测试的定位以及一些思考是什么样的,你知道吗?
  19. 拼多多2018校招—Anniversary
  20. 博士生成长需要经历的7道门

热门文章

  1. hp linux 禁用u盘启动项,BIOS关闭Secure Boot(安全启动)方法大全(联想,华硕,DELL,HP等品牌)...
  2. excel自定义格式分钟计时_巧用EXCEL制作计时器
  3. 下载Excel文件功能通过火狐浏览器下载没有后缀名
  4. 3、太阳能电池板参数解析
  5. 原子物理/狭义相对论初步
  6. ElementUI日期组件(DatePicker )图标定制
  7. 肠胃一直不好,该如何进行调理?
  8. 大数据分析和大数据开发哪个好就业啊?
  9. html 图片自动滚动播放,CSS3如何实现图片滚动播放效果(附代码)_WEB前端开发...
  10. 零基础如何快速入门微信小游戏开发?