解決設置ClickableSpan后長按沖突的問題
問題描述
3月份修改別人代碼的時候想要屏蔽TextView的長按事件,發現TextView有重寫OnTouchEvent方法,然后在其中加了長按事件的判斷,是長按事件則不做任何處理。結果測試發現並沒有得到想要的效果,所以繼續查看代碼,最終發現,代碼里對TextView設置了setSpan(new ClickableSpan),導致長按事件無法被我們捕捉到。
分析
因為查看代碼是因為添加了setSpan(new ClickableSpan)方法導致長按無法被檢測到,所以主要是分析ClickableSpan來尋找解決方法。
ClickableSpan
If an object of this type is attached to the text of a TextView with a movement method of LinkMovementMethod, the affected spans of text can be selected. If selected and clicked, the {@link #onClick} method will be called.
翻譯一下:如果這個類型被添加到TextView的text上,並且這個TextView有一個LinkMovementMethod的行為方法,則text可以被選擇。如果選擇並且點擊,則onClick方法會被調用。
就是說ClickanleSpan被設置到TextView的某段text上,點擊被設置的text,則會調用ClickanleSpan的onClick方法。
LinkMovementModel
A movement method that traverses links in the text buffer and scrolls if necessary. Supports clicking on links with DPad Center or Enter.
翻譯一下:鏈接文字的行為方法並在需要是滾動。支持DPad中心或輸入的鏈接點擊。
查看LInkMovementModel的源碼發現,它有一個onTouchEvent方法:
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
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[] links = buffer.getSpans(off, off, ClickableSpan.class);
if (links.length != 0) {
if (action == MotionEvent.ACTION_UP) {
links[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(links[0]),
buffer.getSpanEnd(links[0]));
}
return true;
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
查看源碼發現,它只是處理了ACTION_UP(抬起)與ACTION_DOWN(按下)事件,里面沒有處理長按事件,也沒有長按事件的接口。
所以解決方法找到了,就是重寫LInkMovementModel的onTouchEvent()方法。
解決方法
寫一個LinkMovementClickMethod類,繼承LinkMovementMethod類,然后重寫onTouchEvent()方法。
將源碼中的onTouchEvent代碼復制過來,加上一個判斷,如果點擊不是長按,則按照源碼的處理方式處理,代碼如下:
public class LinkMovementClickMethod extends LinkMovementMethod {
private long lastClickTime;
private static final long CLICK_DELAY = 500l;
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
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) {
if (System.currentTimeMillis() - lastClickTime < CLICK_DELAY) {
link[0].onClick(widget);
}
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
lastClickTime = System.currentTimeMillis();
}
============================修改的部分==============================
return true;
} else {
Selection.removeSelection(buffer);
}
}
return super.onTouchEvent(widget, buffer, event);
}
public static LinkMovementClickMethod getInstance() {
if (null == sInstance) {
sInstance = new LinkMovementClickMethod();
}
return sInstance;
}
private static LinkMovementClickMethod sInstance;
}
這樣長按事件就被排除在外,問題得以解決。