[英]Android: ClickableSpan in clickable TextView
我有一個可以包含可點擊鏈接的文本視圖。 單擊此鏈接之一時,我想開始一項活動。 這工作正常,但也應該可以單擊整個文本視圖並啟動另一個活動。
所以這就是我目前的解決方案:
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 時,每次我點擊一個鏈接時,首先會調用整個 textview 的偵聽器,然后調用 ClickableSpan 的偵聽器。
單擊鏈接時,有沒有辦法防止android調用整個textview的偵聽器? 或者在整個視圖的偵聽器中決定是否單擊了鏈接?
找到了一個非常簡單的解決方法。 在所有不屬於鏈接的文本區域上定義 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());
希望這會有所幫助(我知道這個帖子很舊,但以防萬一現在有人看到它......)。
這是一個非常簡單的解決方案..這對我有用
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
}
}
});
馬修建議子類化 TextView 並提出了一個相當丑陋的解決方法。 但它有效:
我已經創建了一個“ClickPreventableTextView”,當我在 TextView 中有可點擊的跨度時使用它,它應該作為一個整體可點擊。
在它的 onTouchEvent 方法中,這個類在調用它的基本 TextView 類上的 onTouchEvent 之前調用 MovementMethod 的 onTouchEvent 方法。 所以可以保證,clickablespan 的 Listener 將首先被調用。 我可以防止為整個 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 它按預期工作。
該代碼對我有用,來自 LinkMovementMethod 的源代碼
tv.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
TextView tv = (TextView) v;
if (event.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 = contentSpan.getSpans(off, off, ClickableSpan.class);
if (link.length != 0) {
link[0].onClick(tv);
} else {
//do other click
}
}
return true;
}
});
以非常好的方式解決了非常相似的問題。 我想要一個帶有可點擊鏈接的文本!! 我希望能夠按下沒有鏈接的文本,並在其中有一個點擊監聽器。 我從 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);
不幸的是,我認為這涉及繼承 TextView 並改變其行為。 你有沒有想過嘗試在 TextView 后面放置一個背景並附加一個 onClickListener 到它?
復制下面的功能
private fun setClickableHighLightedText(
tv: TextView,
textToHighlight: String,
onClickListener: View.OnClickListener?
) {
val tvt = tv.text.toString()
var ofe = tvt.indexOf(textToHighlight, 0)
val clickableSpan = object : ClickableSpan() {
override fun onClick(textView: View) {
onClickListener?.onClick(textView)
}
override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
//set color of the text
ds.color = getColor(R.color.black)
//draw underline base on true/false
ds.isUnderlineText = false
}
}
val wordToSpan = SpannableString(tv.text)
var ofs = 0
while (ofs < tvt.length && ofe != -1) {
ofe = tvt.indexOf(textToHighlight, ofs)
if (ofe == -1)
break
else {
wordToSpan.setSpan(
clickableSpan,
ofe,
ofe + textToHighlight.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
tv.setText(wordToSpan, TextView.BufferType.SPANNABLE)
tv.movementMethod = LinkMovementMethod.getInstance()
}
ofs = ofe + 1
}
}
使用上面的函數並傳遞 textview,clickble 字符串
setClickableHighLightedText(tvTest,"test") {
showMessage("click")
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.