如何设置文本视图中的某一部分可点击

290
我有一段文字:“Android 是一个软件堆栈”。在这段文字中,我想将“堆栈”设置为可点击的文本。因此,如果您单击该文本,它将会重定向到一个新活动(不是浏览器中打开)。 我尝试了但是没有得到解决方案。

19
我尝试了,但是没有成功。我想知道你尝试了什么,以及在哪里失败了。 - Rashmi.B
27个回答

643

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"
/>

2
你能在TextView中设置多个ClickableSpan对象吗? - Jono
4
可以设置多个可点击的 Span 到可跨度字符串中。 - degratnik
20
为了将颜色改变为蓝色,您可以添加以下代码: ss.setSpan(fcs, 22, 27, Spannable.SPAN_INCLUSIVE_INCLUSIVE);``` - xemacobra
5
在我的情况下,我想把颜色从蓝色改为其他颜色。在设置可点击区域之后,使用setSpan中的ForegroundColorSpan来设置前景色。如果将前景色放在可点击区域之前,则新的颜色不会反映出来。 - Shubham A.
3
感谢这行代码:stextView.setMovementMethod(LinkMovementMethod.getInstance()); textView.setHighlightColor(Color.TRANSPARENT);它的作用是让textView支持超链接,并去掉超链接点击后的背景高亮色。 - Sasuke Uchiha
显示剩余12条评论

204

我的函数用于在TextView中创建多个链接
更新于2020年:现在这个函数能够支持在一个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://dev59.com/lm035IYBdhLWcg3wJcjT#19445108


1
它完美地工作着。但是有一些奇怪的行为正在显示出来。我有一个以“Policy.”结尾的文本。在可点击的Spannable中,"."将被跳过。有人遇到过同样的问题吗?只有当它是文本视图的结束字符时,“.”才会被跳过。 - Nikhil
1
很棒的解决方案,但如果存在多语言应用程序的本地化字符串,就需要有人来负责。 - Himanshu Mori
1
@BlackBlind 你遇到了哪个错误?如果是导入的问题,请正确导入。另外,你不应该把函数放在onCreate里面,而是放在外面。 - Linh
1
如果存在重复的链接,此解决方案将始终返回第一个链接,因为它使用了 indexOf - Duc Trung Mai
1
谢谢!我的Java等效代码:https://gist.github.com/daler445/b7106b5c5ed431ecfb206afe98746bbe - Daler
显示剩余11条评论

42

您可以像这个帖子中所述那样使用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);

参考资料


谢谢@ImranRana,点赞startIndexendIndex的逻辑。 - Ravi Vaniya
使用[]来计算起始索引和结束索引是最完美的方式,但也会在文本中显示[]。我不想在文本中显示[]。 - Dharmishtha
@Imran Rana 我有一个类似的用例,但需要点击EditText行的提示,这里是:https://stackoverflow.com/questions/74750234/android-launch-popupmenu-from-click-touch-only-on-the-hint-shown-on-an-edittext。你有什么解决方法吗? - AJW

20

我创建了这个辅助方法,以便某些人可以从字符串中获取起始位置和结束位置。

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

2
这样做更好,因为它考虑了本地化。 - hiddeneyes02
1
@aldok 我有一个类似的用例,但需要点击EditText行的提示,这里是:https://stackoverflow.com/questions/74750234/android-launch-popupmenu-from-click-touch-only-on-the-hint-shown-on-an-edittext。你有什么解决方法吗? - AJW

19

您可以使用示例代码。如果您想了解有关ClickableSpan的详细信息,请查看文档

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

13

这里是一个 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") })

8
 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());

3
你能解释一下你的答案吗?带有解释的答案通常更好。我还修改了你的代码片段,因为它并不完全符合正确的格式。 - Popeye
3
看起来这个链接是一个网页链接,而不是原帖中所请求的活动。 - William T. Mallard
这是针对超链接而非活动的。类似的方法是否可用于活动/片段? - Vishnu

6
我建议使用一种不同的方法,它需要更少的代码并且更加“本地化友好”。假设你的目标活动被称为“ActivityStack”,则在AndroidManifest.xml中定义一个意图过滤器,该过滤器使用自定义方案(例如“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://dev59.com/6nE85IYBdhLWcg3whT5r#20647011):

<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" 单词即可打开堆栈活动。


4

Phan Van Linh的答案的Kotlin版本。

请注意,它有一些小修改。

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))
}

4
您可以使用这种方法来设置可点击值。
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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接