android clickablespan listview,解决TextView中ClickableSpan点击事件

需求背景:

在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)。

查看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();

}

});

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值