ClickableSpan In Clickable TextView
我在使用ClickableSpan
时想实现以下功能,点击ClickableSpan
进入事件1,点击TextView
中剩余部分进入事件2,不幸的是,我发现在默认情况下,点击ClickableSpan
区域会同时触发TextView
的OnClick
事件和ClickableSpan
的onClick
事件,而且在响应时,明显感觉到TextView
的onClick
事件比ClickableSpan
的onClick
事件响应稍迟,这是因为TextView
的onClick
事件在一个新的线程响应,而ClickableSpan
的事件直接在UI线程响应,接下来通过查看TextView
源码我发现了这种冲突的解决方案。
TextView
的onTouchEvent
源码如下:
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
if (mEditor != null) mEditor.onTouchEvent(event);
final boolean superResult = super.onTouchEvent(event);
/*
* Don't handle the release after a long press, because it will
* move the selection away from whatever the menu action was
* trying to affect.
*/
if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
mEditor.mDiscardNextActionUp = false;
return superResult;
}
final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
(mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
boolean handled = false;
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
final boolean textIsSelectable = isTextSelectable();
if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
// The LinkMovementMethod which should handle taps on links has not been installed
// on non editable text that support text selection.
// We reproduce its behavior here to open links for these.
ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
getSelectionEnd(), ClickableSpan.class);
if (links.length > 0) {
links[0].onClick(this);
handled = true;
}
}
if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
handled |= imm != null && imm.showSoftInput(this, 0);
}
// The above condition ensures that the mEditor is not null
mEditor.onTouchUpEvent(event);
handled = true;
}
if (handled) {
return true;
}
}
return superResult;
}
从以上源码我们可以看出在点击事件发生时,TextView
首先调用了super.onTouchEvent(event)
触发了TextView
的onClick
事件,此时并没有结束事件,而是一路向下,又来到了links[0].onClick(this);
触发了ClickableSpan
的onClick
事件,进而两个监听事件都被执行了。搞清楚原因之后我们就来处理,重写TextView
的onTouchEvent
事件,代码如下:
import android.content.Context;
import android.content.Intent;
import android.text.Spannable;
import android.text.method.MovementMethod;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
/**
* Created by TuoZhaoBing on 2016/4/28 0028.
*/
public class ClickPreventableTextView extends TextView implements View.OnClickListener {
private boolean preventClick;
private View.OnClickListener clickListener;
private boolean ignoreSpannableClick;
public void setIgnoreSpannableClick(boolean ignoreSpannableClick) {
this.ignoreSpannableClick = ignoreSpannableClick;
}
public Node mNode;
public RootNode mRoot;
private Context mContext;
private MindGraph mVg;
public ClickPreventableTextView(Context context, MindGraph vg) {
super(context);
setClickable(true);
mContext = context;
mVg = vg;
setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mNode){
Intent intent = new Intent(mContext,Main2Activity.class);
mContext.startActivity(intent);
}
}
});
}
public ClickPreventableTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ClickPreventableTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public boolean onTouchEvent(MotionEvent event) {
if (getMovementMethod() != null)
((ClickableMovementMethod)getMovementMethod()).onTouchEvent(this, (Spannable)getText(), event);
this.ignoreSpannableClick = true;
boolean ret = super.onTouchEvent(event);
this.ignoreSpannableClick = false;
return ret;
}
/**
* Returns true if click event for a clickable span should be ignored
* @return true if click event should be ignored
*/
public boolean ignoreSpannableClick() {
return ignoreSpannableClick;
}
/**
* Call after handling click event for clickable span
*/
public void preventNextClick() {
preventClick = true;
}
@Override
public void setOnClickListener(OnClickListener listener) {
this.clickListener = listener;
super.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (preventClick) {
preventClick = false;
} else if (clickListener != null)
clickListener.onClick(v);
}
}
由于使用了自定义的LinkMomentMethod
,所以((ClickableMovementMethod)getMovementMethod()).onTouchEvent(this, (Spannable)getText(), event);
这里做了强转,在Clickable中如下使用:
ClickableSpan is = new ClickableSpan() {
@Override
public void onClick(View view) {
if (view instanceof ClickPreventableTextView){
if (((ClickPreventableTextView)view).ignoreSpannableClick())
return;
//点击ClickableSpan所要执行的操作
((ClickPreventableTextView)view).preventNextClick();
}
}
};
对ClickPreventableTextView
可以照常setOnClickListener
设置点击事件,这时我们发现点击ClickableSpan
区域并不会触发TextView的onClick事件,注意在ClickableSpan
的onClick
事件执行后,一定要执行((ClickPreventableTextView)view).preventNextClick();
参考链接:
StackOverFlow
陈蒙的博客