如何设置文本视图的部分是可点击的

我有文本"Android是一个软件堆栈"在这个文本中,我想设置&;__abc1 &;文本为可点击的。所以,如果你点击它,它将重定向到一个新的活动(不在浏览器中)。

我试过了,但没有找到解决办法。

182176 次浏览

您可以使用ClickableSpan,如此帖子

示例代码:

TextView myTextView = new TextView(this);
String myString = "Some text [clickable]";
int i1 = myString.indexOf("[");
int i2 = myString.indexOf("]");
myTextView.setMovementMethod(LinkMovementMethod.getInstance());
myTextView.setText(myString, BufferType.SPANNABLE);
Spannable mySpannable = (Spannable)myTextView.getText();
ClickableSpan myClickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) { /* do something */ }
};
mySpannable.setSpan(myClickableSpan, i1, i2 + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

Reference .

android.text.style.ClickableSpan可以解决你的问题。

SpannableString ss = new SpannableString("Android is a Software stack");
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View textView) {
startActivity(new Intent(MyActivity.this, NextActivity.class));
}
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false);
}
};
ss.setSpan(clickableSpan, 22, 27, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);


TextView textView = (TextView) findViewById(R.id.hello);
textView.setText(ss);
textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setHighlightColor(Color.TRANSPARENT);

在XML:

<TextView
...
android:textColorLink="@drawable/your_selector"
/>
 t= (TextView) findViewById(R.id.PP1);


t.setText(Html.fromHtml("<bThis is normal text </b>" +
"<a href=\"http://www.xyz-zyyx.com\">This is cliclable text</a> "));
t.setMovementMethod(LinkMovementMethod.getInstance());

大胆的,

mySpannable.setSpan(new StyleSpan(Typeface.BOLD),termStart,termStop,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

您可以使用示例代码。您希望了解关于ClickableSpan的详细信息。请检查this documentaion

  SpannableString myString = new SpannableString("This is example");
            

ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View textView) {
ToastUtil.show(getContext(),"Clicked Smile ");
}
};
        

//For Click
myString.setSpan(clickableSpan,startIndex,lastIndex,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        

//For UnderLine
myString.setSpan(new UnderlineSpan(),startIndex,lastIndex,0);
        

//For Bold
myString.setSpan(new StyleSpan(Typeface.BOLD),startIndex,lastIndex,0);
        

//Finally you can set to textView.
        

TextView textView = (TextView) findViewById(R.id.txtSpan);
textView.setText(myString);
textView.setMovementMethod(LinkMovementMethod.getInstance());

我做了这个helper方法,以防有人需要从字符串中开始和结束位置。

public static TextView createLink(TextView targetTextView, String completeString,
String partToClick, ClickableSpan clickableAction) {


SpannableString spannableString = new SpannableString(completeString);


// make sure the String is exist, if it doesn't exist
// it will throw IndexOutOfBoundException
int startPosition = completeString.indexOf(partToClick);
int endPosition = completeString.lastIndexOf(partToClick) + partToClick.length();


spannableString.setSpan(clickableAction, startPosition, endPosition,
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);


targetTextView.setText(spannableString);
targetTextView.setMovementMethod(LinkMovementMethod.getInstance());


return targetTextView;
}

下面是你如何使用它

private void initSignUp() {
String completeString = "New to Reddit? Sign up here.";
String partToClick = "Sign up";
ClickableTextUtil
.createLink(signUpEditText, completeString, partToClick,
new ClickableSpan() {
@Override
public void onClick(View widget) {
// your action
Toast.makeText(activity, "Start Sign up activity",
Toast.LENGTH_SHORT).show();
}


@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
// this is where you set link color, underline, typeface etc.
int linkColor = ContextCompat.getColor(activity, R.color.blumine);
ds.setColor(linkColor);
ds.setUnderlineText(false);
}
});
}

我的函数在TextView中创建多个链接
现在这个函数能够在1 TextView内支持多个相同的文本链接,但请记住将链接按正确的顺序放置

fun TextView.makeLinks(vararg links: Pair<String, View.OnClickListener>) {
val spannableString = SpannableString(this.text)
var startIndexOfLink = -1
for (link in links) {
val clickableSpan = object : ClickableSpan() {
override fun updateDrawState(textPaint: TextPaint) {
// use this to change the link color
textPaint.color = textPaint.linkColor
// toggle below value to enable/disable
// the underline shown below the clickable text
textPaint.isUnderlineText = true
}


override fun onClick(view: View) {
Selection.setSelection((view as TextView).text as Spannable, 0)
view.invalidate()
link.second.onClick(view)
}
}
startIndexOfLink = this.text.toString().indexOf(link.first, startIndexOfLink + 1)
//      if(startIndexOfLink == -1) continue // todo if you want to verify your texts contains links text
spannableString.setSpan(
clickableSpan, startIndexOfLink, startIndexOfLink + link.first.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
this.movementMethod =
LinkMovementMethod.getInstance() // without LinkMovementMethod, link can not click
this.setText(spannableString, TextView.BufferType.SPANNABLE)
}

使用

my_text_view.makeLinks(
Pair("Terms of Service", View.OnClickListener {
Toast.makeText(applicationContext, "Terms of Service Clicked", Toast.LENGTH_SHORT).show()
}),
Pair("Privacy Policy", View.OnClickListener {
Toast.makeText(applicationContext, "Privacy Policy Clicked", Toast.LENGTH_SHORT).show()
}))

XML

<TextView
android:id="@+id/my_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Please accept Terms of Service and Privacy Policy"
android:textColorHighlight="#f00" // background color when pressed
android:textColorLink="#0f0"
android:textSize="20sp" />

演示

参考

清除链接高亮选择的解决方案遵循https://stackoverflow.com/a/19445108/5381331

Phan Van Linh的答案芬兰湾的科特林版本

请注意它有一些小的改动。

fun makeLinks(textView: TextView, links: Array<String>, clickableSpans: Array<ClickableSpan>) {
val spannableString = SpannableString(textView.text)


for (i in links.indices) {
val clickableSpan = clickableSpans[i]
val link = links[i]


val startIndexOfLink = textView.text.indexOf(link)


spannableString.setSpan(clickableSpan, startIndexOfLink, startIndexOfLink + link.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}


textView.movementMethod = LinkMovementMethod.getInstance()
textView.setText(spannableString, TextView.BufferType.SPANNABLE)
}


fun setupClickableTextView() {
val termsOfServicesClick = object : ClickableSpan() {
override fun onClick(p0: View?) {
Toast.makeText(applicationContext, "ToS clicked", Toast.LENGTH_SHORT).show()
}
}


val privacyPolicyClick = object : ClickableSpan() {
override fun onClick(p0: View?) {
Toast.makeText(applicationContext, "PP clicked", Toast.LENGTH_SHORT).show()
}
}


makeLinks(termsTextView, arrayOf("terms", "privacy policy"), arrayOf(termsOfServicesClick, privacyPolicyClick))
}

您可以使用此方法设置可点击的值

public void setClickableString(String clickableValue, String wholeValue, TextView yourTextView){
String value = wholeValue;
SpannableString spannableString = new SpannableString(value);
int startIndex = value.indexOf(clickableValue);
int endIndex = startIndex + clickableValue.length();
spannableString.setSpan(new ClickableSpan() {
@Override
public void updateDrawState(TextPaint ds) {
super.updateDrawState(ds);
ds.setUnderlineText(false); // <-- this will remove automatic underline in set span
}


@Override
public void onClick(View widget) {
// do what you want with clickable value
}
}, startIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
yourTextView.setText(spannableString);
yourTextView.setMovementMethod(LinkMovementMethod.getInstance()); // <-- important, onClick in ClickableSpan won't work without this
}

下面是如何使用它:

TextView myTextView = findViewById(R.id.myTextView);
setClickableString("stack", "Android is a Software stack", myTextView);

下面是一个Kotlin方法,使TextView的部分可点击:

private fun makeTextLink(textView: TextView, str: String, underlined: Boolean, color: Int?, action: (() -> Unit)? = null) {
val spannableString = SpannableString(textView.text)
val textColor = color ?: textView.currentTextColor
val clickableSpan = object : ClickableSpan() {
override fun onClick(textView: View) {
action?.invoke()
}
override fun updateDrawState(drawState: TextPaint) {
super.updateDrawState(drawState)
drawState.isUnderlineText = underlined
drawState.color = textColor
}
}
val index = spannableString.indexOf(str)
spannableString.setSpan(clickableSpan, index, index + str.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = spannableString
textView.movementMethod = LinkMovementMethod.getInstance()
textView.highlightColor = Color.TRANSPARENT
}

它可以被多次调用,在TextView中创建几个链接:

makeTextLink(myTextView, str, false, Color.RED, action = { Log.d("onClick", "link") })
makeTextLink(myTextView, str1, true, null, action = { Log.d("onClick", "link1") })

它对文本的某些部分的可点击部分非常有帮助。

点是正则表达式中的一个特殊字符。如果你想对点进行分隔,需要将点转义为\\。而不是直接将"."传递给可扩展的文本方法。或者,你也可以使用正则表达式[.]在Java中通过一个点来分隔字符串。

这是我的MovementMethod,用于检测链接/文本/图像点击。它被修改为LinkMovementMethod

import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.ScrollingMovementMethod;
import android.text.style.ClickableSpan;
import android.text.style.ImageSpan;
import android.text.style.URLSpan;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;


public class ClickMovementMethod extends ScrollingMovementMethod {
private Object FROM_BELOW = new NoCopySpan.Concrete();


private static final int CLICK = 1;
private static final int UP = 2;
private static final int DOWN = 3;


private Listener listener;


public void setListener(Listener listener) {
this.listener = listener;
}


@Override
public boolean canSelectArbitrarily() {
return true;
}


@Override
protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
int movementMetaState, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
case KeyEvent.KEYCODE_ENTER:
if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
if (event.getAction() == KeyEvent.ACTION_DOWN &&
event.getRepeatCount() == 0 && action(CLICK, widget, buffer)) {
return true;
}
}
break;
}
return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, 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) {
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, URLSpan.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;
}


if (listener != null) {
URLSpan[] link = buffer.getSpans(selStart, selEnd, URLSpan.class);
if (link.length >= 1) {
listener.onClick(link[0].getURL());
} else {
ImageSpan[] image = buffer.getSpans(selStart, selEnd, ImageSpan.class);
if (image.length >= 1) {
listener.onImageClicked(image[0].getSource());
} else {
listener.onTextClicked();
}
}
}
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;
}


@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);


URLSpan[] link = buffer.getSpans(off, off, URLSpan.class);


if (action == MotionEvent.ACTION_UP) {
if (listener != null) {
if (link.length >= 1) {
listener.onClick(link[0].getURL());
} else {
ImageSpan[] image = buffer.getSpans(off, off, ImageSpan.class);
if (image.length >= 1) {
listener.onImageClicked(image[0].getSource());
} else if (Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) {
listener.onTextClicked();
}
}
}
}


if (action == MotionEvent.ACTION_DOWN && link.length != 0) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
return true;
}


if (link.length == 0) {
Selection.removeSelection(buffer);
}
}


return super.onTouchEvent(widget, buffer, event);
}


@Override
public void initialize(TextView widget, Spannable text) {
Selection.removeSelection(text);
text.removeSpan(FROM_BELOW);
}


@Override
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 interface Listener {


void onClick(String clicked);


void onTextClicked();


void onImageClicked(String source);


}


}

我会建议一种不同的方法,我认为这种方法需要更少的代码,而且更“本地化友好”。

假设你的目标活动被称为“ActivityStack”,在manifest中定义一个意图过滤器,使用一个自定义的方案(例如。“myappscheme”)

<activity
android:name=".ActivityStack">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:host="stack"/>
<data android:scheme="myappscheme" />
</intent-filter>
</activity>

定义没有任何特殊标签的TextView(重要的是不要使用"android:autoLink"标签,参见:https://stackoverflow.com/a/20647011/1699702):

<TextView
android:id="@+id/stackView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/stack_string" />

然后在TextView的文本中使用带有自定义方案和主机的链接作为(在String.xml中):

<string name="stack_string">Android is a Software <a href="myappscheme://stack">stack</a></string>

和“激活”与setMovementMethod()(在onCreate()的活动或onCreateView()的片段链接):

TextView stack = findViewById(R.id.stackView);
stack.setMovementMethod(LinkMovementMethod.getInstance());

这将通过点击“stack”字打开堆栈活动。

kotlin中更通用的答案

   fun setClickableText(view: TextView, firstSpan: String, secondSpan: String) {
val context = view.context
val builder = SpannableStringBuilder()
val unClickableSpan = SpannableString(firstSpan)
val span = SpannableString(" "+secondSpan)


builder.append(unClickableSpan);
val clickableSpan: ClickableSpan = object : ClickableSpan() {
override fun onClick(textView: View) {
val intent = Intent(context, HomeActivity::class.java)
context.startActivity(intent)
}


override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.isUnderlineText = true
ds.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.ITALIC));
}
}
builder.append(span);
builder.setSpan(clickableSpan, firstSpan.length, firstSpan.length+secondSpan.length+1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)


view.setText(builder,TextView.BufferType.SPANNABLE)
view.setMovementMethod(LinkMovementMethod.getInstance());




}

提供的解决方案相当不错。然而,我通常使用更简单的解决方案。

这是一个linkify效用函数

/**
* Method is used to Linkify words in a TextView
*
* @param textView TextView who's text you want to change
* @param textToLink The text to turn into a link
* @param url   The url you want to send the user to
*/
fun linkify(textView: TextView, textToLink: String, url: String) {
val pattern = Pattern.compile(textToLink)
Linkify.addLinks(textView, pattern, url, { _, _, _ -> true })
{ _, _ -> "" }
}

使用这个函数非常简单。这里有一个例子

    // terms and privacy
val tvTerms = findViewById<TextView>(R.id.tv_terms)
val tvPrivacy = findViewById<TextView>(R.id.tv_privacy)
Utils.linkify(tvTerms, resources.getString(R.string.terms),
Constants.TERMS_URL)
Utils.linkify(tvPrivacy, resources.getString(R.string.privacy),
Constants.PRIVACY_URL)

我编写了一个例子来解决你的问题在Kotlin。

这是准则:

    val completeText = getString(R.string.terms_description)
val textToFind = getString(R.string.show_terms)
val spannableString: Spannable = SpannableString(completeText)
val startFocus = completeText.indexOf(textToFind)
val endFocus = startFocus + textToFind.length


spannableString.setSpan(object: ClickableSpan() {
override fun onClick(p0: View) {
showMessage()
}
}, startFocus, endFocus, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
show_terms.text = spannableString
show_terms.movementMethod = LinkMovementMethod.getInstance();
show_terms.highlightColor = Color.TRANSPARENT;

这是XML

    <CheckBox
android:id="@+id/check_agree_terms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>


<TextView
android:id="@+id/show_terms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColorLink="@color/colorPrimary"
android:layout_toEndOf="@id/check_agree_terms"/>

这就是它的样子

enter image description here

创建优雅的Kotlin方式扩展:

fun TextView.setClickableText(text: Spanned,
clickableText: String,
@ColorInt clickableColor: Int,
clickListener: () -> Unit) {
val spannableString = SpannableString(text)


val startingPosition: Int = text.indexOf(clickableText)


if (startingPosition > -1) {
val clickableSpan: ClickableSpan = object : ClickableSpan() {
override fun onClick(textView: View) {
clickListener()
}


override fun updateDrawState(textPaint: TextPaint) {
super.updateDrawState(textPaint)
textPaint.isUnderlineText = false
}
}


val endingPosition: Int = startingPosition + clickableText.length
spannableString.setSpan(clickableSpan, startingPosition,
endingPosition, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
spannableString.setSpan(ForegroundColorSpan(clickableColor), startingPosition,
endingPosition, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
movementMethod = LinkMovementMethod.getInstance()
highlightColor = Color.TRANSPARENT
}


setText(spannableString)
}

对于那些在芬兰湾的科特林中寻找解决方案的人来说,这里是对我有用的:

private fun setupTermsAndConditions() {
val termsAndConditions = resources.getString(R.string.terms_and_conditions)
val spannableString = SpannableString(termsAndConditions)
val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
if (checkForWifiAndMobileInternet()) {
// binding.viewModel!!.openTermsAndConditions()
showToast("Good, open the link!!!")


} else {
showToast("Cannot open this file because of internet connection!")
}


}


override fun updateDrawState(textPaint : TextPaint) {
super.updateDrawState(textPaint)
textPaint.color = resources.getColor(R.color.colorGrey)
textPaint.isFakeBoldText = true
}
}


spannableString.setSpan(clickableSpan, 34, 86, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
binding.tvTermsAndConditions.text = spannableString
binding.tvTermsAndConditions.movementMethod = LinkMovementMethod.getInstance()
binding.tvTermsAndConditions.setHighlightColor(Color.TRANSPARENT);


}

Kotlin上复杂但通用的解决方案

  /*
* Receive Pair of Text and Action and set it clickable and appearing as link
* */
fun TextView.setClickableText(vararg textToSpanAndClickAction: Pair<String, (String) -> Unit>) {
val builder = SpannableStringBuilder(text.toString())


textToSpanAndClickAction.forEach { argPair ->
val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
argPair.second.invoke(argPair.first)
}
}


this.text.toString().let { fullText ->
val indexOfFirst = fullText.indexOf(argPair.first)
val indexOfLast = indexOfFirst + argPair.first.length
if (indexOfFirst < 0){
//No match found
return
}else{
builder.setSpan(
clickableSpan,
indexOfFirst,
indexOfLast,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}


this.text = builder
this.movementMethod = LinkMovementMethod.getInstance()
}

Boom Check for java Lovers:D 我们可以根据需要修改:

List<Pair<String, View.OnClickListener>> pairsList = new ArrayList<>();


pairsList.add(new Pair<>("38,50", v -> {
Intent intent = new Intent(SignUpActivity.this, WebActivity.class);
intent.putExtra("which", "tos");
startActivity(intent);
}));


pairsList.add(new Pair<>("81,95", v -> {
Intent intent = new Intent(SignUpActivity.this, WebActivity.class);
intent.putExtra("which", "policy");
startActivity(intent);
}));


makeLinks(pairsList); // Method calling




private void makeLinks(List<Pair<String, View.OnClickListener>> pairsList) {


SpannableString ss = new SpannableString(By signing up, I’m agree to PAKRISM’s Terms of Use and confirms that I have read Privacy Policy);


for (Pair<String, View.OnClickListener> pair : pairsList) {


ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View textView) {
//Toast.makeText(MyApplication.getAppContext(), "Clicked!", Toast.LENGTH_SHORT).show();
pair.second.onClick(textView);
}


@Override
public void updateDrawState(TextPaint ds) {


ds.linkColor = ContextCompat.getColor(SignUpActivity.this, R.color.primary_main);
ds.setUnderlineText(true);


super.updateDrawState(ds);
}
};


String[] indexes = pair.first.split(",");
ss.setSpan(clickableSpan, Integer.parseInt(indexes[0]), Integer.parseInt(indexes[1]), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
        

TextView tv = findViewById(R.id.txtView);
tv.setText(ss);
tv.setMovementMethod(LinkMovementMethod.getInstance());
}

使用URLSpan类获取url

val spans: Array<URLSpan> = result.getSpans(0, result.length, URLSpan::class.java)

方法

fun TextView.createClickable(string: String, action:(String)->Unit ) {
text = HtmlCompat.fromHtml(string, HtmlCompat.FROM_HTML_MODE_LEGACY)
val result = SpannableString(text)
val spans = result.getSpans(0, result.length, URLSpan::class.java)
for (span in spans) {
val link:Pair<String, View.OnClickListener> = Pair(span.url, View.OnClickListener {
action(span.url)
})
val start = result.getSpanStart(span)
val end = result.getSpanEnd(span)
val flags = result.getSpanFlags(span)
result.removeSpan(span)
val clickableSpan: ClickableSpan = object : ClickableSpan() {
override fun onClick(textView: View) {
textView.invalidate()
link.second.onClick(textView)
}
override fun updateDrawState(textPaint: TextPaint) {
super.updateDrawState(textPaint)
textPaint.isUnderlineText = false
}
}
result.setSpan(clickableSpan, start, end, flags)
this.movementMethod = LinkMovementMethod.getInstance()
this.setText(result, TextView.BufferType.SPANNABLE)
}
}

使用

文本示例:Android是一个软件堆栈,它非常棒

包装你的可点击的文本内锚标签

如:Android is a Software <a href='https://example.com/stack'> Stack </a> and it' <a href='https://example.com/awesome'> Awesome </a>.

 val str = "Android is a Software <a href='https://example.com/stack'> Stack </a> and it' <a href='https://example.com/awesome'> Awesome </a>."


textView.createClickable(str) {
when(it) {
"https://example.com/stack"->{
startActivity(Intent(this,StackActivity::class.java))
}
"https://example.com/awesom"->{
startActivity(Intent(this,AwesomeActivity::class.java))
}
}
}

这里有一个Kotlin解决方案,与本地化工作得更好:

data class LinkedText(@StringRes val textRes: Int, val clickListener: View.OnClickListener? = null)


fun TextView.setPartiallyLinkedText(vararg texts: LinkedText) {
this.text = texts.joinToString(" ") { context.getString(it.textRes) }
val spannableString = SpannableString(this.text)
var startIndexOfLink = -1
texts.forEach { text ->
val string = context.getString(text.textRes)
if (text.clickListener != null) {
val clickableSpan = object : ClickableSpan() {
override fun updateDrawState(textPaint: TextPaint) {
textPaint.color = textPaint.linkColor
textPaint.isUnderlineText = true
}
override fun onClick(view: View) {
Selection.setSelection((view as TextView).text as Spannable, 0)
view.invalidate()
text.clickListener.onClick(view)
}
}
startIndexOfLink = this.text.toString().indexOf(string, startIndexOfLink + 1)
spannableString.setSpan(
clickableSpan, startIndexOfLink, startIndexOfLink + string.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
this.movementMethod = LinkMovementMethod.getInstance()
this.setText(spannableString, TextView.BufferType.SPANNABLE)
}

像这样使用它:

textView.setPartiallyLinkedText(
LinkedText(R.string.not_linked_text),
LinkedText(R.string.linked_text) {
Toast.makeText(context, "You clicked", Toast.LENGTH_LONG).show()
},
)

Java解决方案(更新2022年)

特点:

  • 允许多个点击时,有重复的词。
  • 可以为每个重复的单词量身定制特定的命令。

我以daler445的代码为基础,允许对重复的单词使用多个可单击的命令。

在Java课上:

public class MainActivity extends AppCompatActivity {
SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);


sp = getSharedPreferences("MyUserPrefs", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();


TextView fulltext = (TextView) findViewById(R.id.fulltext);


//replace setText("") to setText("Android is a Software stack") for his case
fulltext.setText("Search [1] this full [1] text with repeated strings. Search [2] Search [3] full [2] full [3]");


List<Pair<String, View.OnClickListener>> links = new ArrayList<>();


//replace "Search" with "stack" for his case
String stringtohyperlink = "Search";
String stringtohyperlink2 = "full";


links.add(new Pair<>(stringtohyperlink, new View.OnClickListener() {
@Override
public void onClick(View v) {
String position = sp.getString("position","0");
if (position.equals("1")) {
Toast.makeText(MainActivity.this, "Search 1 has been Clicked.", Toast.LENGTH_SHORT).show();
editor.putString("position","0");
editor.apply();
}
if (position.equals("2")) {
Toast.makeText(MainActivity.this, "Search 2 has been Clicked.", Toast.LENGTH_SHORT).show();
editor.putString("position","0");
editor.apply();
}
if (position.equals("3")) {
Toast.makeText(MainActivity.this, "Search 3 has been Clicked.", Toast.LENGTH_SHORT).show();
editor.putString("position","0");
editor.apply();
}
}
}));


links.add(new Pair<>(stringtohyperlink2, new View.OnClickListener() {
@Override
public void onClick(View v) {
String position = sp.getString("position","0");
if (position.equals("1")) {
Toast.makeText(MainActivity.this, "full 1 has been Clicked.", Toast.LENGTH_SHORT).show();
editor.putString("position","0");
editor.apply();
}
if (position.equals("2")) {
Toast.makeText(MainActivity.this, "full 2 has been Clicked.", Toast.LENGTH_SHORT).show();
editor.putString("position","0");
editor.apply();
}
if (position.equals("3")) {
Toast.makeText(MainActivity.this, "full 3 has been Clicked.", Toast.LENGTH_SHORT).show();
editor.putString("position","0");
editor.apply();
}
}
}));




makeLinks(fulltext, links);




}


public void makeLinks(TextView textView, List<Pair<String, View.OnClickListener>> links) {
SpannableString spannableString = new SpannableString(textView.getText().toString());


int startIndexState = -1;
SharedPreferences.Editor editor = sp.edit();


for (Pair<String, View.OnClickListener> link : links) {
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
editor.putString("position","1");
editor.apply();


widget.invalidate();
assert link.second != null;
link.second.onClick(widget);
}
};
ClickableSpan clickableSpan2 = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
editor.putString("position","2");
editor.apply();


widget.invalidate();
assert link.second != null;
link.second.onClick(widget);
}
};


ClickableSpan clickableSpan3 = new ClickableSpan() {
@Override
public void onClick(@NonNull View widget) {
editor.putString("position","3");
editor.apply();


widget.invalidate();
assert link.second != null;
link.second.onClick(widget);
}
};
//... This only allows for 3 repeated words
//... Add more of it, if there are more repeated words.


assert link.first != null;
int startIndexOfLink = textView.getText().toString().indexOf(link.first, startIndexState + 1);
spannableString.setSpan(clickableSpan, startIndexOfLink, startIndexOfLink + link.first.length(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);


int startIndexOfLink2 = textView.getText().toString().indexOf(link.first, startIndexOfLink + 1);
if (startIndexOfLink2 != -1) {
spannableString.setSpan(clickableSpan2, startIndexOfLink2, startIndexOfLink2 + link.first.length(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}


int startIndexOfLink3 = textView.getText().toString().indexOf(link.first, startIndexOfLink2 + 1);
if (startIndexOfLink3 != -1) {
spannableString.setSpan(clickableSpan3, startIndexOfLink3, startIndexOfLink3 + link.first.length(), Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
}
//... This only allows for 3 repeated words
//... Add more of it, if there are more repeated words.


textView.setMovementMethod(LinkMovementMethod.getInstance());
textView.setText(spannableString, TextView.BufferType.SPANNABLE);
}
}
}

在xml

  <TextView
android:id="@+id/fulltext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

演示

enter image description here

这是一个kotlin扩展(不工作,如果文本重复)

textView.makeTextClickable(
contentText = "Hello world",
clickableText = "world",
isBoldText = true,
onClick = {
openPageInBrowser(BuildConfig.PRIVACY_POLICY_URL)
}
)






internal fun TextView.makeTextClickable(
contentText: String,
clickableText: String,
onClick: () -> Unit,
isUnderlineText: Boolean = false,
isBoldText: Boolean = false
) {
val spannableString = SpannableString(contentText)


val clickableSpan = object : ClickableSpan() {
override fun onClick(widget: View) {
onClick()
}


override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.isUnderlineText = isUnderlineText
ds.isFakeBoldText = isBoldText
}
}


val clickableTextStartIndex = text.indexOf(clickableText)
val clickableTextEndIndex = clickableTextStartIndex + clickableText.length


spannableString.setSpan(
clickableSpan,
clickableTextStartIndex,
clickableTextEndIndex,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)


this.text = spannableString
this.movementMethod = LinkMovementMethod.getInstance()
}

对于kotlin使用这个扩展

fun TextView.makeLinks(vararg links: Pair<String, View.OnClickListener>) {
val spannableString = SpannableString(this.text)
for (link in links) {
val clickableSpan = object : ClickableSpan() {
override fun onClick(view: View) {
Selection.setSelection((view as TextView).text as Spannable, 0)
view.invalidate()
link.second.onClick(view)
}
}
val startIndexOfLink = this.text.toString().indexOf(link.first)
spannableString.setSpan(
clickableSpan, startIndexOfLink, startIndexOfLink + link.first.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
spannableString.setSpan(
ForegroundColorSpan(Color.parseColor("#46C2CC")),
startIndexOfLink,
startIndexOfLink + link.first.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
this.movementMethod =
LinkMovementMethod.getInstance() // without LinkMovementMethod, link can not click
this.setText(spannableString, TextView.BufferType.SPANNABLE)

像这样调用它

binding.agreeText.makeLinks(Pair(getString(R.string.terms_conditionsClick),View.OnClickListener {
startActivity(TermsAndConditionActivity.getIntent(this))
}))