Android 如何优雅地实现@人功能?

本文探讨了如何在Android中不扩展EditText优雅地实现@人功能。通过分析微博、微信和QQ的处理方式,介绍了如何添加标签文本样式,保证数据安全,以及获取绑定数据的方法。重点在于使用Spannable和DataBindingSpan接口,以及处理光标活动和选中状态的策略,以实现类似微博的@人体验。
摘要由CSDN通过智能技术生成

最近有个需求:评论@人。网上已经有一些文章分享了类似功能实现逻辑,但是几乎都是扩展EditText类,这种实现方式肯定不能进入我的首发阵容。你以为是因为它不符合面向对象六大原则?错,只因为它不够优雅!不够优雅!不够优雅!

那么,只有饮水机代码怎么办?当然是

read the fuking source code

功夫不负有心人,我读了一遍EditText源码,然后就造出了这个“优雅的”轮子(开玩笑,EditText源码怎么能叫fuking source code,他有一个爸爸叫TextView)。废话不多说,上酸菜。

在此之前,你需要记住一个跟文本相关的思想:一切皆Span

一、添加标签文本样式,并与标签的业务数据绑定

所有人都知道文本样式与Spannable有关。这里同样使用Spannable,我定义了一个DataBindingSpan<T>接口,主要有两个功能:

  1. 让用户提供一个CharSequence对象作为标签,它决定了标签文本的样式和内容
  2. 提供一个方法返回DataBindingSpan<T>对象所绑定的业务数据。
interface DataBindingSpan<T> {
    fun spannedText(): CharSequence
    fun bindingData(): T
}

示例代码:

class SpannableData(private val spanned: String): DataBindingSpan<String> {

    override fun spannedText(): CharSequence {
        return SpannableString(spanned).apply { 
            setSpan(ForegroundColorSpan(Color.RED), 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
        }
    }

    override fun bindingData(): String {
        return spanned
    }
}

这个类仅仅包装了一个字符串,spannedText()返回一个改变标签文本颜色为红色的字符串,同时 bindingData()将该字符串作为业务数据返回。

你也可以把它换成其他的,user对象不错。spannedText()返回username,bindingData()返回userId,你就可以轻松实现@人功能业务数据绑定相关的逻辑了。

二、保证文本上绑定的数据的安全可靠

当我们把Span绑定到文本上以后,我们需要在文本发生变化时,保证文本和数据的安全性,可靠性,一致性。

其实从DataBindingSpan开始,我们就在处理这个事情了。正如SpannableData所展现的一样,当spannedText()返回的是一个Spannable对象时,使用Spanned.SPAN_EXCLUSIVE_EXCLUSIVE作为flag。它不能在头部和尾部扩展Span的范围,只允许中间插入。同时,当Span覆盖的文本被删除时,Span也会被删除。也就是说,它天生具有一定数据安全可靠的属性。这会为我们省掉很多事情。

当然,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE并不具备完全的安全性。毕竟它不能阻止中间插入。这个事情得我们自己来做。那么,为了禁止中间插入,我们应该怎么做呢?

这个需求又产生了两个问题:

  1. 当普通文本发生变化后,如何监控一个Span起始位置发生变化?
  2. 如何禁止Span内部插入光标?

对于第一个问题,我在网上看到过一种思路。维护一个Span起始位置管理器SpanRangeManager,然后利用TextWather监听文本变化,文本的任何变化都会导致SpanRangeManager重新测算Span的位置。

当然,如果我使用这种方式,就不会有这篇博客了。其实Android SDK便有一个优秀的Span管理器,那就是SpannableStringBuilder。同时SDK提供了一个侦听器SpanWatcher侦听SpannableStringBuilder中Span的变化。有兴趣的同学可以去看一看他的源码。

第二个问题,我们要保证文本与数据的一致性,禁止光标插入到Span覆盖的文本中间。有三种做法:

  1. 普通文本,当标签文本被破坏(删除、插入、追加文本)时,让绑定的数据失效,这就是微信的做法。
  2. 普通文本,把标签文本作为一个整体,不能对标签内部插入光标,杜绝数据被破坏的情况,这是微博的做法。
  3. 占位符,使用不可分割的Span(如ImageSpan)替换,这是QQ的做法。

微博、微信的方法都必须要对软键盘删除键、文本变化、光标活动、文本选中状态以及span变化进行监听和处理。QQ就简单多了,后面会讲到。

微博的做法

1. 侦听并处理光标活动、选中状态以及Span位置变化

对于光标活动和选中状态侦听,如果采用继承EditText的方式实现标签文本功能,重写onSelectionChanged(int selStart, int selEnd)方法便能够侦听光标活动。但是,这种方式怎么能算优雅呢?

要想“优雅地”实现怎么办?还是那句话:

read the fuking source code

两个角色:

  1. Selection
  2. SpanWatcher

如果有一篇文章叫做《Selection如何管理文本光标活动和选中状态?》,那么它一定能回答这个问题。这里不会详细讲述Selection内部实现,你只需要知道两点:

  1. 选中状态具有起点(start)和终点(end),而start与end反映在文本中,其实是两个NoCopySpan: START, END。
  2. 光标是一种特殊的选中状态,start与end在同一位置;

既然选中状态的实现是Span,它就是与View无关的,而与Spannable有关。也就是说,我们可以不使用EditText自身的API却能够管理它的光标活动和选中状态(请注意这几句话,他是“优雅实现”的基石)。

Selection管理光标活动。那么࿰

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值