美文网首页
解决TextView中ClickableSpan点击事件

解决TextView中ClickableSpan点击事件

作者: Brian512 | 来源:发表于2018-05-03 19:32 被阅读443次

需求背景:
在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

相关文章

网友评论

      本文标题:解决TextView中ClickableSpan点击事件

      本文链接:https://www.haomeiwen.com/subject/bgulrftx.html