ClickableSpan

398 阅读2分钟

##点击瞄点

最近在开发中遇到使用SpannableStringBuilder给TextView中部分字体变色并可点击,TextView设置LinkMovementMethod,并且在

stringBuilder.setSpan(ClickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)

使用了ClickableSpan,但测试反馈说如果标签后面有/n换行符时,点击结尾空白处会响应点击事件,这显然无法满足需求,此前为了解决LinkMovementMethod在多行时会滚动,所以重写了OnTouchListener

public class LinkMovementMethodOverride implements View.OnTouchListener {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        TextView widget = (TextView) v;
        Object text = widget.getText();
        if (text instanceof Spanned) {
            Spanned buffer = (Spanned) text;
            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) {
                        return true;
                    }
                }
            }
        }
        return false;
    }
}

这样在TextView多行展示时就不会滚动,但是ClickableSpan的问题如何解决呢? 在查阅相关资料后,发现了两个比较有效的解决方法:

第一种

public class LinkMovementMethodOverride implements View.OnTouchListener {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (!(v instanceof TextView)) {
            return false;
        }
        TextView widget = (TextView) v;
        CharSequence text = widget.getText();
        if (!(text instanceof Spanned)) {
            return false;
        }
        int action = event.getAction();
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            int index = getTouchedIndex(widget, event);
            ClickableSpan link = getClickableSpanByIndex(widget, index);
            if (link != null) {
                if (action == MotionEvent.ACTION_UP) {
                    link.onClick(widget);
                }
                return true;
            }
        }
        return false;
    }

    public static ClickableSpan getClickableSpanByIndex(TextView widget, int index) {
        if (widget == null || index < 0) {
            return null;
        }
        CharSequence charSequence = widget.getText();
        if (!(charSequence instanceof Spanned)) {
            return null;
        }
        Spanned buffer = (Spanned) charSequence;
        // end 应该是 index + 1,如果也是 index,得到的结果会往左偏
        ClickableSpan[] links = buffer.getSpans(index, index + 1, ClickableSpan.class);
        if (links != null && links.length > 0) {
            return links[0];
        }
        return null;
    }

    public static int getTouchedIndex(TextView widget, MotionEvent event) {
        if (widget == null || event == null) {
            return -1;
        }
        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();
        // 根据 y 得到对应的行 line
        int line = layout.getLineForVertical(y);
        // 判断得到的 line 是否正确
        if (x < layout.getLineLeft(line) || x > layout.getLineRight(line)
                || y < layout.getLineTop(line) || y > layout.getLineBottom(line)) {
            return -1;
        }
        // 根据 line 和 x 得到对应的下标
        int index = layout.getOffsetForHorizontal(line, x);
        // 这里考虑省略号的问题,得到真实显示的字符串的长度,超过就返回 -1
        int showedCount = widget.getText().length() - layout.getEllipsisCount(line);
        if (index > showedCount) {
            return -1;
        }
        // getOffsetForHorizontal 获得的下标会往右偏
        // 获得下标处字符左边的左边,如果大于点击的 x,就可能点的是前一个字符
        if (layout.getPrimaryHorizontal(index) > x) {
            index -= 1;
        }
        return index;
    }

}

第二种:

int clickEnd = start + officialTag.length();
if (clickEnd > 1) {
       clickEnd = clickEnd - 1;
    }
spannable.setSpan(clickableSpan, start, clickEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(Color.parseColor("#25d4d0")), start, start + officialTag.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

参考链接:

blog.csdn.net/wzlyd1/arti… blog.csdn.net/weixin_3417…