您的问题的第一个答案是您没有在 TextView 上设置点击侦听器,正如 user2558882 指出的那样,它正在消耗点击事件。在 TextView 上设置单击侦听器后,您会看到 ClickableSpans 触摸区域之外的区域将按预期工作。但是,您随后会发现,当您单击其中一个 ClickableSpan 时,TextView 的 onClick 回调也会被触发。如果两个火都对你来说是个问题,这会给我们带来一个难题。user2558882 的回复不能保证您的 ClickableSpan 的 onClick 回调将在您的 TextView 之前触发。这是来自类似线程的一些解决方案更好地实施并从源头解释。线程应该适用于大多数设备的公认答案,但该答案的评论提到某些设备存在问题。看起来应该归咎于某些具有自定义运营商/制造商 UI 的设备,但这是猜测。
那么为什么不能保证onClick回调顺序呢?如果您查看 TextView (Android 4.3) 的源代码,您会注意到在 onTouchEvent 方法中, boolean superResult = super.onTouchEvent(event);
(super is View) 在调用之前handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
调用了移动方法,然后调用了 ClickableSpan 的 onClick。看看 super 的 (View) onTouchEvent(..),你会注意到:
// Use a Runnable and post this rather than
// performClick directly. This lets other visual
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) { // <---- In the case that this won't post,
performClick(); // it'll fallback to calling it directly
}
performClick() 调用点击侦听器集,在本例中是我们的 TextView 的点击侦听器。这意味着,您将不知道 onClick 回调将按什么顺序触发。您所知道的是,您的 ClickableSpan 和 TextView 单击侦听器将被调用。我之前提到的线程上的解决方案有助于确保顺序,以便您可以使用标志。
如果确保与许多设备的兼容性是一个优先事项,那么最好再看看你的布局,看看你是否可以避免陷入这种情况。像这样的裙子通常有很多布局选项。
编辑评论答案:
当您的 TextView 执行 onTouchEvent 时,它会调用您的 LinkMovementMethod 的 onTouchEvent,以便它可以处理对您的各种 ClickableSpan 的 onClick 方法的调用。您的 LinkMovementMethod 在其 onTouchEvent 中执行以下操作:
@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[] link = buffer.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
您会注意到它需要 MotionEvent,获取动作(ACTION_UP:抬起手指,ACTION_DOWN:按下手指),触摸起源的 x 和 y 坐标,然后找到哪个行号和偏移量(文本中的位置)触摸击中。最后,如果存在包含该点的 ClickableSpans,则会检索它们并调用它们的 onClick 方法。由于我们希望将任何触摸传递给您的父布局,您可以调用布局 onTouchEvent 如果您希望它在触摸时执行所有操作,或者如果它实现了您需要的功能,您可以调用它的点击侦听器。这是在哪里做的:
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
// Your call to your layout's onTouchEvent or it's
//onClick listener depending on your needs
}
}
因此,回顾一下,您将创建一个扩展 LinkMovementMethod 的新类,覆盖它的 onTouchEvent 方法,使用您的调用复制并粘贴此源代码到我评论的正确位置,确保您将 TextView 的移动方法设置为这个新的子类和你应该被设置。
再次编辑以避免可能的副作用
查看 ScrollingMovementMethod 的源(LinkMovementMethod 的父级),您会看到它是调用静态方法的委托方法return Touch.onTouchEvent(widget, buffer, event);
这意味着您可以将其添加为方法中的最后一行并避免调用super 的(LinkMovementMethod 的)onTouchEvent 实现将复制您粘贴的内容,并且其他事件可能会按预期失败。