需求背景:
在TextView中包含局部可点击的链接,且改链接其他地方也是有相应的点击事件
其实需求比较合理,实现也应该不难,于是简单的demo如下:
TextView content = (TextView) findViewById(R.id.comment_item_detail_content);
String string = "我是和常常大声点发大水发送到发送到发";
SpannableString spannableString = new SpannableString(string);
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
LogUtil.showCallStack();
}
};
spannableString.setSpan(clickableSpan, 3, 9, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
content.setText(spannableString);
content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtil.showCallStack();
}
});
content.setMovementMethod(LinkMovementMethod.getInstance());
点击clickableSpan
之外的其他地方是可以触发普通的点击事件,问题是点击clickableSpan
区域时,不仅会触发ClickableSpan.onClick(view)
,还会触发TextView.onClick(view)
。
PS:这里仅探讨单个TextView中的问题,若是ListView中使用,请参考https://stackoverflow.com/questions/16792963/android-clickablespan-intercepts-the-click-event
查看TextView.onTouchEvent(MotionEvent)
可以发现问题:
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
...
final boolean superResult = super.onTouchEvent(event);
...
if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
...
if (handled) {
return true;
}
}
return superResult;
}
上面代码仅显示了主要部分,具体代码自行查看;首先可以看到点击时会先调用super.onTouchEvent(event)
触发点击事件,然后往下走到mMovement.onTouchEvent(this, (Spannable) mText, event)
,这里会判断点击区是否是ClickableSpan
,是的话就会调用ClickableSpan.onClick(widget)
。
我在调用的地方查看了两个onClick回调的调用路径:
——> (Activity.java:2820) dispatchTouchEvent
——> (PhoneWindow.java:1737) superDispatchTouchEvent
——> (PhoneWindow.java:2403) superDispatchTouchEvent
——> (ViewGroup.java:2255) dispatchTouchEvent
——> (ViewGroup.java:2554) dispatchTransformedTouchEvent
——> (ViewGroup.java:2255) dispatchTouchEvent
——> (ViewGroup.java:2554) dispatchTransformedTouchEvent
——> (ViewGroup.java:2255) dispatchTouchEvent
——> (ViewGroup.java:2554) dispatchTransformedTouchEvent
——> (ViewGroup.java:2255) dispatchTouchEvent
——> (ViewGroup.java:2554) dispatchTransformedTouchEvent
——> (ViewGroup.java:2255) dispatchTouchEvent
——> (ViewGroup.java:2554) dispatchTransformedTouchEvent
——> (ViewGroup.java:2255) dispatchTouchEvent
——> (ViewGroup.java:2554) dispatchTransformedTouchEvent
——> (View.java:9306) dispatchTouchEvent
——> (TextView.java:8347) onTouchEvent
——> (LinkMovementMethod.java:217) onTouchEvent
——> (TextViewClickActivity.java:35) onClick
——> LogUtil.showCallStack()
05-03 18:24:49.126 16296-16296/com.brian.testandroid E/LogUtil: showCallStack:DEMO(ZygoteInit.java:625) main
——> (ZygoteInit.java:735) run
——> (Method.java:-2) invoke
——> (ActivityThread.java:5432) main
——> (Looper.java:148) loop
——> (Handler.java:95) dispatchMessage
——> (Handler.java:739) handleCallback
——> (View.java:21177) run
——> (View.java:5207) performClick
——> (TextViewClickActivity.java:48) onClick
——> LogUtil.showCallStack()
从上面的调用路径发现ClickableSpan.onClick(widget)
是同步调用的,先执行,而TextView.onClick(view)
是异步调用的,在后面执行。看到这里我们就可以有办法处理上面的问题。
由于上面两个回调都是在UI线程执行,所以必然会有先后顺序,并且ClickableSpan.onClick(widget)
是先执行的。我们就可以想办法在ClickableSpan.onClick(widget)
触发后,能否把后面的TextView.onClick(view)
回调拦截或者取消。
于是,添加一个标志字段:
private boolean mClickHandled = false;
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
LogUtil.showCallStack();
mClickHandled = true; // 标记为已处理
}
};
spannableString.setSpan(clickableSpan, 3, 9, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
content.setText(spannableString);
content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mClickHandled) { // 若已处理则直接返回
mClickHandled = false;
return;
}
LogUtil.showCallStack();
}
});
===========================
参考:
https://blog.csdn.net/zhaizu/article/details/51038113
https://stackoverflow.com/questions/16792963/android-clickablespan-intercepts-the-click-event
网友评论