我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
Span 是什么
看上图中的 Span 是什么?假如你回答是测验或许测验及下面的下划线那就错了。咱们看一下 Google 的界说
Span 是功能强大的符号目标,可用于在字符或阶段等级为文本设置款式。经过将 Span 附加到文本目标,能够以各种办法更改文本,包含添加色彩、使文本可点击、缩放文本巨细以及以自界说办法制作文本。Span 还能够更改 TextPaint 特点、在 Canvas 上制作,以及更改文本布局。
简略的说,Span 是用来处理指定范围内的文本款式的东西。如上图,Span 就为[0, 1]范围内的文本设置了下划线的款式。这儿是为了告知你 Span 不是文本,它是文本处理的东西。真实的文本是分别完成Spannable
和Spanned
接口的三个类,分别是SpannedString
、SpannableString
、SpannableStringBuilder
。这三个完成类稍后再讲,咱们先看一下Spannable
和Spanned
接口。
Spannable 和 Spanned 接口
Spannable
和Spanned
的差异很简略,Spanned
只能获取Span,但Spannable
能够设置和修正Span
。Spannable
承继Spanned
,并添加了setSpan
、removeSpan
办法来设置和修正Span。咱们先来看Spannable
接口界说的办法。
Spannable 接口办法
public void setSpan(Object what, int start, int end, int flags);
setSpan
办法为文本设置 Span ,参数作用如下:
- what : 为文本设置的 Span
- start : 设置 Span 的开端的方位
- end : 设置 Span 的完毕的方位,end 参数值是不被包含的。例如,要设置上图的下滑线作用,end 值要设置为2,而不是1。
- flags : Span 的标志位
这儿start能够等于end吗?答案是SpannedString
SpannableString
能够;而SpannableStringBuilder
在 flags 为 SPAN_EXCLUSIVE_EXCLUSIVE 时不可。flags 的作用到底是什么?这儿先不讲,等后面详细介绍 flags 时,你就知道了。
还有个点需求留意,setSpan 内部会判别 what 目标是否之前设置过了,假如是同一个目标,会修正它的方位和flag值。示例代码如下:
UnderlineSpan underlineSpan = new UnderlineSpan();
spannableString.setSpan(underlineSpan, 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
//修正 Span 的方位
spannableString.setSpan(underlineSpan, 2, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
//由于只判别目标是否相同,导致了添加了2个完全相同的 Span
spannableString.setSpan(new UnderlineSpan(), 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new UnderlineSpan(), 0, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);
public void removeSpan(Object what)
办法用来删去指定的 Span 。这个很简略,就不多介绍了。
Spanned 接口办法
public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind)
getSpans
办法获取指定“范围”的指定 Class 的 Span 。假如你想要获取一切的 Span ,你能够运用 Object.class。这儿的范围要特别留意,一般咱们说的范围是指start < pos < end
的部分,如下图。
但是,getSpan
办法不光会获取start < pos < end
的部分,还会获取部分方位在范围内的 Span 。如下图所示:
为什么 Android 要运用这种规矩来获取 Span 呢?
答案是让一个SpanWatcher能监听到多个 Span。SpanWatcher是监听 Span (add、remove、set操作时)变化的 Span。它的原理很简略,便是当 Span 变化时,会调用getSpan
获取范围内的 SpanWatcher,然后调用相应的办法。看下图,采用当时的规矩下,SpanWatcher能监听到 Span1、Span2、Span3;SpanWatcher1能监听 Span1、Span2;SpanWatcher2能监听 Span2。假如采用start < pos < end
的部分的规矩,就无法经过一个 SpanWatcher 监听多个 Span 了。
public int nextSpanTransition(int start, int limit, Class kind)
回来一个类型的 Span 开端或完毕的第一个大于start的偏移,假如没有大于start但小于limit的开端或完毕,则回来limit。看文字有点难理解,直接上图。
除了图上的两种状况外,nextSpanTransition
办法只会回来传入的 limit 的值。你没看错,即使 SpanStart == start 时也是回来 limit 的值。
其他 Spanned 界说的办法的介绍如下:
-
public int getSpanStart(Object what)
: 获取指定 Span 的 start 的值。没找到回来 -1 -
public int getSpanEnd(Object what)
: 获取指定 Span 的 end 的值。没找到回来 -1 -
public int getSpanFlags(Object tag)
: 获取 Span 的 flag。没有则回来 0
SpannedString 、SpannableString 和 SpannableStringBuilder
对于这三个类,直接看下面官方文档的比照:
类 | 可变文本 | 可变 Span | 数据结构 |
---|---|---|---|
SpannedString | 否 | 否 | 线性数组 |
SpannableString | 否 | 是 | 线性数组 |
SpannableStringBuilder | 是 | 是 | 区间树 |
这儿弥补一下,SpannableStringBuilder
完成了Editable
接口,能够对文本进行replace
、insert
、append
等操作,所以它是可变文本。下面介绍了怎么决议运用哪个类:
- 假如不准备在创立后修正文本或符号,请运用 SpannedString。
- 假如需求将少量 Span 附加到单个文本目标,并且文本自身为只读,请运用 SpannableString。
- 假如需求在创立后修正文本,并且需求将 Span 附加到文本,请运用 SpannableStringBuilder。
- 假如需求将很多 Span 附加到文本目标,那么不管文本自身是否为只读,都请运用 SpannableStringBuilder。这儿是由于 SpannableStringBuilder 运用了区间树来完成提高了功能。
flags
SPAN_MARK_MARK、SPAN_MARK_POINT、SPAN_POINT_MARK 和 SPAN_POINT_POINT
当咱们在 Span边界内刺进文本,Span 会主动扩展以包含刺进的文本。在 Span边界上(即在 start 或 end 索引处)刺进文本时,这四个 flags 参数便是用于确定 Span 是否应扩展以包含刺进的文本。下面是运用四个不同 flags 往测验文本的start和end处刺进字符串的代码和作用。
String source = "测验";
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(source);
SpannableStringBuilder spannableStringBuilder1 = new SpannableStringBuilder(source);
SpannableStringBuilder spannableStringBuilder2 = new SpannableStringBuilder(source);
SpannableStringBuilder spannableStringBuilder3 = new SpannableStringBuilder(source);
spannableStringBuilder.setSpan(new BackgroundColorSpan(Color.parseColor("#6aFFFF00")), 0, 2, Spanned.SPAN_MARK_POINT);
spannableStringBuilder1.setSpan(new BackgroundColorSpan(Color.parseColor("#6aFFFF00")), 0, 2, Spanned.SPAN_MARK_MARK);
spannableStringBuilder2.setSpan(new BackgroundColorSpan(Color.parseColor("#6aFFFF00")), 0, 2, Spanned.SPAN_POINT_MARK);
spannableStringBuilder3.setSpan(new BackgroundColorSpan(Color.parseColor("#6aFFFF00")), 0, 2, Spanned.SPAN_POINT_POINT);
spannableStringBuilder.insert(0, "刺进数据1");
spannableStringBuilder.insert(spannableStringBuilder.length(), "刺进数据2");
spannableStringBuilder1.insert(0, "刺进数据1");
spannableStringBuilder1.insert(spannableStringBuilder1.length(), "刺进数据2");
spannableStringBuilder2.insert(0, "刺进数据1");
spannableStringBuilder2.insert(spannableStringBuilder2.length(), "刺进数据2");
spannableStringBuilder3.insert(0, "刺进数据1");
spannableStringBuilder3.insert(spannableStringBuilder3.length(), "刺进数据2");
mBinding.testRight1.setText(spannableStringBuilder);
mBinding.testRight2.setText(spannableStringBuilder1);
mBinding.testRight3.setText(spannableStringBuilder2);
mBinding.testRight4.setText(spannableStringBuilder3);
看上去是很难记,其实你只需了解 Mark 和 Point 在 Android中表明什么就行了。如下图所示,其实十分简略,Mark 表明在字符左边方位,Point 表明字符右边的方位,光标在 Mark 和 Ponit 中间。与上图的成果对照,是不是豁然开朗。
假如你真实记不住,Android 也提供了替代品:SPAN_INCLUSIVE_INCLUSIVE 、SPAN_INCLUSIVE_EXCLUSIVE SPAN_EXCLUSIVE_EXCLUSIVE 、SPAN_EXCLUSIVE_INCLUSIVE。其中 INCLUSIVE 表明包含,EXCLUSIVE 表明不包含,这个是不是好记多了。
其他 flags
其他 flags 的官方描述不怎么清晰,并且网上的信息也比较少。下面的内容是我依据源码和一些介绍得出的定论,不一样准确。
- SPAN_INTERMEDIATE
带有 SPAN_INTERMEDIATE 的 Span 被移除时不会调用 SpanWatcher 的onSpanRemoved
办法。首要运用于文字的挑选区域的开端方位,猜想是用来标志挑选区域的,如下图所示。
- SPAN_PARAGRAPH
运用 SPAN_PARAGRAPH 的 Span 有必要运用于整个文本或许文本中的一个阶段。什么是阶段呢?在 Android 中,阶段结尾处具有一个换行 (‘\n’) 符,如下图所示。
运用 SPAN_PARAGRAPH 的代码示例如下
String source = "哈\n哈哈\n哈哈哈哈哈哈";
SpannableStringBuilder spannableStringBuilder1 = new SpannableStringBuilder(source);
SpannableStringBuilder spannableStringBuilder2 = new SpannableStringBuilder(source);
SpannableStringBuilder spannableStringBuilder3 = new SpannableStringBuilder(source);
SpannableStringBuilder spannableStringBuilder4 = new SpannableStringBuilder(source);
spannableStringBuilder1.setSpan(new UnderlineSpan(), 0, spannableStringBuilder1.length(), Spanned.SPAN_PARAGRAPH);
mBinding.testRight1.setText(spannableStringBuilder1);
spannableStringBuilder2.setSpan(new UnderlineSpan(), 0, 2, Spanned.SPAN_PARAGRAPH);
mBinding.testRight2.setText(spannableStringBuilder2);
spannableStringBuilder3.setSpan(new UnderlineSpan(), 2, 5, Spanned.SPAN_PARAGRAPH);
mBinding.testRight3.setText(spannableStringBuilder3);
spannableStringBuilder4.setSpan(new UnderlineSpan(), 5, spannableStringBuilder4.length(), Spanned.SPAN_PARAGRAPH);
mBinding.testRight4.setText(spannableStringBuilder4);
作用如下图:
那这个 flag 是用来做什么的呢?当替换文本时,假如文本中的 Span 满意带有 SPAN_PARAGRAPH ,同时不在要求范围内,就扔掉这个 Span 。
如上图,假如 Span1 带有 SPAN_PARAGRAPH,Span2 没有;则 Span2 会运用在替换的文本上,Span1 不会。
SpannableStringBuilder spannableStringBuilder1 = new SpannableStringBuilder("测验内容");
spannableStringBuilder1.setSpan(new BackgroundColorSpan(Color.parseColor("#6aFFFF00")), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
SpannableStringBuilder spannableStringBuilder2 = new SpannableStringBuilder("\n哈哈\n哈哈哈哈哈哈");
spannableStringBuilder2.setSpan(new UnderlineSpan(), 0, 4, Spanned.SPAN_PARAGRAPH);
spannableStringBuilder2.setSpan(new RelativeSizeSpan(1.5f), 0, 4, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableStringBuilder1.replace(0, 2, spannableStringBuilder2, 0, 3);
mBinding.testRight1.setText(spannableStringBuilder1);
作用如上图,UnderlineSpan 由于设置了 SPAN_PARAGRAPH 被删去了。没有设置的 RelativeSizeSpan 则保存下来了。猜想这个作用应该是避免不同阶段的 Span 被错误运用到新文本上。
- SPAN_PRIORITY
SPAN_PRIORITY指的是用于更新意图的文本布局的优先级;它只应在特别状况下设置,因而没有必要由开发者设置。
- SPAN_USER 和 SPAN_USER_SHIFT
SPAN_USER 和 SPAN_USER_SHIFT 是额外的自界说标量数据的存储区域,假如开发人员挑选运用它们,它们将与 Span 一起存储。
- SPAN_COMPOSING
被 IME(输入法)运用,详细作用不知道。
Span
上面的内容总算把 Span相关的文本讲完了,现在才是真实的介绍 Span 了。
在 Android 的官方文档中,把 Span 分为四种,分别是:
影响文本外观的 Span
影响文本外观的 Span:影响文本外观,例如更改文本或布景色彩以及添加下划线或删去线。这些 Span 会完成 UpdateAppearance 并扩展 CharacterStyle。如下图,为文本添加下划线。
代码示例如下:
SpannableString spannableString = new SpannableString("测验文本");
spannableString.setSpan(new UnderlineSpan(), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
影响文本目标的 Span
影响文本目标的 Span:影响文本目标,例如行高和文本巨细。一切这些 Span 都会扩展 MetricAffectingSpan 类。如下图,将文本巨细添加 50%
代码示例如下:
SpannableString string = new SpannableString("测验文本");
string.setSpan(new RelativeSizeSpan(1.5f), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
test.setText(string);
影响单个字符的 Span
影响单个字符的 Span:影响字符等级的文本。例如,您能够更新布景色彩、款式或巨细等字符元素。影响单个字符的 Span 会扩展 CharacterStyle 类。如下图,为文本添加布景色彩。
代码示例如下:
SpannableString string = new SpannableString("测验文本");
string.setSpan(new BackgroundColorSpan(Color.YELLOW), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
test.setText(string);
影响阶段的 Span
影响阶段的 Span:影响阶段等级的文本,例如更改整个文本块的对齐办法或边距。影响整个阶段的 Span 会完成 ParagraphStyle。
阶段 Span 只会影响\n
左右的款式,不会包含换行符。但是其他类型的 Span 会包含。
假如你尝试将阶段 Span 运用于除整个阶段以外的其他内容,Android 底子不会运用该 Span。
Android 提供了五种接口,它们都完成了 ParagraphStyle 接口。
- LeadingMarginSpan:处理阶段的首行间距和其他行间距
- AlignmentSpan:处理整个阶段对其办法;
- LineBackgroundSpan:处理一行的布景;
- LineHeightSpan:处理一行的高度;
- TabStopSpan:将字符串中的”\t”替换成相应的空行;
下面是运用LeadingMarginSpan
的运用示例。图片和代码来源What is Leading Margin in Android?
LeadingMarginSpan span = new LeadingMarginSpan.Standard(20, 100); // left example
LeadingMarginSpan span = new LeadingMarginSpan.Standard(100, 0); // right exmaple
自界说 Span
文本相关的自界说 Span 十分简略,它是经过修正 TextPain 的特点来完成自界说作用的。下面是完成可用于修正文本巨细和色彩的自界说 Span的官方示例。
public class RelativeSizeColorSpan extends RelativeSizeSpan {
private int color;
public RelativeSizeColorSpan(float spanSize, int spanColor) {
super(spanSize);
color = spanColor;
}
@Override
public void updateDrawState(TextPaint textPaint) {
super.updateDrawState(textPaint);
textPaint.setColor(color);
}
}
阶段相关的 Span 与文本相关的 Span 不同,无法归纳。这儿以文本环绕为例,代码如下。原理很简略,便是经过 图片的高 / 行高 获取需求设置的行数,并对指定行回来 图片宽度 + padding 的值就行了。
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(source);
spannableStringBuilder.setSpan(new LeadingMarginSpan.LeadingMarginSpan2() {
@Override
public int getLeadingMarginLineCount() {
//获取需求设置margin的行数
int count = mImageView.getHeight() / mTextView.getLineHeight();
return count;
}
@Override
public int getLeadingMargin(boolean first) {
if (first) {
//回来 margin 的值
return mImageView.getWidth() + 20;
} else {
return 0;
}
}
@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top,
int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
}
}, 0, spannableStringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mTextView.setText(spannableStringBuilder);
作用如图:
总结
这篇文章着重介绍了Spannable
和Spanned
接口的办法和 flags 参数的影响;同时弥补了 Span 的分类、一些常用 Span 的运用以及怎么自界说 Span 等。
最后,求求点个免费的赞吧。
参阅
- Span guide
- Explain the definitions of 0these flags
- What is Leading Margin in Android?