携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
富文本中某段特殊文本实现点击事件大家想也不想的会重写
ClickableSpan实现,但是如何实现长点击事件呢,本篇文章介绍一种思路:通过设置TextView的setOnTouchListener()实现特殊文本段的点击和长点击事件。
一. 自定义标识来标识特殊文本
标识类ClickOrLongClickSpan:
class ClickOrLongClickSpan(val value: Any) {
}
标识TextView中的某段特殊文本:
val tv = TextView(this)
tv.setText("今天非常开心,因为明天就是周五后天就是周六大后天就是周日了。", TextView.BufferType.SPANNABLE)
(tv.text as? Spannable)?.let {
it[5, 10] = ClickOrLongClickSpan("")
}
接下来的功能都需要通过setOnTouchListener{}实现了。
二.判断用户点击坐标是否落在特殊文本中
先上代码,然后一步步进行分析:
tv.setOnTouchListener { v, event ->
//1.过滤掉非富文本的实现View
if (v !is TextView) {
return@setOnTouchListener false
}
val x = event.x
val y = event.y
//2.获取点击的坐标在富文本字符中位置
val layout = v.layout
val line = layout.getLineForVertical((y - tv.paddingTop).toInt())
val pos = layout.getOffsetForHorizontal(line, x)
//3. 判断点击坐标是否落在特殊文本中
val spannable = v.text as Spannable
val spans = spannable.getSpans<ClickOrLongClickSpan>(pos, pos + 1)
if (spans.isEmpty().not()) {
}
return@setOnTouchListener true
}
-
过滤掉非富文本的实现View
由于富文本的载体是
TextView或EditView,所以非TextView实现类直接返回false,不拦截整体的TextView点击事件。 -
获取点击的坐标在富文本字符中位置
这个就得通过
Layout来大显身手了:
-
getLineForVertical()传入点击位置的y轴方向的坐标,可以获取当前点击了富文本的第几行。PS:请注意,
EditView顶部存在内边距,所以需要将当前的触摸点坐标减去顶部内边距,即y - tv.paddingTop,这样获取的结果才够准确。 -
getOffsetForHorizontal()该方法传入上面计算出的行数及触摸点的横坐标,就可以获取当前触摸点的字符在富文本中的位置。
-
判断触摸点是否落在
ClickOrLongClickSpan标识的特殊文本中关键就是调用
spannable.getSpans<ClickOrLongClickSpan>()获取触摸点的ClickOrLongClickSpan数组集合,如果当前数组大小不为0,就代表当前的触摸点落在了ClickOrLongClickSpan标识的特殊文本中。
三.实现特殊文本点击事件
这里我们就参考View源码中点击实现的方式,在MotionEvent的UP事件中实现:
tv.setOnTouchListener { v, event ->
//...省略上面触摸点落在特殊文本中的判断
//点击了特殊文本
when(event.action) {
MotionEvent.ACTION_UP -> {
performClick()
//拦截触摸事件,不响应TextView本身的点击事件
return@setOnTouchListener true
}
}
return@setOnTouchListener false
}
private fun performClick() {
}
请注意,如果实现了特殊文本的点击事件之后,setOnTouchListener{}方法就得返回为true,防止响应TextView本身的点击事件,解决点击冲突。
四.实现特殊文本长按事件
这个我们同样参考下View源码中点击事件如何实现的,直接看下View的onTouchEvent实现源码:
MotionEvent.DOWN最终会调用checkForLongClick方法:
-
CheckForLongPress真正实现了长点击事件的调用CheckForLongPress是个Runnable对象,最终在其run()中会调用这个熟悉的方法performLongClick,最终会走到performLongClickInternal()中:mOnLongClickListener就是我们设置的长点击事件,发生了调用。 -
postDelayed()延迟执行长点击事件可以看到,长点击事件的包装类
CheckForLongPress并不是直接被执行的,而是通过postDelayed()发送了一个延迟消息执行,这就很符合用户平常的使用习惯了:触摸屏幕一会会才能触发长按事件。一般这个延迟执行的时间为
300或400。
经过上面的一番探究,我们就可以在setOnTouchListener{}实现特殊文本的长按事件:
tv.setOnTouchListener { v, event ->
//点击了特殊文本
when(event.action) {
MotionEvent.ACTION_DOWN -> {
//实现长按事件
v.postDelayed(performLongClick(), 300)
return@setOnTouchListener true
}
}
return@setOnTouchListener false
}
}
五.处理特殊文本点击和长按事件冲突
特殊文本的点击事件和长按事件肯定不能同时执行,所以需要进行处理:
//点击了特殊文本
when (event.action) {
MotionEvent.ACTION_UP -> {
//移除长点击事件,可能执行可能没执行
v.removeCallbacks(mRunnable)
//判断是否发生了长按事件,是不执行点击
if (mIsLongClickOccur.not()) {
performClick()
}
//拦截触摸事件,不响应TextView本身的点击事件
return@setOnTouchListener true
}
MotionEvent.ACTION_DOWN -> {
//重置长点击事件发生标识
mIsLongClickOccur = false
v.postDelayed(mRunnable, 300)
return@setOnTouchListener true
}
}
//获取长点击执行Runnable
private var mRunnable: Runnable? = null
get() {
if (field == null) {
field = performLongClick()
}
return field
}
//是否发生了长点击事件
private var mIsLongClickOccur = false;
private fun performLongClick() = Runnable {
//设置长点击标识为true
mIsLongClickOccur = true
//执行其他逻辑
}
上面代码上的注释已经很充分了,大家看下就好了。
总结
本篇文章主要讲解了通过setOnTouchListener{}实现富文本中特殊文本段的点击和长按事件的一个思路,上面的代码也是描述一个思路,以及部分简单实现,真正的业务场景还有很多其他的特殊处理。
如果觉得还行,可以点个赞支持下哈,感谢!!