IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 一文讲透Android Span的原理 -> 正文阅读

[移动开发]一文讲透Android Span的原理

Span 是什么

在这里插入图片描述
看上图中的 Span 是什么?如果你回答是 测试 或者 测试 及下面的下划线那就错了。我们看一下 Google 的定义

Span 是功能强大的标记对象,可用于在字符或段落级别为文本设置样式。通过将 Span 附加到文本对象,能够以各种方式更改文本,包括添加颜色、使文本可点击、缩放文本大小以及以自定义方式绘制文本。Span 还可以更改 TextPaint 属性、在 Canvas 上绘制,以及更改文本布局。

简单的说,Span 是用来处理指定范围内的文本样式的工具。如上图,Span 就为[0, 1]范围内的文本设置了下划线的样式。这里是为了告诉你 Span 不是文本,它是文本处理的工具。真正的文本是分别实现 SpannableSpanned 接口的三个类,分别是 SpannedStringSpannableStringSpannableStringBuilder。这三个实现类稍后再讲,我们先看一下 SpannableSpanned 接口。

Spannable 和 Spanned 接口

SpannableSpanned 的区别很简单,Spanned 只能获取 Span,但 Spannable 可以设置和修改 SpanSpannable 继承 Spanned ,并增加了 setSpanremoveSpan 方法来设置和修改 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 接口,可以对文本进行 replaceinsertappend等操作,所以它是可变文本。下面介绍了如何决定使用哪个类:

  • 如果不准备在创建后修改文本或标记,请使用 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 往 测试 文本的 startend 处插入字符串的代码和效果。

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

效果如图:

在这里插入图片描述

总结

这篇文章着重介绍了 SpannableSpanned 接口的方法和 flags 参数的影响;同时补充了 Span 的分类、一些常用 Span 的使用以及如何自定义 Span 等。最后求求点个免费的赞

参考

-Span guide
-Explain the definitions of 0these flags
-What is Leading Margin in Android?

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:27:25  更:2022-09-13 11:29:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:48:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码