Related
I have the text "Android is a Software stack". In this text i want to set the "stack" text as clickable. So, if you click on that it will redirected to a new activity(not in the browser).
I tried but i am not getting a solution.
android.text.style.ClickableSpan can solve your problem.
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);
In XML:
<TextView
...
android:textColorLink="#drawable/your_selector"
/>
My function for make multiple links inside TextView
Update 2020: Now this function able to support multiple same texts link inside 1 TextView, but remember to put the link in the correct order
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)
}
USING
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" />
DEMO
Reference
Solution for clear the link highlight selection follow https://stackoverflow.com/a/19445108/5381331
You can use ClickableSpan as described in this post
Sample code:
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
You can use sample code. You want to learn detail about ClickableSpan. Please check 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());
I made this helper method in case someone need start and end position from a String.
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;
}
And here is how you use it
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);
}
});
}
Here is a Kotlin method to make parts of a TextView clickable:
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
}
It can be called multiple times to create several links within a TextView:
makeTextLink(myTextView, str, false, Color.RED, action = { Log.d("onClick", "link") })
makeTextLink(myTextView, str1, true, null, action = { Log.d("onClick", "link1") })
t= (TextView) findViewById(R.id.PP1);
t.setText(Html.fromHtml("<bThis is normal text </b>" +
"This is cliclable text "));
t.setMovementMethod(LinkMovementMethod.getInstance());
I would suggest a different approach that I think requires less code and is more "localization-friendly".
Supposing that your destination activity is called "ActivityStack", define in the manifest an intent filter for it with a custom scheme (e.g. "myappscheme") in AndroidManifest.xml:
<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>
Define the TextView without any special tag (it is important to NOT use the "android:autoLink" tag, see: 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" />
then use a link with custom scheme and host in the text of the TextView as (in String.xml):
<string name="stack_string">Android is a Software stack</string>
and "activate" the link with setMovementMethod() (in onCreate() for activities or onCreateView() for fragments):
TextView stack = findViewById(R.id.stackView);
stack.setMovementMethod(LinkMovementMethod.getInstance());
This will open the stack activity with a tap on the "stack" word.
Kotlin Version of Phan Van Linh's answer.
Please note it has some minor modifications.
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))
}
You can you this method to set the clickable value
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
}
This is how to use it:
TextView myTextView = findViewById(R.id.myTextView);
setClickableString("stack", "Android is a Software stack", myTextView);
For those that are looking for a solution in Kotlin here is what worked for me:
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);
}
Created elegant Kotlin way with extension:
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)
}
Boom Check this for java Lovers :D
We can modify it according to our need:
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());
}
For kotlin use this extension
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)
}
Call this like this
binding.agreeText.makeLinks(Pair(getString(R.string.terms_conditionsClick),View.OnClickListener {
startActivity(TermsAndConditionActivity.getIntent(this))
}))
For bold,
mySpannable.setSpan(new StyleSpan(Typeface.BOLD),termStart,termStop,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
i coded an example to solve your question in Kotlin.
This is the Code:
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;
This is the 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"/>
This is how it looks
enter image description here
Using URLSpan class to get urls
val spans: Array<URLSpan> = result.getSpans(0, result.length, URLSpan::class.java)
Method
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)
}
}
Use
Example Text : Android is a Software Stack and it' Awesome
Wrapper your clickable text inside anchor tag
Like: 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))
}
}
}
Here a Kotlin solution that work better with localization:
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)
}
And use it like this:
textView.setPartiallyLinkedText(
LinkedText(R.string.not_linked_text),
LinkedText(R.string.linked_text) {
Toast.makeText(context, "You clicked", Toast.LENGTH_LONG).show()
},
)
Here is a kotlin extension (not works if text repeats)
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()
}
This is my MovementMethod for detecting link/text/image clicks. It is modified 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);
}
}
more generic answer in 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());
}
The solutions provided are pretty decent. However, I generally use a more simple solution.
Here is a linkify utility function
/**
* 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 })
{ _, _ -> "" }
}
Using this function is pretty simple. Here is an example
// 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)
Complicated but universal solution on 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()
}
kotlin spannable
Solution in Java (Updated 2022)
Features:
Allows for multiple clickable when there are repeated words.
Specific commands can be tailored for each repeated words.
I built upon daler445's code to allow for multiple clickable commands for repeated words.
At Java class:
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);
}
}
}
At .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" />
Demo
It really helpful for the clickable part for some portion of the text.
The dot is a special character in the regular expression. If you want to spanable the dot need to escape dot as \\. instead of just passing "." to the spanable text method. Alternatively, you can also use the regular expression [.] to spanable the String by a dot in Java.
I am developing an emoji keyboard for android but don't know how to add a animated emoji in currentInputConnection of InputMethodService.
Edittext content= findVie......
sb = new SpannableStringBuilder();
String dummyText = "-";
sb.append(dummyText);
try {
sb.setSpan(anim = new AnimatedImageSpan(new AnimatedGifDrawable(
getAssets().open("54.gif"),
new AnimatedGifDrawable.UpdateListener() {
#Override
public void update() {
content.requestLayout();
content.invalidate();
}
})), sb.length() - dummyText.length(), sb.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
content.setText(sb);
here content is an editText view but I don't have any edit text view. All I have is currentInputConnection return by InputMethodService.getCurrentInputConnection().
If I understand you correctly, you're trying to send a "animated image" (sth like a gif) to EditText right? As far as I know, that may be impossible. If you have read the documentation of InputConnection, you should know that it doesn't provide any API to do this.
Actually I'm working on an Android IME project in which we implement an static Emoji input keyboard. All we do is to simply send the encoded bytes of emoji through InputConnection then the smiling face appears in the edit box.
If you have used WeChat, you may notice that its client has implemented a custom emoji keyboard(including static and dynamic content). That's because they know exactly what they're doing.
I am also trying to do the same thing. then i got my answer that .
If you want to implement within your app and sent message to your own app.
Then you can map each image pattern to some value.
////////
public class ViewsUtils {
private static final Map<Pattern, Integer> emoticons = new HashMap<Pattern, Integer>();
static {
addPattern(emoticons, "\ud83d\udeb6", R.drawable.emot_d83ddeb6);
...
}
private static void addPattern(Map<Pattern, Integer> map, String smile,
int resource) {
map.put(Pattern.compile(Pattern.quote(smile)), resource);
}
public static boolean addSmiles(Context context, Spannable spannable) {
boolean hasChanges = false;
for (Entry<Pattern, Integer> entry : emoticons.entrySet()) {
Matcher matcher = entry.getKey().matcher(spannable);
while (matcher.find()) {
boolean set = true;
for (ImageSpan span : spannable.getSpans(matcher.start(),
matcher.end(), ImageSpan.class))
if (spannable.getSpanStart(span) >= matcher.start()
&& spannable.getSpanEnd(span) <= matcher.end())
spannable.removeSpan(span);
else {
set = false;
break;
}
if (set) {
hasChanges = true;
spannable.setSpan(new ImageSpan(context, entry.getValue()),
matcher.start(), matcher.end(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
return hasChanges;
}
public static void setText(TextView view, String text) {
if (null != view && null != text) {
Spanned spanned = Html.fromHtml(text);
SpannableString spannableString = SpannableString.valueOf(spanned);
addSmiles(view.getContext(), spannableString);
view.setText(spannableString);
}
}
public static void setText(View parent, int viewId, int resId) {
if (null != parent) {
String text = parent.getContext().getString(resId);
setText(parent, viewId, text);
}
}
public static void setText(View parent, int viewId, String text) {
if (null != parent) {
TextView view = (TextView) parent.findViewById(viewId);
if (null != view && null != text) {
Spanned spanned = Html.fromHtml(text);
SpannableString spannableString = SpannableString.valueOf(spanned);
addSmiles(view.getContext(), spannableString);
view.setText(spannableString);
}
}
}
public static void setText(View parent, int viewId, String text,
int visibility) {
if (null != parent) {
TextView view = (TextView) parent.findViewById(viewId);
if (null != view && null != text) {
Spanned spanned = Html.fromHtml(text);
SpannableString spannableString = SpannableString.valueOf(spanned);
addSmiles(view.getContext(), spannableString);
view.setText(spannableString);
view.setVisibility(visibility);
}
}
}
}
You just have to add map entries for the emote icons you want to handle in the static block. Using this class is pretty easy after that - just call some of the setText (or call addSmiles directly) methods. It would handle Html parsing too.
If i select a particular text in a text box (Edit Text), i want to make bold, italic, underline, change font, change font color of that selected text alone.
Eg:
Text want to be formatted segmented
if I select "matt" in the word formatted and try to make unbold, the result will be like this
Text want to be for matted segmented
Code:
public class RichTextEditor extends EditText
{
Context context;
boolean isBold = false;
boolean isItalic = false;
boolean isUnderline = false;
int fontSize = 6;
int fontColor = Color.BLACK;
TextView firstT, middleT, lastT;
SpannableStringBuilder res = new SpannableStringBuilder();
StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
StyleSpan italicSpan = new StyleSpan(Typeface.ITALIC);
StyleSpan BoldItalicSpan
= new StyleSpan(Typeface.BOLD_ITALIC);
public void setAllT (TextView firstT, TextView middleT, TextView lastT)
{
this.firstT = firstT;
this.middleT = middleT;
this.lastT = lastT;
}
public void makeBold ()
{
SpannableString text = new SpannableString(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start == end)
{
isBold = true;
}
else
{
SpannableString before = (SpannableString) text.subSequence(0,
start);
System.out.println("First : " + before);
SpannableString middle = (SpannableString) text.subSequence(start,
end);
System.out.println("Middle : " + middle);
SpannableString after = (SpannableString) text.subSequence(end,
text.length());
System.out.println("Last : " + after);
StyleSpan ref = isBetween(text, Typeface.BOLD);
if (ref != null)
{
middle.removeSpan(ref);
System.out.println("Removing Bold...");
setText(before);
append(middle);
append(new SpannableString(" "));
append(after);
}
else
{
middle.setSpan(getBoldStyleSpan(), 0, middle.length(), 0);
setText(before);
append(middle);
append(after);
}
CharSequence now = TextUtils.concat(before, > middle, after);
SpannableString ne = new SpannableString(now);
}
}
public void makeItalic ()
{
SpannableString text = new SpannableString(getText());
res = new SpannableStringBuilder(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start == end)
{
isBold = true;
}
else
{
SpannableString before = (SpannableString) text.subSequence(0,
start);
SpannableString middle = (SpannableString) text.subSequence(start, end);
SpannableString after = (SpannableString) text.subSequence(end,
text.length());
StyleSpan ref = isBetween(text, Typeface.ITALIC);
if (ref != null)
{
middle.removeSpan(getBoldStyleSpan());
System.out.println("Removing...");
}
else
{
middle.setSpan(getItalicStyleSpan(), 0, middle.length(), > 0);
}
setText(before);
append(middle);
append(after);
}
}
public void changeFontSize () {} // TODO Auto-generated method stub
public void changeFont () {} // TODO Auto-generated method stub
public void changeFontColor () {} // TODO Auto-generated method stub
private StyleSpan isBetween (SpannableString text, StyleSpan toCheck){
//return span if it is between the param tocheck span,else null
}
private StyleSpan getBoldStyleSpan () {
return new StyleSpan(Typeface.BOLD);
}
private StyleSpan getItalicStyleSpan () {
return new StyleSpan(Typeface.ITALIC);
}
}
public RichTextEditor (Context context, AttributeSet attr)
{
super(context, attr);
this.context = context;>mStyles = new ArrayList<RichTextEditor.PropertyHolder>();
}
Suppose I have the next text :
Hello stackOverflow
And I wish to set the second word to be both RelativeSizeSpan (to set a relative font size) and TextAppearanceSpan (to set the color of the text) , how do I merge them both ?
All I know is that I can choose one of them , using the next code for example :
final SpannableString textToShow = new SpannableString("Hello stackOverflow");
textToShow.setSpan(new RelativeSizeSpan(1.5f), textToShow.length() - "stackOverflow".length(),textToShow.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(textToShow);
But I need to also set the color , or even add other features from other spanning classes ...
What can I do ?
Simply set additional spans. They are going to overlap/merge when neccessary. This code works for me:
final SpannableString text = new SpannableString("Hello stackOverflow");
text.setSpan(new RelativeSizeSpan(1.5f), text.length() - "stackOverflow".length(), text.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
text.setSpan(new ForegroundColorSpan(Color.RED), 3, text.length() - 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(text);
I know this a new reply to an already answered question but I'd like to share a utility class I made which makes this task easier.
Java Version
public class SimpleSpanBuilder {
private class SpanSection{
private final String text;
private final int startIndex;
private final CharacterStyle[] styles;
private SpanSection(String text, int startIndex,CharacterStyle... styles){
this.styles = styles;
this.text = text;
this.startIndex = startIndex;
}
private void apply(SpannableStringBuilder spanStringBuilder){
if (spanStringBuilder == null) return;
for (CharacterStyle style : styles){
spanStringBuilder.setSpan(style, startIndex, startIndex + text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
}
}
}
private List<SpanSection> spanSections;
private StringBuilder stringBuilder;
public SimpleSpanBuilder(){
stringBuilder = new StringBuilder();
spanSections = new ArrayList<>();
}
public SimpleSpanBuilder append(String text,CharacterStyle... styles){
if (styles != null && styles.length > 0) {
spanSections.add(new SpanSection(text, stringBuilder.length(),styles));
}
stringBuilder.append(text);
return this;
}
public SimpleSpanBuilder appendWithSpace(String text,CharacterStyle... styles){
return append(text.concat(" "),styles);
}
public SimpleSpanBuilder appendWithLineBreak(String text,CharacterStyle... styles){
return append(text.concat("\n"),styles);
}
public SpannableStringBuilder build(){
SpannableStringBuilder ssb = new SpannableStringBuilder(stringBuilder.toString());
for (SpanSection section : spanSections){
section.apply(ssb);
}
return ssb;
}
#Override
public String toString() {
return stringBuilder.toString();
}
}
Usage:
SimpleSpanBuilder ssb = new SimpleSpanBuilder();
ssb.appendWithSpace("Hello");
ssb.append("StackOverflow",new ForegroundColorSpan(Color.RED),new RelativeSizeSpan(1.5));
textView.setText(ssb.build());
Kotlin Version
class SimpleSpanBuilder() {
class Span {
private var startIndex: Int = 0
internal var text: String
private var styles: Array<out CharacterStyle>
internal constructor(index: Int, text: String, vararg styles: CharacterStyle) {
this.startIndex = index
this.text = text
this.styles = styles
}
constructor(text: String, vararg styles: CharacterStyle) : this(0, text, *styles)
internal fun setIndex(index: Int): Span {
return Span(index, this.text, *this.styles)
}
internal fun apply(spanStringBuilder: SpannableStringBuilder?) {
if (spanStringBuilder == null) return
for (style in styles) {
spanStringBuilder.setSpan(
style,
startIndex,
startIndex + text.length,
Spannable.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}
}
private val spanSections = mutableListOf<Span>()
private val stringBuilder = StringBuilder()
constructor(text: String, vararg styles: CharacterStyle) : this() {
plus(Span(text, *styles))
}
operator fun plus(span: SimpleSpanBuilder.Span): SimpleSpanBuilder {
spanSections.add(span.setIndex(stringBuilder.length))
stringBuilder.append(span.text)
return this
}
fun build(): SpannableStringBuilder {
val ssb = SpannableStringBuilder(stringBuilder.toString())
for (section in spanSections) {
section.apply(ssb)
}
return ssb
}
override fun toString(): String {
return stringBuilder.toString()
}
}
Usage
var ssb = SimpleSpanBuilder("Hello ",ForegroundColorSpan(Color.BLUE))
ssb += SimpleSpanBuilder.Span(
"StackOverflow",
ForegroundColorSpan(Color.RED),
RelativeSizeSpan(1.5f)
)
textView.text = ssb.build()
Most Easy Way?
textView.setText("I love coding");
setHighLightedText(textView,"coding");
Just use below method -
public void setHighLightedText(TextView tv, String textToHighlight) {
String tvt = tv.getText().toString();
int ofe = tvt.indexOf(textToHighlight, 0);
Spannable wordToSpan = new SpannableString(tv.getText());
for (int ofs = 0; ofs < tvt.length() && ofe != -1; ofs = ofe + 1) {
ofe = tvt.indexOf(textToHighlight, ofs);
if (ofe == -1)
break;
else {
// you can change or add more span as per your need
wordToSpan.setSpan(new RelativeSizeSpan(2f), ofe,ofe + textToHighlight.length(), 0); // set size
wordToSpan.setSpan(new ForegroundColorSpan(Color.RED), ofe, ofe + textToHighlight.length(), 0);// set color
tv.setText(wordToSpan, TextView.BufferType.SPANNABLE);
}
}
}
Kotlin can help doing this with an extension on SpannableStringBuilder:
fun SpannableStringBuilder.spansAppend(
text: CharSequence,
flags: Int,
vararg spans: Any
): SpannableStringBuilder {
val start = length
append(text)
spans.forEach { span ->
setSpan(span, start, length, flags)
}
return this
}
Examples of usage:
val builder = SpannableStringBuilder()
builder.append("Start of string ")
builder.spansAppend(
"text spanned",
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE,
RelativeSizeSpan(1.1f),
ForegroundColorSpan(Color.RED)
)
I have the text "Android is a Software stack". In this text i want to set the "stack" text as clickable. So, if you click on that it will redirected to a new activity(not in the browser).
I tried but i am not getting a solution.
android.text.style.ClickableSpan can solve your problem.
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);
In XML:
<TextView
...
android:textColorLink="#drawable/your_selector"
/>
My function for make multiple links inside TextView
Update 2020: Now this function able to support multiple same texts link inside 1 TextView, but remember to put the link in the correct order
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)
}
USING
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" />
DEMO
Reference
Solution for clear the link highlight selection follow https://stackoverflow.com/a/19445108/5381331
You can use ClickableSpan as described in this post
Sample code:
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
You can use sample code. You want to learn detail about ClickableSpan. Please check 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());
I made this helper method in case someone need start and end position from a String.
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;
}
And here is how you use it
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);
}
});
}
Here is a Kotlin method to make parts of a TextView clickable:
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
}
It can be called multiple times to create several links within a TextView:
makeTextLink(myTextView, str, false, Color.RED, action = { Log.d("onClick", "link") })
makeTextLink(myTextView, str1, true, null, action = { Log.d("onClick", "link1") })
t= (TextView) findViewById(R.id.PP1);
t.setText(Html.fromHtml("<bThis is normal text </b>" +
"This is cliclable text "));
t.setMovementMethod(LinkMovementMethod.getInstance());
I would suggest a different approach that I think requires less code and is more "localization-friendly".
Supposing that your destination activity is called "ActivityStack", define in the manifest an intent filter for it with a custom scheme (e.g. "myappscheme") in AndroidManifest.xml:
<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>
Define the TextView without any special tag (it is important to NOT use the "android:autoLink" tag, see: 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" />
then use a link with custom scheme and host in the text of the TextView as (in String.xml):
<string name="stack_string">Android is a Software stack</string>
and "activate" the link with setMovementMethod() (in onCreate() for activities or onCreateView() for fragments):
TextView stack = findViewById(R.id.stackView);
stack.setMovementMethod(LinkMovementMethod.getInstance());
This will open the stack activity with a tap on the "stack" word.
Kotlin Version of Phan Van Linh's answer.
Please note it has some minor modifications.
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))
}
You can you this method to set the clickable value
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
}
This is how to use it:
TextView myTextView = findViewById(R.id.myTextView);
setClickableString("stack", "Android is a Software stack", myTextView);
For those that are looking for a solution in Kotlin here is what worked for me:
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);
}
Created elegant Kotlin way with extension:
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)
}
Boom Check this for java Lovers :D
We can modify it according to our need:
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());
}
For kotlin use this extension
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)
}
Call this like this
binding.agreeText.makeLinks(Pair(getString(R.string.terms_conditionsClick),View.OnClickListener {
startActivity(TermsAndConditionActivity.getIntent(this))
}))
For bold,
mySpannable.setSpan(new StyleSpan(Typeface.BOLD),termStart,termStop,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
i coded an example to solve your question in Kotlin.
This is the Code:
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;
This is the 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"/>
This is how it looks
enter image description here
Using URLSpan class to get urls
val spans: Array<URLSpan> = result.getSpans(0, result.length, URLSpan::class.java)
Method
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)
}
}
Use
Example Text : Android is a Software Stack and it' Awesome
Wrapper your clickable text inside anchor tag
Like: 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))
}
}
}
Here a Kotlin solution that work better with localization:
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)
}
And use it like this:
textView.setPartiallyLinkedText(
LinkedText(R.string.not_linked_text),
LinkedText(R.string.linked_text) {
Toast.makeText(context, "You clicked", Toast.LENGTH_LONG).show()
},
)
Here is a kotlin extension (not works if text repeats)
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()
}
This is my MovementMethod for detecting link/text/image clicks. It is modified 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);
}
}
more generic answer in 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());
}
The solutions provided are pretty decent. However, I generally use a more simple solution.
Here is a linkify utility function
/**
* 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 })
{ _, _ -> "" }
}
Using this function is pretty simple. Here is an example
// 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)
Complicated but universal solution on 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()
}
kotlin spannable
Solution in Java (Updated 2022)
Features:
Allows for multiple clickable when there are repeated words.
Specific commands can be tailored for each repeated words.
I built upon daler445's code to allow for multiple clickable commands for repeated words.
At Java class:
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);
}
}
}
At .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" />
Demo
It really helpful for the clickable part for some portion of the text.
The dot is a special character in the regular expression. If you want to spanable the dot need to escape dot as \\. instead of just passing "." to the spanable text method. Alternatively, you can also use the regular expression [.] to spanable the String by a dot in Java.