您的问题的第一个答案是您没有在 TextView 上设置点击侦听器,正如 user2558882 指出的那样,它正在消耗点击事件。在 TextView 上设置点击侦听器后,您会看到 ClickableSpans 触摸区域之外的区域将按预期工作。但是,您随后会发现,当您单击其中一个 ClickableSpan 时,TextView 的 onClick 回调也会被触发。如果同时开火对您来说是个问题,这将导致我们遇到一个难题。 user2558882 的回复不能保证您的 ClickableSpan 的 onClick 回调将在您的 TextView 之前触发。以下是来自a similar thread 的一些解决方案,这些解决方案得到了更好的实施以及来自源代码的解释。线程应该适用于大多数设备的公认答案,但该答案的 cmets 提到某些设备存在问题。看起来应该归咎于某些具有自定义运营商/制造商 UI 的设备,但这是猜测。
那么为什么不能保证onClick回调顺序呢?如果您查看 TextView (Android 4.3) 的源代码,您会注意到在 onTouchEvent 方法中,boolean superResult = super.onTouchEvent(event);(超级是 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 实现会复制您粘贴的内容,其他事件可能会按预期失败。