1、ClickableSpan与TextView点击冲突
在我们使用ClickableSpan的时候,需要配合MovementMethod才能实现,不过往往会导致整个TextView的点击事件都被拦截
movementMethod = LinkMovementMethod.getInstance()
解决方案,不使用LinkMovementMethod,手动解析处理
var onItemClick: (() -> Unit)? = null
setOnTouchListener { v, event ->
when (event.actionMasked) {
MotionEvent.ACTION_UP -> {
val tv = v as? TextView ?: return@setOnTouchListener false
val spanned = (tv.text as? Spanned) ?: return@setOnTouchListener false
val x = event.x - tv.totalPaddingLeft + tv.scrollX
val y = event.y - tv.totalPaddingTop + tv.scrollY
val offset =
tv.layout.getOffsetForHorizontal(
tv.layout.getLineForVertical(y.toInt()),
x
)
if (spanned.getSpans(offset, offset, ClickableSpan::class.java)
.getOrNull(0) != null
) {
spanned.getSpans(offset, offset, ClickableSpan::class.java).getOrNull(0)
?.onClick(v)
} else {
onItemClick?.invoke()
}
}
}
true
}
2、TextView点击展开效果
//使用StaticLayout测量文字宽度
val layout = StaticLayout(
originalText, paint, measuredWidth,
Layout.Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineSpacingExtra, false
)
val span = SpannableStringBuilder()
if (layout.lineCount > maxLines) {
val expandTextWithDot = "... $expandText"
//获取最大一行的最后一个字符的位置
val lastVisibleLineEndIndex = layout.getLineVisibleEnd(maxLines - 1)
val content =
originalText.subSequence(
0,
lastVisibleLineEndIndex - expandTextWithDot.length - 1
)
span.append(content)
span.append("... ")
span.append(expandText)
span.setSpan(
MoreClickableSpan(moreColor) { toggle() },
span.length - expandText.length,
span.length,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
完整代码如下,只有点击展开,没有关闭
class ExpandableTextView : AppCompatTextView {
private val expandText = context.getString(R.string.string_key_2067)
private val moreColor = context.resources.getColor(R.color.sui_color_link)
var onItemClick: (() -> Unit)? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
init {
setOnTouchListener { v, event ->
when (event.actionMasked) {
MotionEvent.ACTION_UP -> {
val tv = v as? TextView ?: return@setOnTouchListener false
val spanned = (tv.text as? Spanned) ?: return@setOnTouchListener false
val x = event.x - tv.totalPaddingLeft + tv.scrollX
val y = event.y - tv.totalPaddingTop + tv.scrollY
val offset =
tv.layout.getOffsetForHorizontal(
tv.layout.getLineForVertical(y.toInt()),
x
)
if (spanned.getSpans(offset, offset, ClickableSpan::class.java)
.getOrNull(0) != null
) {
spanned.getSpans(offset, offset, ClickableSpan::class.java).getOrNull(0)
?.onClick(v)
} else {
onItemClick?.invoke()
}
}
}
true
}
}
private var originalText: CharSequence = ""
var isExpanded = false
/**
* 展开/收起文本
*/
private fun toggle() {
isExpanded = !isExpanded
setText()
}
/**
* 设置文本内容
*/
fun setTextSuper(content: CharSequence) {
originalText = content
setText()
}
/**
* 根据是否展开设置文本内容
*/
private fun setText() {
// 如果文本已经展开,直接显示原始内容和收起按钮
if (isExpanded) {
maxLines = Integer.MAX_VALUE
text = SpannableStringBuilder(originalText)
} else {
val layout = StaticLayout(
originalText, paint, measuredWidth,
Layout.Alignment.ALIGN_NORMAL, lineSpacingMultiplier, lineSpacingExtra, false
)
val span = SpannableStringBuilder()
if (layout.lineCount > maxLines) {
val expandTextWithDot = "... $expandText"
val lastVisibleLineEndIndex = layout.getLineVisibleEnd(maxLines - 1)
val content =
originalText.subSequence(
0,
lastVisibleLineEndIndex - expandTextWithDot.length - 1
)
span.append(content)
span.append("... ")
span.append(expandText)
span.setSpan(
MoreClickableSpan(moreColor) { toggle() },
span.length - expandText.length,
span.length,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
} else {
span.append(originalText)
}
text = span
}
}
class MoreClickableSpan(private val moreColor: Int, private val listener: () -> Unit) :
ClickableSpan() {
override fun onClick(widget: View) {
listener.invoke()
}
override fun updateDrawState(ds: TextPaint) {
ds.color = moreColor
ds.isUnderlineText = false // 取消下划线
}
}
}