6

真·富文本编辑器的演进之路-Span的整体性控制

 3 years ago
source link: https://xuyisheng.top/richtext_integrated/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

时隔多日,终于又更新了。

在了解了Span的基本知识后,我们先来处理下「Span的整体性控制」,怎么理解呢?我们在编辑富文本的时候,经常会遇到一些整体内容的输入,例如「@用户」、输入话题「#什么话题#」、跳转链接「URL」,这些Span区别于普通文字,输入时是整体输入,删除时,也是整体删除,而知中间也不能插入文字或者修改,这就是「Span的整体性控制」。

所以,我们需要对Span做下面的限制:

  • 中间不允许光标插入
  • 增加时整体新增
  • 删除时整体删除

对应这样的需求,我们有两种方式来处理,第一种是使用原本就是整体的Span,例如ImageSpan,这是最简单的方法,而且代码也非常简单,另一种是通过代码处理,让普通文本来实现整体性的功能。

通过ImageSpan保证完整性

将Span内容生成ImageSpan,从而实现整体性控制。这种方案简单易行,我们以新增「@用户」为例。

  1. 首先,创建一个ATSpan,继承自ImageSpan,附带@的数据信息
  2. 解析要添加的富文本数据,将要展示的内容,例如「@xuyisheng」,作为文本,创建一个TextView来承载
  3. 将生成的TextView转化为Drawable,设置给ATSpan,并传入@的相关数据信息
  4. 将ImageSpan插入Edittext,实现整体性Span的富文本插入

可以发现,这种方案的实现步骤是比较简单的,但是它的确定也很明显:

首先,由于是ImageSpan,所以在与普通文本的对齐方式上,始终会存在一些误差,这些误差有来自TextView-Drawable的转换过程,也有ImageSpan的对齐过程,所以,在样式上,对齐会有一些问题,同时,由于TextView-Drawable的整体性,一旦TextView有多行或者当前行剩余位置不够,那么第二行的剩余区域都将被View的矩形区域填满,从而导致这些区域无法再输入文本,如下所示。

image-20210819162910988

这是由于View的图形限制导致的问题,使用ImageSpan的话,是无法解决的问题,由此可见,ImageSpan虽然天生具有整体性,但是却只是一个妥协的方案,不能算是最好的实现方式。

通过SpanWatcher控制

第二种方案,我们使用普通文本,但是对普通文本增加Span标记,并对这个Span做整体性控制,这种方案复杂一点,要处理的地方也比较多,但是由于它使用的是普通文本,所以在样式上可以和其它普通文本完全保持一致,视觉样式应该是最好的。

首先,我们来实现普通文本的变色功能,做一个蓝色的字色,这个比较简单,可以使用ClickableSpan或者其它Span来着色,为了方便我们富文本的输入和展示,这里直接选择ClickableSpan来实现。

在讲解如何在普通文本中对Span做整体性控制前,我们先来考虑下选择的问题——如何让「整体性Span」的内部无法被选中。

首先,我们要知道,Edittext的光标也是一种Span。也就是说,我们可以通过监听光标的移动事件,通过Selection实现当光标移动到Span内部时,让它重新移动到Span最近的边缘位置,从而让Span内部永远无法插入光标,这就是我们的主要思路。

那么问题来了,我要怎么监听Edittext的光标呢?

其实,Android的Span不仅功能非常强大,而且也提供了非常完善的管理API,在TextView和Edittext中,我们要监听Text的变化过程,可以使用TextWatcher,它可以在文本发生改变时进行回调,类似的,在SpannableStringBuidler中,也有类似的管理类——SpanWatcher,它同样可以用于在Span发生变化时进行回调。

SpanWatcher,官方介绍如下。

When an object of this type is attached to a Spannable, its methods will be called to notify it that other markup objects have been added, changed, or removed.

在TextVIew的内部,它通过DynamicLayout来渲染Spannable数据,在其内部会设置SpanWatcher来监听Span的新增、修改和删除,当监听到变化后,会调用其内部的方法进行刷新。

image-20210819165313706

SpanWatcher和TextWatcher一样,都是继承自NoCopySpan,它们一个监听文本变化,一个监听Span变化。

看完了SpanWatcher,再来看下Selection,Selection是为TextView和Edittext设计的一套管理选中态的工具类,借助Selection,可以在不依赖具体View的情况下,对Span做选中态的修改。

Selection有两个状态,Start和End,而选择光标,就是Selection的两个状态,当两个状态重合时,就是光标的输入状态。

现在我们的思路就很明显了,在SpanWatcher的onSpanChanged中监听Selection的Start和End状态即可,一旦Selection的Start和End在我们的「整体性Span」中,就将Selection光标移动到最近的Span标记处。

image-20210819173317458

那么SpanWatcher怎么使用呢?

Edittext提供了Editable.Factory来自定义添加SpanWatcher,我们只需要在初始化的时候传入即可,代码如下所示。

class ExEditableFactory(private val spans: List<NoCopySpan>) : Factory() {
    override fun newEditable(source: CharSequence): Editable {
        val spannableStringBuilder = RepairSpannableStringBuilder(source)
        for (span in spans) {
            spannableStringBuilder.setSpan(span, 0, source.length, Spanned.SPAN_INCLUSIVE_INCLUSIVE or Spanned.SPAN_PRIORITY)
        }
        return spannableStringBuilder
    }
}

val watchers = ArrayList<NoCopySpan>()
watchers.add(SelectionSpanWatcher(IntegratedSpan::class))
setEditableFactory(ExEditableFactory(watchers))

这样我们就完成了选中的整体性功能,当我们的Selection在「整体性Span」(通过IntegratedSpan来标记)中时,就自动修改Selection的位置,从而实现「整体性Span」中间无法插入光标。

那么除了选中之外,剩下的一个问题就是删除的整体性控制。

相比于选中来说,删除就比较简单了,我们可以通过setOnKeyListener来监听KeyEvent.KEYCODE_DEL和KeyEvent.ACTION_DOWN事件。

当我们检测到这两个事件后,根据当前Selection的位置,拿到当前是否存在「整体性Span」,如果是「整体性Span」,那么在删除时则整体移除即可。

这里有个很重要的地方,getSpan函数传入的Start和End,是闭区间,也就是说,即使光标现在在「整体性Span」的末尾,getSpan函数也是能拿到这个Span的。

有了思路之后,我们的代码就很容易了,关键代码如下所示。

image-20210820145414181

其实这里除了对「整体性Span」进行整体性删除以为,你甚至可以使用removeSpan来移除「整体性Span」,从而将其恢复成普通文本,当然,这都是看你自己的需求了。

好了,到此为止,我们又实现了富文本编辑器中的一个非常重要的功能——Span的整体性控制。

向大家推荐下我的网站 https://xuyisheng.top/ 专注 Android-Kotlin-Flutter 欢迎大家访问


Recommend

  • 114

    中国智能手机步入“零增长”时代李娜尽管中国智能手机的增长放缓论已经持续了一段时间,但当2017年年底真正发放“成绩单”的时候,现实却比预测更加残酷。在市场调研机构Canalys公布的最新数据中,中国智能手机出货量在2017年首次迎来整体性的

  • 62
    • 掘金 juejin.im 7 years ago
    • Cache

    富文本编辑器初探

    长期以来,作为用户我是富文本编辑器的使用者,作为前端开发,我也只是富文本插件的使用者,对内部实现细节不甚了解,使用上也只停留在调用插件提供的API,实现一些业务逻辑。最近的项目,需要开发一个简易富文本编辑器,也算是让我有机会对其一窥究竟。 可编辑富...

  • 29
    • zhuanlan.zhihu.com 4 years ago
    • Cache

    Angular富文本编辑器之路的探索

    本文主要分享基于Angular和Slate开发富文本编辑器的实践历程,基于Angular做编辑器对我们来说也是一个新的尝试,社区关于Angular编辑器的实现更多的是基于原生编辑器组件化包装(比如基于Quill、Prosemirror的Angular组件),我...

  • 10

    【译】深入理解Span 这是Span开发者的一篇非常好的文章,这里翻译出来,希望大家能对Span有新的认识。 原文地址如下:https://medium.com/androiddevelopers/underspanding-spans-1b91008b97e4 Spans...

  • 9

    Span的基础性概念分析 国际惯例,官网镇楼,这是对Span最好的全局概览。 https://developer.android.com/guide/topics/text/spans Span种类 Span通常按照下面两个区别进行分类,即根据Span修改Text外观或者尺寸和Span影响...

  • 14

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

  • 4

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

  • 9

    HTML文本标签和span标签和背景图片 精选 原创 摸鱼咸鱼 2022-12-15 12:14:53

  • 6
    • qiidb.github.io 1 year ago
    • Cache

    整体性与隐缠序

    整体性与隐缠序 2022年5月1日 出版 《整体性与隐缠序:卷展中的宇宙与意识》为当代卓越的量子物理学家与最为活跃的科学思想家玻姆的著作,在本书中玻姆发展了一种量子理论,把存在作为一个未破缺的整体(包括物质与精神)来...

  • 4

    自我管理是成功的前提,应聚焦优势而非弱点,借助最佳工作方式实现高效。合理统计时间,专注单一任务,创造个人思考时间,减少决策,发挥独特价值。清晰辨别停止和开始的事项,保持精益运营,关注如何对他人有用,而非追求成功本身。 为什么作为整体...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK