Android:可点击的TextView中的ClickableSpan

我有一个可以包含可点击链接的文本视图。 当点击其中一个链接时,我想开始一个活动。 这工作正常,但也应该可以点击整个文本视图,并开始另一个活动。

所以这是我目前的解决scheme:

TextView tv = (TextView)findViewById(R.id.textview01); Spannable span = Spannable.Factory.getInstance().newSpannable("test link span"); span.setSpan(new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "link clicked"); Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); tv.setText(span); tv.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Log.d("main", "textview clicked"); Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show(); } }); tv.setMovementMethod(LinkMovementMethod.getInstance()); 

问题是,当我设置一个OnClickListener时,每次我点击一个链接, 首先是整个文本视图的监听器,然后 ClickableSpan被调用。

有没有办法阻止Android调用整个文本视图的监听器,当一个链接被点击? 或者在整个视图的听众中决定是否点击链接?

Solutions Collecting From Web of "Android:可点击的TextView中的ClickableSpan"

find一个非常简单的解决方法。 在所有不属于链接部分的文本区域上定义ClickableSpan,并像点击文本视图一样处理点击:

 TextView tv = (TextView)findViewById(R.id.textview01); Spannable span = Spannable.Factory.getInstance().newSpannable("test link span"); span.setSpan(new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "link clicked"); Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // All the rest will have the same spannable. ClickableSpan cs = new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "textview clicked"); Toast.makeText(Main.this, "textview clicked", Toast.LENGTH_SHORT).show(); } }; // set the "test " spannable. span.setSpan(cs, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // set the " span" spannable span.setSpan(cs, 6, span.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); tv.setText(span); tv.setMovementMethod(LinkMovementMethod.getInstance()); 

希望这有助于(我知道这个线程是旧的,但万一任何人现在看到它…)。

这是一个相当简单的解决scheme..这对我工作

 textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ClassroomLog.log(TAG, "Textview Click listener "); if (textView.getSelectionStart() == -1 && textView.getSelectionEnd() == -1) { // do your code here this will only call if its not a hyperlink } } }); 

马修build议子类化TextView,并提出了一个相当丑陋的解决方法。 但它的作品:

我已经创build了一个“ClickPreventableTextView”,当我在一个TextView中使用clickablespans时,它应该是可点击的整体。

在onTouchEvent方法中,在调用基本TextView类的onTouchEvent之前,此类将调用MovementMethod的onTouchEvent方法。 所以这是保证,clickablespan的监听器将首先被调用。 而且我可以防止调用整个TextView的OnClickListener

 /** * TextView that allows to insert clickablespans while whole textview is still clickable<br> * If a click an a clickablespan occurs, click handler of whole textview will <b>not</b> be invoked * In your span onclick handler you first have to check whether {@link ignoreSpannableClick} returns true, if so just return from click handler * otherwise call {@link preventNextClick} and handle the click event * @author Lukas * */ public class ClickPreventableTextView extends TextView implements OnClickListener { private boolean preventClick; private OnClickListener clickListener; private boolean ignoreSpannableClick; public ClickPreventableTextView(Context context) { super(context); } 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) 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); } } 

可点击范围的听众现在看起来像这样

  span.setSpan(new ClickableSpan() { @Override public void onClick(View v) { Log.d("main", "link clicked"); if (widget instanceof ClickPreventableTextView) { if (((ClickPreventableTextView)widget).ignoreSpannableClick()) return; ((ClickPreventableTextView)widget).preventNextClick(); } Toast.makeText(Main.this, "link clicked", Toast.LENGTH_SHORT).show(); } }, 5, 9, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 

对我来说,主要的缺点是,现在getMovementMethod()。onTouchEvent将被调用两次(TextView在它的onTouchEvent方法中调用该方法)。 我不知道这是否有任何副作用,atm它按预期工作。

以非常好的方式解决了非常相似的事情。 我想有文字,有一个可点击的链接! 我希望能够按下文本在哪里没有链接,并有一个点击监听器。 我从grepcode中取出了LinkMovementMethod,并将其改为一个“复制”(Copy),然后复制这个类的底部,它将起作用:

 import android.text.Layout; import android.text.NoCopySpan; import android.text.Selection; import android.text.Spannable; import android.text.method.MovementMethod; import android.text.method.ScrollingMovementMethod; import android.text.style.ClickableSpan; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; public class CustomLinkMovementMethod extends ScrollingMovementMethod { private static final int CLICK = 1; private static final int UP = 2; private static final int DOWN = 3; public abstract interface TextClickedListener { public abstract void onTextClicked(); } TextClickedListener listener = null; public void setOnTextClickListener(TextClickedListener listen){ listener = listen; } @Override public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: case KeyEvent.KEYCODE_ENTER: if (event.getRepeatCount() == 0) { if (action(CLICK, widget, buffer)) { return true; } } } return super.onKeyDown(widget, buffer, keyCode, event); } @Override protected boolean up(TextView widget, Spannable buffer) { if (action(UP, widget, buffer)) { return true; } return super.up(widget, buffer); } @Override protected boolean down(TextView widget, Spannable buffer) { if (action(DOWN, widget, buffer)) { return true; } return super.down(widget, buffer); } @Override protected boolean left(TextView widget, Spannable buffer) { if (action(UP, widget, buffer)) { return true; } return super.left(widget, buffer); } @Override protected boolean right(TextView widget, Spannable buffer) { if (action(DOWN, widget, buffer)) { return true; } return super.right(widget, buffer); } private boolean action(int what, TextView widget, Spannable buffer) { boolean handled = false; Layout layout = widget.getLayout(); int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); int areatop = widget.getScrollY(); int areabot = areatop + widget.getHeight() - padding; int linetop = layout.getLineForVertical(areatop); int linebot = layout.getLineForVertical(areabot); int first = layout.getLineStart(linetop); int last = layout.getLineEnd(linebot); ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class); int a = Selection.getSelectionStart(buffer); int b = Selection.getSelectionEnd(buffer); int selStart = Math.min(a, b); int selEnd = Math.max(a, b); if (selStart < 0) { if (buffer.getSpanStart(FROM_BELOW) >= 0) { selStart = selEnd = buffer.length(); } } if (selStart > last) selStart = selEnd = Integer.MAX_VALUE; if (selEnd < first) selStart = selEnd = -1; switch (what) { case CLICK: if (selStart == selEnd) { return false; } ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class); if (link.length != 1) return false; link[0].onClick(widget); break; case UP: int beststart, bestend; beststart = -1; bestend = -1; for (int i = 0; i < candidates.length; i++) { int end = buffer.getSpanEnd(candidates[i]); if (end < selEnd || selStart == selEnd) { if (end > bestend) { beststart = buffer.getSpanStart(candidates[i]); bestend = end; } } } if (beststart >= 0) { Selection.setSelection(buffer, bestend, beststart); return true; } break; case DOWN: beststart = Integer.MAX_VALUE; bestend = Integer.MAX_VALUE; for (int i = 0; i < candidates.length; i++) { int start = buffer.getSpanStart(candidates[i]); if (start > selStart || selStart == selEnd) { if (start < beststart) { beststart = start; bestend = buffer.getSpanEnd(candidates[i]); } } } if (bestend < Integer.MAX_VALUE) { Selection.setSelection(buffer, beststart, bestend); return true; } break; } return false; } public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { return false; } @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) { link[0].onClick(widget); } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])); } return true; } else { Selection.removeSelection(buffer); if (action == MotionEvent.ACTION_UP) { if(listener != null) listener.onTextClicked(); } } } return super.onTouchEvent(widget, buffer, event); } public void initialize(TextView widget, Spannable text) { Selection.removeSelection(text); text.removeSpan(FROM_BELOW); } public void onTakeFocus(TextView view, Spannable text, int dir) { Selection.removeSelection(text); if ((dir & View.FOCUS_BACKWARD) != 0) { text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT); } else { text.removeSpan(FROM_BELOW); } } public static MovementMethod getInstance() { if (sInstance == null) sInstance = new CustomLinkMovementMethod(); return sInstance; } private static CustomLinkMovementMethod sInstance; private static Object FROM_BELOW = new NoCopySpan.Concrete(); 

}

然后在你的代码中添加文本视图:

  CustomLinkMovementMethod link = (CustomLinkMovementMethod)CustomLinkMovementMethod.getInstance(); link.setOnTextClickListener(new CustomLinkMovementMethod.TextClickedListener() { @Override public void onTextClicked() { Toast.makeText(UserProfileActivity.this, "text Pressed", Toast.LENGTH_LONG).show(); } }); YOUR_TEXTVIEW.setMovementMethod(link); 

该代码适用于我,这是从LinkMovementMethod的源代码

 tv.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { TextView tv = (TextView) v; if (action == MotionEvent.ACTION_UP) { int x = (int) event.getX(); int y = (int) event.getY(); Layout layout = tv.getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); ClickableSpan[] link = h.diary.contentSpan.getSpans(off, off, ClickableSpan.class); if (link.length != 0) { link[0].onClick(tv); } else { //do other click } } return true; } }); 

我认为,这涉及到inheritanceTextView并改变其行为,不幸的是。 你有没有想过试图把背景放在TextView后面,并附上一个onClickListener?