##点击瞄点
最近在开发中遇到使用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);
参考链接: