多Span场景下ClickableSpan的OnClick事件仅首个生效问题咨询
解决多ClickableSpan仅首个OnClick触发的问题
我之前也碰到过完全一样的坑——给SpannableString加了好几个ClickableSpan,文字样式(变红、下划线)都正常生效,可偏偏只有第一个Span的点击事件能触发,后面的完全没反应。折腾了半天终于摸清楚了关键问题,给你梳理下解决思路:
1. 先检查Span的范围是否准确
这是最容易忽略的错误!很多时候我们手动计算文字索引时出错,导致后面的ClickableSpan根本没覆盖到目标文本。比如用substring或者手动数字符位置时,搞反了起始/结束索引,或者漏算了字符长度。
举个正确的范围设置例子:
SpannableStringBuilder ssb = new SpannableStringBuilder("这是第一个可点击文本,这是第二个可点击文本"); // 第一个可点击区域:"第一个可点击文本",索引左闭右开(2到9) ClickableSpan span1 = new ClickableSpan() { @Override public void onClick(View widget) { Toast.makeText(widget.getContext(), "点击第一个", Toast.LENGTH_SHORT).show(); } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setColor(Color.RED); ds.setUnderlineText(true); } }; ssb.setSpan(span1, 2, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // 第二个可点击区域:"第二个可点击文本",索引11到18 ClickableSpan span2 = new ClickableSpan() { @Override public void onClick(View widget) { Toast.makeText(widget.getContext(), "点击第二个", Toast.LENGTH_SHORT).show(); } @Override public void updateDrawState(TextPaint ds) { super.updateDrawState(ds); ds.setColor(Color.RED); ds.setUnderlineText(true); } }; ssb.setSpan(span2, 11, 18, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setText(ssb);
2. 替换默认的LinkMovementMethod(核心解决办法)
默认的LinkMovementMethod.getInstance()在处理多个ClickableSpan时,经常会出现点击事件被首个Span拦截的情况。解决办法是自定义一个MovementMethod,精准定位点击位置对应的Span:
public class MultiClickMovementMethod extends LinkMovementMethod { private static MultiClickMovementMethod sInstance; public static MultiClickMovementMethod getInstance() { if (sInstance == null) { sInstance = new MultiClickMovementMethod(); } return sInstance; } @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { int action = event.getAction(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { int x = (int) event.getX(); int y = (int) event.getY(); // 计算点击位置相对于文本内容的坐标 x -= widget.getTotalPaddingLeft(); y -= widget.getTotalPaddingTop(); x += widget.getScrollX(); y += widget.getScrollY(); Layout layout = widget.getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); // 获取点击位置对应的所有ClickableSpan ClickableSpan[] spans = buffer.getSpans(off, off, ClickableSpan.class); if (spans.length != 0) { if (action == MotionEvent.ACTION_UP) { spans[0].onClick(widget); } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(spans[0]), buffer.getSpanEnd(spans[0])); } return true; } else { Selection.removeSelection(buffer); } } return super.onTouchEvent(widget, buffer, event); } }
然后给TextView设置这个自定义MovementMethod:
textView.setMovementMethod(MultiClickMovementMethod.getInstance()); textView.setHighlightColor(Color.TRANSPARENT); // 可选:去掉点击时的默认高亮背景
3. 检查TextView的属性设置
- 不要设置
android:clickable="true",否则会覆盖Span的点击事件 - 如果开启了
android:autoLink属性,可能会和自定义ClickableSpan冲突,建议关闭 - 可以保留
android:focusable="true"和android:focusableInTouchMode="true",不影响Span点击
按这几步排查和修改后,多个ClickableSpan的点击事件应该就能正常触发了!
内容的提问来源于stack exchange,提问作者Omar Qadomi
相关产品推荐
相关产品推荐

